chapter_backtracking/n_queens_problem/ #484
Replies: 27 comments 44 replies
-
牛啊 解法写的清晰易懂 |
Beta Was this translation helpful? Give feedback.
-
K神您好!相较于网上其他的解题思路,您的代码简单高效。 |
Beta Was this translation helpful? Give feedback.
-
您好,感谢解答!
我使用N=4,正确的排列结果应该是2种,但是程序输出165种。
改为if(!cols[col] && !diags1[diag1] && !diags2[diag2])后,输出为2种。后续我测试了N=5、6、7,结果分别是10种、4种、40种。
附件中是我的测试样例。
祝好!
…------------------ 原始邮件 ------------------
发件人: "krahets/hello-algo" ***@***.***>;
发送时间: 2023年6月20日(星期二) 中午1:58
***@***.***>;
***@***.******@***.******@***.***>;
主题: Re: [krahets/hello-algo] chapter_backtracking/n_queens_problem/ (Discussion #484)
Hi 可以分享下结果不正确的测试样例吗?
if(!cols[col] && !diags1[diag1] && !diags2[diag2]) 和 !(cols[col] || diags1[diag1] || diags2[diag2]) 是等价的,后者也只有在三者都为 false 时输出 true
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you commented.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
实在是泰裤辣!逻辑清晰,代码也通俗易懂,注释也详细,k神yyds! |
Beta Was this translation helpful? Give feedback.
-
这个对角线判断就简单了,我之前都是直接遍历 public static void main(String[] args)
{
int len = 8;
int[][] chess = new int[len][len];
// put(chess,1,1);
putChess(chess, 0);
}
public static int solution = 0;
public static void putChess(int[][] chess, int time)
{
// 扫描了所有行就结束
if (time == chess.length)
{
solution++;
System.out.println("第" + solution + "种解法");
for (int[] ints : chess)
{
for (int i = 0; i < ints.length; i++)
{
if (ints[i] == 1)
{
System.out.print(i+" ");
break;
}
}
}
System.out.println();
return ;
}
for (int i = 0; i < chess[time].length; i++)
{
// 如果是0表示可以放棋,1表示已经放置棋子
if (chess[time][i] == 0)
{
//缓存落子之前的状态用于恢复
int[][] temp = tempArr(chess);
// 标记无法放置棋子的区域,和放置棋子的位置
put(chess, time, i);
// 一层只能放一个
putChess(chess, time+1);
//恢复上一次落子
chess = temp;
}
}
}
/**
* 落子
* @param chess 棋盘
* @param cI 落子位置
* @param cJ 落子位置
*/
public static void put(int[][] chess, int cI, int cJ)
{
int tj1 = cJ;
int tj = cJ;
//每层只放一个棋子,所以只需要关注列和斜线
for (int i = cI; i < chess.length; i++)
{
chess[i][cJ] = 2;
if (tj + 1 < chess[i].length && i + 1 < chess.length)
{
chess[i + 1][++tj] = 2;
}
if (tj1 - 1 >= 0 && i + 1 < chess.length)
{
chess[i + 1][--tj1] = 2;
}
}
// 1表示放置了棋子
chess[cI][cJ] = 1;
}
/**
* 暂存棋盘
* @param arr 棋盘
*/
public static int[][] tempArr(int[][] arr)
{
int[][] temp = new int[arr.length][];
for (int i = 0; i < arr.length; i++)
{
temp[i] = Arrays.copyOf(arr[i],arr[i].length);
}
return temp;
} |
Beta Was this translation helpful? Give feedback.
-
看不懂原文判断对角线的方法,于是参照着写了一版:joy: def valid(state: list[list[bool]], row: int, col: int):
n = len(state)
for i in range(row):
_row = state[i]
# 对角线
if i != row:
offset = abs(row - i)
if col - offset > -1 and _row[col - offset]:
return False
if col + offset < n and _row[col + offset]:
return False
for j in range(n if i < row else col):
# 行
if i == row and _row[j]:
return False
# 列
if j == col and _row[j]:
return False
return True
def backtrack(
state: list[list[bool]],
row: int,
col: int,
count: int,
res: list[list[list[bool]]]
):
n = len(state)
if count == n:
res.append([i[:] for i in state])
return
for i in range(row, n):
for j in range(col, n):
if not valid(state, i, j):
continue
count += 1
state[i][j] = True
# 本行已放子,尝试下一行
backtrack(state, i + 1, 0, count, res)
count -= 1
state[i][j] = False
# 回到第一列
col = 0
def nQueen(n: int):
res = []
# 棋盘状态
state = [[False for _ in range(n)] for _ in range(n)]
backtrack(state, 0, 0, 0, res)
return res |
Beta Was this translation helpful? Give feedback.
-
diags2: list[bool], 后面多打了一个逗号哈哈哈 |
Beta Was this translation helpful? Give feedback.
-
K神您好!想请问一下为什么在记录解时将代码res.append([list(row) for row in state])改为res.append(state)会导致无法输出正确的结果呢? |
Beta Was this translation helpful? Give feedback.
-
/ 计算该格子对应的主对角线和副对角线 |
Beta Was this translation helpful? Give feedback.
-
写的太好了,尤其是图画的好,学起来好轻松! |
Beta Was this translation helpful? Give feedback.
-
想问下这个row+col的范围为什么是[0,2n-2],不是[2,2n] |
Beta Was this translation helpful? Give feedback.
-
hi,我对这里的空间复杂度有一些困惑: |
Beta Was this translation helpful? Give feedback.
-
好的 感谢你的回答 我明白了
|
Beta Was this translation helpful? Give feedback.
-
数组 diag1 和 diag2 的长度都为 2n-1,这里应该是diags1和diags2吧? |
Beta Was this translation helpful? Give feedback.
-
每次看都觉得不可思议。老实说,我就很好奇,生活中究竟是多优秀的人,才能写出这样代码 |
Beta Was this translation helpful? Give feedback.
-
state[i][n]不是溢出了吗,假如是4X4棋盘,n=4=Maxsize,最大的数组不是[1][3]吗? |
Beta Was this translation helpful? Give feedback.
-
求教一个问题:解集 |
Beta Was this translation helpful? Give feedback.
-
时间复杂度的分析有些问题,递归到 base case |
Beta Was this translation helpful? Give feedback.
-
K神,您好!我想请问一下为什么将记录解的代码改成res.add(new ArrayList<>(state);之后的结果出错了呢? |
Beta Was this translation helpful? Give feedback.
-
K神,有个问题想请教,为什么 diag1 = row - col + n - 1, 而不是 diag1 = col - row + n - 1? 当遍历到(row=1, col=2) 时(第一个queen已经放在第一行第一列),尝试将queen放在这个位置,随后diags1[diag1]=T,使得diags1=[FFTTFFF],但是以图13-18为例,queen放在第二行第三列,diags1应该是[FFFTTFF]。 我哪里的理解出了问题? |
Beta Was this translation helpful? Give feedback.
-
按照之前的逻辑写下来,比较顺畅 # 回溯方法,n皇后问题 state列表记录每一行纵坐标 如[0,2,1]-> 第一个皇后坐标(0,0),第二个(1,2)
# state: 表示每一行皇后的纵坐标,取值0~n,n: 表示n皇后问题,res:结果列表
# 皇后的坐标为(x, y),x代表棋盘的横坐标,y代表棋盘的纵坐标
def trackback_queen(state: list[int], n: int, res: list[list[int]]):
if len(state) == n:
res.append(list(state))
return
# 新插入的皇后坐标 (i, len(state))
for i in range(n):
flg = True
# 遍历state中的皇后坐标 (state[j], j)
for j in range(len(state)):
# 横坐标相等 或者 纵坐标相等(纵坐标必不相等,跳过检查) 或者 横纵坐标的差相等(绝对值)
if state[j] == i or abs(i - state[j]) == abs(j - len(state)):
flg = False
break
if flg is True:
state.append(i)
trackback_queen(state, n, res)
state.pop()
# 入口函数
def trackback_queen_fn(n: int) -> list[list[int]]:
res = []
trackback_queen([], n, res)
return res |
Beta Was this translation helpful? Give feedback.
-
hello,关于go语言版本,我有些小疑惑 if !(*cols)[col] && !(*diags1)[diag1] && !(*diags2)[diag2] { 感觉三个条件的顺序并不会影响判断结果,但是经过测试,当把 if !(*diags1)[diag1] && !(*diags2)[diag2] && !(*cols)[col] { 会出现索引越界的错误
发现会出现 |
Beta Was this translation helpful? Give feedback.
-
图示中两个对角线的数组名称是不是标反了 |
Beta Was this translation helpful? Give feedback.
-
对于棋盘,我们可以用一个一维的 int 数组来表示。每个元素表示第 i 排的皇后所在的 j 列。比如state[0] = 1 ,就表示第 0 排的皇后在第 1 列。这样我们就可以将空间复杂度缩小到 大致的算法如下 func backtrackNQueens2(state []int, N int, occupiedCols []bool,
occupiedDiag1 []bool, occupiedDig2 []bool, ret *[][]int) {
if len(state) == N {
board := append([]int{}, state...)
*ret = append(*ret, board)
return
}
row := len(state)
for j := 0; j < N; j++ {
if occupiedCols[j] || occupiedDiag1[row+j] || occupiedDig2[row-j+N-1] {
continue
}
state = append(state, j)
occupiedCols[j] = true
occupiedDiag1[row+j] = true
occupiedDig2[row-j+N-1] = true
backtrackNQueens2(state, N, occupiedCols, occupiedDiag1, occupiedDig2, ret)
state = state[:len(state)-1]
occupiedCols[j] = false
occupiedDiag1[row+j] = false
occupiedDig2[row-j+N-1] = false
}
} |
Beta Was this translation helpful? Give feedback.
-
图13-18.,主次对角线说反了。 |
Beta Was this translation helpful? Give feedback.
-
学习感想:
const diag1 = row - col + n - 1;
const diag2 = row + col; 自己写的代码没有这两个优化,要慢很多。非常受启发。 |
Beta Was this translation helpful? Give feedback.
-
补充,时间复杂度,个人认为for循环无论在哪一层都应该操作n次,故不考虑其他的原来操作次数是n+n*n+n^3+n^4+……+n^n,故为n^n,但是能进入到下一层的递归函数会逐渐锐减,原来每个i都能进入下一层递归函数,从而执行n循环操作,现在有些路直接断了,怎么办呢?第一层显然仍为n,往下传的节点数量为n,第二层路径开始截断但是每个i仍有完整for循环,n^2,其截出来的路径每个i保底少于n-1,故往下传的节点数量小于n(n-1),第三层每个节点for循环故小于n(n-1)*n,往下传的节点数量小于n(n-1)(n-2),…………第n层循环小于n!*n,上一层传下来的节点数量小于n!,故对于深拷贝复制的时间复杂度至多就有O(n!*n^2)了(可当成上界函数了,虽然放缩的太远了),再来计算每一层的for循环小于O(n+n^2+……+n!*n),故最终时间复杂度取n!*n,小于O(n!*n^2)。而这还没考虑回退操作总体,但其实其根本改变不了整体时间复杂度了,因为太大了目前。这只是最离谱的情况,事实上真正能走到底的路径很少,中途能连上的很少,所以for循环操作次数和深拷贝的解个数都远小于n+n^2+……+n!*n)和n! |
Beta Was this translation helpful? Give feedback.
-
chapter_backtracking/n_queens_problem/
一本动画图解、能运行、可提问的数据结构与算法入门书
https://www.hello-algo.com/chapter_backtracking/n_queens_problem/
Beta Was this translation helpful? Give feedback.
All reactions