1、题目描述
2、解题思路
题目要求统计至少经过 m 个点,但是不超过 n 个点的所有解锁手势种类数量,因此,我们分别统计走过 m、走过 m+1、…、走过 n 个点的解锁手势种类,然后全部加起来就是至少经过 m 个点又不超过 n 个点的手势种类数。
以下为统计走过 i 个点的手势种类数计算:
我们从一个点出发,不断滑来滑去,直到总共划过 i 个点,就是一种解锁手势,现在要统计总共有多少种,很明显要进行 DFS,即深度优先搜索遍历。
而且再仔细观察本题,会发现一个规律:从 1、3、7、9 出发的种类数是一样的,从 2、4、6、8 出发的种类数也是一样的。
因此,我们只要计算从 1 出发的解锁种类数;然后乘以4,计算 从 2 出发的解锁种类数,然后乘以 4;最后加上从 5 出发的解锁种类数;三者加起来,就是经过 i 个点的手势种类数了。
本题还有一个要求,即,如果两个点中间存在未走过的点,是不能走过去的。
因为总共才 1-9 的点,我们用二维整型数组 skip[][] 来记录两点中间的点的情况,比如 skip[1][3] = 2,表示 1 和 3 中间有一个 2。
再用一个一维布尔型数组 visited[] 来表示某个点是否走过,比如 visited[3] true 表示 3 已经走过。
明白了上面的准备工作,我们就可以设计 DFS 函数了,DFS 函数的功能是:统计从某个点出发的所有不同的解锁手势的种类数。
public int DFS(int current, boolean []visited, int [][]skip,int remainKeyCount);
其中,current 表示出发点,remainKeyCount 表示走了 current 后的剩余可走步数。
注意:在 DFS 函数的开始,要把 current 设置为 true,到 DFS 函数结束之前,要重新设置为 false。毕竟每一个点都是可以重复利用的。
3、解题代码
class Solution {
public int numberOfPatterns(int m, int n) {
int[][] skip = new int[10][10];
//这个skip数组是为了记录跳跃的点数,比如说从1到3,就跳跃2
//而且因为是对称的操作,所以3到1也是如此
skip[1][3] = skip[3][1] = 2;
skip[1][7] = skip[7][1] = 4;
skip[3][9] = skip[9][3] = 6;
skip[4][6] = skip[6][4] = skip[2][8] = skip[8][2] = 5;
skip[1][9] = skip[9][1] = skip[3][7] = skip[7][3] = 5;
skip[7][9] = skip[9][7] = 8;
int result = 0;
boolean[] visited = new boolean[10];
//深度遍历所有情况,即走过的路程分别为 m,m+1,...,n-1,n 时的方法数
for(int i = m; i<=n; i++){
// 为什么 i 要减一呢,因为我们是从 i 出发,即出发也算走了一步
//因为从1,3,7,9出发都是对称的
result += DFS(1,visited,skip,i-1)*4;
//2,4,6,8对称
result += DFS(2,visited,skip,i-1)*4;
//唯独5独立
result += DFS(5,visited,skip,i-1);
}
return result;
}
/**
*
* @param current 起点
* @param visited 保存所有点是否走过的布尔数组
* @param skip 记录两点之间是否存在其他点的二维数组
* @param remainKeyCount 剩余可走步数
* @return
*/
public int DFS(int current, boolean []visited, int [][]skip,int remainKeyCount){
if(remainKeyCount == 0){
return 1;
}
int result = 0; // 从这个点开始,往后走完 remainKeyCount 的所有可能情况数
visited[current] = true; // 当前点已经走过了
// 遍历 current 的下一步所有的可能目标
for(int i = 1; i <= 9; i++){
// 看当前的节点到i节点的路径中有没有其他节点在中间
int crossThroughNumber = skip[current][i];
// 如果这一次我们的i节点没有被读过,如果读过,就忽略这个点
// 如果没用过,看能不能走过去
// 即判断有没有路过中间节点(visited[crossThroughNumber])或者这两个节点相邻没有中间节点(currentThrough=0)
if(!visited[i] && (crossThroughNumber == 0 ||visited[crossThroughNumber])){
// current 点已经处理好,剩下的路就交给 i 点了,已走了一步,因此 remainKeyCount-1
result += DFS(i,visited,skip,remainKeyCount-1);
}
}
visited[current] = false; // 已经统计好 current 往下走的所有可能情况数,重置为 false
return result;
}
}