输入
4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end
输出
417369825632158947958724316825437169791586432346912758289643571573291684164875293
416837529982465371735129468571298643293746185864351297647913852359682714128574936
剪枝
优化搜索顺序:优先处理方案数少能尽早出现方案的分支 ⭐(本题使用)
可行性剪枝:所有非法方案直接跳过不搜
最优化剪枝:当前结果已经比当前求得最优解糟糕了,继续进行搜索也不会使得结果更好,剪掉
排除等效冗余:组合和排序的问题,同一方案只搜一次
思路
一看就知道是暴搜,一看就知道会超时,嗯……
二进制表示状态,每一位上的 0 1 表示 每一位的使用状态
0 表示该数位的值已使用,1表示未使用
lowbit():快速求出某数二进制位的最低数位的 1 (刚好可以作为一种搜索方案)
代码复杂度⭐⭐⭐⭐
思维复杂度⭐⭐
import java.util.*;
public class Main
{
static int N = 9;
static int M = 1 << N;//M表示最大的状态值
static int[] ones = new int[M];// ones[i] 存 i的二进制 上有多少个 1
// map[i] 表示 i的二进制 上的 1 对应的是第几数位(从右从0开始数) map[4] = 2; (0000 0100)
static int[] map = new int[M];
// 状态都用二进制表示(1 表示该位置为空,0表示该位置有数)
// 例:000000110:表示 2,3已经使用了
static int[] row = new int[N];// 行状态
static int[] col = new int[N];// 列状态
static int[][] cell = new int[3][3];// 九宫格的状态
static char[] ss = new char[81];// 存地图
// 返回二进制 x 中的最低位 1
static int lowbit(int x)
{
return x & -x;
}
// isSet == true 即在 (x,y) 上填上 t,否则把(x,y)上的数字去掉
static void draw(int x, int y, int t, boolean isSet)
{
if (isSet)
ss[x * N + y] = (char) ('1' + t);// 整型转换为字符型
else
ss[x * N + y] = '.';// 把(x,y)位置置空
int v = 1 << t;// 二进制表示状态,1表示空,0表示放了数
if (!isSet)
v = -v;// -(-v) = +v 相当于把对应二进制位上的数 从 0 变成 1
row[x] -= v;
col[y] -= v;
cell[x / 3][y / 3] -= v;
}
// 根据输入初始化数独地图
static int init()
{
int cnt = 0;// 记录总共有多少个空格可以放
int state = (1 << N) - 1;// 初始状态state的9个二进制位全是1
Arrays.fill(row, state);
Arrays.fill(col, state);
for(int i = 0; i < 3; i++)//九宫格每个都要初始化
Arrays.fill(cell[i], state);
int k = 0;
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++, k++)
if (ss[k] != '.')
draw(i, j, ss[k] - '1', true);
else
cnt++;
return cnt;
}
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
for (int i = 0; i < N; i++)
map[1 << i] = i;// map[i] 即是 存 log(i) 的值
for (int i = 0; i < M; i++)
for (int j = i; j != 0; j -= lowbit(j))
ones[i] += 1;
while (sc.hasNext())
{
String s = sc.nextLine();
if (s.equals("end"))
break;
ss = s.toCharArray();
int k = init();
// System.out.println(String.valueOf(ss));
dfs(k);
System.out.println(String.valueOf(ss));
}
}
// 返回 x行y列和所在的九宫格可以填哪些数字,1表示可以填
static int get(int x, int y)
{
// 1011 & 1001 & 1000 == 1000 ,1 所在的位的校标表示可以填的数值
return row[x] & col[y] & cell[x / 3][y / 3];
}
static boolean dfs(int cnt)
{
if (cnt == 0)// 递归出口,填完了所有的空位
return true;
int min = 10;// 最少填多少个数字
int x = 0, y = 0;
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
if (ss[i * N + j] == '.')
{
// 获取当前位置可以填的数字
int state = get(i, j);
// 选一个分支数最小的开始递归搜索(搜索顺序优化)
if (ones[state] < min)
{
min = ones[state];
x = i;
y = j;
}
}
// System.out.println("bug");
// 对当前最少分支数的 分支 尝试进行 放合法数
for (int i = get(x, y); i != 0; i -= lowbit(i))// 每次取 低位1,每次减掉低位1
{
int t = map[lowbit(i)];// 取出低位1,映射成所对应可填的 数值
draw(x, y, t, true);// 填数
if (dfs(cnt - 1))// 看看能不能成功(成功直接返回)
return true;
draw(x, y, t, false);// 失败的尝试,回溯
}
return false;// 所有方案都跑完了,没一次成功的则返回false
}
}
参考题解