层序遍历之打开转盘锁

题目描述

题目来源于leetcode
你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转:例如把 ‘9’ 变为 ‘0’,‘0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨轮的一位数字。

锁的初始数字为 ‘0000’ ,一个代表四个拨轮的数字的字符串。

列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。

字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1。

示例 1:

输入:deadends = [“0201”,“0101”,“0102”,“1212”,“2002”], target = “0202”
输出:6
解释:
可能的移动序列为 “0000” -> “1000” -> “1100” -> “1200” -> “1201” -> “1202” -> “0202”。
注意 “0000” -> “0001” -> “0002” -> “0102” -> “0202” 这样的序列是不能解锁的,
因为当拨动到 “0102” 时这个锁就会被锁定。
示例 2:

输入: deadends = [“8888”], target = “0009”
输出:1
解释:
把最后一位反向旋转一次即可 “0000” -> “0009”。
示例 3:

输入: deadends = [“8887”,“8889”,“8878”,“8898”,“8788”,“8988”,“7888”,“9888”], target = “8888”
输出:-1
解释:
无法旋转到目标数字且不被锁定。
示例 4:

输入: deadends = [“0000”], target = “8888”
输出:-1

提示:

死亡列表 deadends 的长度范围为 [1, 500]。
目标数字 target 不会在 deadends 之中。
每个 deadends 和 target 中的字符串的数字会在 10,000 个可能的情况 ‘0000’ 到 ‘9999’ 中产生。

分析

对于每个密码有8种操作(一位2种操作,密码有4位),你可以把本此题看作一颗树处理,把target作为根节点,8种操作作为子节点,进行广度优先遍历,直到得到“0000”为止。遍历过程中不能把deadends中的死亡数字作为根节点,也不能将已经被遍历过的节点作为根节点,所以可以把已经遍历过的密码加入到deadends中,确保每次遍历过程中都得到的是新的密码以及不是死亡密码,遍历过程就是这样。
如何得到结果呢?我们需要定义一个result作为结果,result的值就是树的深度-1。那如何判断遍历时从上一层到达了下一层,也就是什么时result++呢?我们只需要在每一层的末尾添加一个null,当遍历得到的结果为null是,说明要进入下一层了,此时执行result++。

代码

class Solution {
    /*
	 * 定义移动单个拨轮+1的操作
	 * 
	 */
	public static char actionAdd(char i) {
		char j = '0';
		int temp = i - '0';
		if(i>='0' && i<='8') {
			j = (char) (temp + 1 + '0');
		}
		return j;
	}
	/*
	 * 定义移动单个拨轮-1的操作
	 * 
	 */
	public static char actionSubtract(char i) {
		char j = '0';
		int temp = i - '0';
		if(i>='1' && i<='9') {
			j = (char) (temp - 1 + '0');
		}
		if(i == '0') {
			j = '9';
		}
		return j;
	}
	/*
	 * 定义分别移动四个拨轮的操作,执行此操作后可得新的8种转盘锁的状态
	 * 
	 */
	public static List<String> action(String str) {
		List<String> list = new ArrayList<>();//定义动态数组存储8种结果
		char[] s = str.toCharArray();
		char[] m = Arrays.copyOf(s, s.length);
		for(int i=0;i<s.length;i++) {
			s[i] = actionAdd(s[i]);
			list.add(new String(s));
			s = Arrays.copyOf(m, m.length);//每一次操作都是在原状态下进行的,所以每次操作完后重置为原状态
			s[i] = actionSubtract(s[i]);
			list.add(new String(s));
			s = Arrays.copyOf(m, m.length);
		}
		return list;	
	}
	public int openLock(String[] deadends, String target) {
		Set<String> dead = new HashSet();//定义Hashset来方便查找8种结果中是不是存在死亡数字
        for(String str:deadends) dead.add(str);	
		int result = 0;
		Queue<String> queue= new LinkedList<>();
		queue.add(target);
		queue.add(null);
	
		while(!queue.isEmpty()) {
			
			String s = queue.poll();
		
			if(s==null) {
				result++;
				if(queue.peek()!=null)
                    queue.offer(null);//在每一层遍历完后,添加一个null,只要s=null就说明即将处于下一层了,结果+1
			}else if(s.equals("0000")){
				return result;
			}else{
				dead.add(s);//把已经遍历过的密码添加进dead
				List<String> l = new ArrayList<>();
				l = action(s);
				for(String x : l) {
					if(!dead.contains(x)) {//确认x是新的未遍历过的密码并且不是死亡数字
						queue.add(x);
						dead.add(x);//此时的x已经遍历得到过,加入dead
					}										
				}
			}
		}
		return -1;
			
    }
}

相关知识

LinkedList的poll()方法:检索并删除此列表的头部(第一个元素),返回值是被删除的元素
LinkedList的peek()方法:检索但不删除此列表的头部(第一个元素),返回值是第一个元素
LinkedList的offer​(E e)方法:将指定的元素添加为此列表的尾部(最后一个元素)。
HashSet的contains​(Object o) 方法:如果此set包含指定的元素,则返回 true 。
Arrays类的copyOf()方法和copyOfRange()方法用来复制数组:

String string = "abc";
char[] str = string.toCharArray();
char[] newstr1 = Arrays.copyOf(str, str.length);
char[] newstr2 = Arrays.copyOfRange(str, 0, 3);

Arrays 类的 copyOf() 方法的语法格式如下:

Arrays.copyOf(dataType[] srcArray,int length);
其中,srcArray 表示要进行复制的数组,length 表示复制后的新数组的长度。

使用这种方法复制数组时,默认从源数组的第一个元素(索引值为 0)开始复制,目标数组的长度将为 length。如果 length 大于 srcArray.length,则目标数组中采用默认值填充;如果 length 小于 srcArray.length,则复制到第 length 个元素(索引值为 length-1)即止。

Arrays 类的 CopyOfRange() 方法是另一种复制数组的方法,其语法形式如下:

Arrays.copyOfRange(dataType[] srcArray,int startIndex,int endIndex)

其中,srcArray 表示源数组;startIndex 表示开始复制的起始索引,目标数组中将包含起始索引对应的元素,另外,startIndex 必须在 0 到 srcArray.length 之间;endIndex 表示终止索引,目标数组中将不包含终止索引对应的元素,endIndex 必须大于等于 startIndex,可以大于 srcArray.length,如果大于 srcArray.length,则目标数组中使用默认值填充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值