1. 问题描述:
如下的10个格子
+--+--+--+
| | | |
+--+--+--+--+
| | | | |
+--+--+--+--+
| | | |
+--+--+--+
(如果显示有问题,也可以参看【图1.jpg】)
填入0~9的数字。要求:连续的两个数字不能相邻
(左右、上下、对角都算相邻)
一共有多少种可能的填数方案?
请填写表示方案数目的整数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字
2. 思路分析:
① 对于这种方格填0~9无重复数字的这一类题目,我们需要有一种全局的思维,因为这样的题目是可以使用全排列来解决的,类似于这样的题目还有很多,只是可能题目中求解的问题和相应的限制条件不太一样,但是我们都是可以使用全排列来做的
② 使用全排列来做,首先要生成0~9这四个数字的全排列,每生成一个排列我们使用一个函数来进行检查,所以对于这样的题目的套路在于先求解出排列然后对当前排列进行校验,看是否符合题目的要求,假如满足那么我们的计数就加一,所以对于这种问题的套路都是这样的:生成全排列加上在递归出口使用check函数校验
而求解全排列又可以使用递归加上回溯的框架来做,即交换当前位置和当前位置后面的位置的元素(包括当前位置)
③ 每生成一个全排列我们使用函数来校验,根据题目中的限定条件来进行一一的判断,对于这道题目来说,判断条件是比较多的,需要细心一点才不会出错
3. 具体的代码如下:
public class Main {
static int count = 0;
public static void main(String[] args) {
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
f(arr, 0);
System.out.println(count);
}
private static void f(int[] arr, int k) {
if(k == 10){
if(check(arr)){
count++;
for(int i = 0; i < 10; i++){
System.out.print(arr[i]);
}
System.out.print("\n");
}
return;
}
for(int i = k; i < 10; i++){
int t = arr[k];
arr[k] = arr[i];
arr[i] = t;
f(arr, k + 1);
t = arr[k];
arr[k] = arr[i];
arr[i] = t;
}
}
private static boolean check(int[] arr) {
if(Math.abs(arr[0] - arr[1]) == 1 ||
Math.abs(arr[0] - arr[3]) == 1 ||
Math.abs(arr[0] - arr[4]) == 1 ||
Math.abs(arr[0] - arr[5]) == 1 ||
Math.abs(arr[1] - arr[2]) == 1 ||
Math.abs(arr[1] - arr[4]) == 1 ||
Math.abs(arr[1] - arr[5]) == 1 ||
Math.abs(arr[1] - arr[6]) == 1 ||
Math.abs(arr[2] - arr[5]) == 1 ||
Math.abs(arr[2] - arr[6]) == 1 ||
Math.abs(arr[3] - arr[4]) == 1 ||
Math.abs(arr[3] - arr[7]) == 1 ||
Math.abs(arr[3] - arr[8]) == 1 ||
Math.abs(arr[4] - arr[5]) == 1 ||
Math.abs(arr[4] - arr[7]) == 1 ||
Math.abs(arr[4] - arr[8]) == 1 ||
Math.abs(arr[4] - arr[9]) == 1 ||
Math.abs(arr[5] - arr[6]) == 1 ||
Math.abs(arr[5] - arr[8]) == 1 ||
Math.abs(arr[5] - arr[9]) == 1 ||
Math.abs(arr[6] - arr[9]) == 1 ||
Math.abs(arr[7] - arr[8]) == 1 ||
Math.abs(arr[8] - arr[9]) == 1){
return false;
}
return true;
}
}
4. 除了上面的解法之外,我们还可以使用另外的思路,具体的思路如下:
① 我们可以这样想,对于方格中的十个数字我们可以先取出一个数字然后尝试填进去方格中,检查一下填进去之后以自己为中心的九宫格是否满足题目的限制条件,假如填入的时候不符合那么我们需要尝试另外的一个没有被访问过数字,所以需要使用一个长度为10的数组来记录数字是否之前被访问过,假如访问过,那么我们直接跳过,尝试下一个
② 因为要检查以自己为中心的九宫格是否满足条件,在检查的时候有可能数组越界,一个简单的方法是在方格的外包上一层,即可以数组声明为5行6列的数组(原来是3行4列)这样就可以避免再校验的时候发生的数组越界的问题,此外由于要填入数字进去,所以我们需要将一开始的二维数组初始化全为-10的元素,这样新增加的二维数组边缘的元素对于填进去的元素是没有影响的,初始化的时候数字只要不是0-9,-1这些数字都是可以的,只要是对填进去的数字没有影响就可以,所以在对二维数组初始化的时候这是需要注意的一个点,容易出现错误,一开始的时候我就是因为初始化的问题导致调试了很久结果一直不对
③ 因为每一个格子我都可以尝试去填数字,然后再判断填进去的数字是否满足条件,所以需要使用递归的方法去求解,每一个格子我都尝试去填,此外还需要注意回溯的问题,因为之前已经访问过当前的元素了,当递归退回来之后我还需要尝试其他的元素,所以需要将之前访问过的元素恢复到之前的状态,将访问过的元素和二维数组中填入的元素都恢复到之前的状态,这样进入到下一次的递归的时候才可以尝试其他的元素,当我们发现列的下标为4的时候直接换行,因为这个时候当前行的元素之后的元素都是-10不需要填入,直接换行即可,所以这也是需要注意的一个点
④ 这种解法与数独游戏很类似,每一次都是尝试填进去当前的数字看是否满足条件,也是使用递归的方法去求解,所以来说对于这个对于理解递归也是很有帮助的
根据上面的思路我们又可以想出求解出全排列的另外一种方法就是取出当前的元素去填这些格子,如果之前没有被访问过,那么我就可以将数字填进去,然后进行递归求解
总结一下:第一种方法是将方格转为一维数组来做,第二种方法是将其转化为二维数组来做,都有各自的好处
具体的代码如下:
public class Main {
static int arr[][] = new int[5][6];
static int visit[] = new int[10];
static int count = 0;
public static void main(String[] args) {
for(int i = 0; i < 5; i++){
for(int j = 0; j < 6; j++){
arr[i][j] = -10;
}
}
dfs(1, 2);
System.out.println(count);
}
private static void dfs(int x, int y) {
if(x == 3 && y == 4){
count++;
System.out.println(arr[1][2] + " " + arr[1][3] + " " + arr[1][4]);
System.out.println(arr[2][1] + " " + arr[2][2] + " " + arr[2][3] + " " + arr[2][4]);
System.out.println(arr[3][1] + " " + arr[3][2] + " " + arr[3][3]);
System.out.println();
return;
}
for(int i = 0; i < 10; i++){
if(visit[i] == 0){
if(check(x, y, i)){
arr[x][y] = i;
visit[i] = 1;
if(y == 4){
dfs(x + 1, 1);
}else{
dfs(x, y + 1);
}
visit[i] = 0;
arr[x][y] = -10;
}
}
}
}
public static boolean check(int x, int y, int cur) {
for(int i = x - 1; i <= x + 1; i++){
for(int j = y - 1; j <= y + 1; j++){
if(Math.abs(cur - arr[i][j]) == 1){
return false;
}
}
}
return true;
}
}