利用位运算解决 N 皇后问题
題目:
LeetCode 51. N-Queens
分析:
N 皇后問題是考查遞歸回溯的經典問題,深度優先搜索的難點在于如何剪枝,在這個問題里面的剪枝,我們需要利用額外的空間去記錄當前行的有效空位,只要一行當中找不到有效空位,遞歸將不會繼續下去。一般的解法是開一個 n*n 的數組,優化一點解法是利用三個數組,分別表示左上,上,右上對當前位置的影響情況,這里有一個點就是一個位置會對其左下,下,右下產生影響,其中,下最好記錄,即當前空位的列,左下有一個規律是,row + col 的和是固定的,例如,(0,3),(1,2),(2,1), 它們的行號和列號加起來都是一個相同的值,右下的規律是row - col,或者 col - row 是固定的,看圖也很好理解,往右下的話,行列都會遞增,而且每次遞增幅度都是 1,Java 代碼如下:
public List<List<String>> solveNQueens(int n) {if (n <= 0) {return new ArrayList<>();}boolean[] col = new boolean[n]; // 上boolean[] pie = new boolean[2 * n]; // 左上boolean[] na = new boolean[2 * n]; // 右上List<String> result = new ArrayList<>();List<List<String>> results = new ArrayList<>();helper(results, result, col, pie, na);return results; }private void helper(List<List<String>> results,List<String> result,boolean[] col,boolean[] pie,boolean[] na) {if (col.length == result.size()) {results.add(new ArrayList<String>(result));return;}int currentRow = result.size();for (int i = 0; i < col.length; ++i) {int pieId = currentRow + i, naId = currentRow - i + col.length;if (!col[i] && !pie[pieId] && !na[naId]) {char[] row = new char[col.length];Arrays.fill(row, '.');row[i] = 'Q';result.add(new String(row));col[i] = true; pie[pieId] = true; na[naId] = true;helper(results, result, col, pie, na);col[i] = false; pie[pieId] = false; na[naId] = false;result.remove(result.size() - 1);}} } 復制代碼優化:
這里開始引入我們今天的重點,那就是能不能不用額外的空間進行記錄;可以假想 N 其實沒多大(LeetCode 的 testcases 也只有 9 個),這道題的深度優先搜索的時間復雜度其實可以看成指數冪,因此 N 不會特別大;這里有個非常巧妙的點就是利用位運算,是怎么想到的呢?首先要明確一點就是,一個空位只有兩種狀態,即有皇后和無皇后,我們之前是利用 boolean 數組,這個很直觀很容易想到,但是用三個整數來代替上面的三個 boolean 數組是否可行?在 N 不是特別大的情況是完全可行的,以 Java 為例,我們可以用 int 中的 32 個 bits 來分別表示上面代碼中的 boolean 的每個空位,代碼如下,這里 0 表示有效,1 表示無效:
private List<String> result = new ArrayList<>(); private List<List<String>> results = new ArrayList<>();public List<List<String>> solveNQueens(int n) {if (n <= 0) {return new ArrayList<>();}dfs(n, 0, 0, 0);return results; }private void dfs(int n, int col, int pie, int na) {if (n == result.size()) {results.add(new ArrayList<String>(result));return;}// 獲得所有的有效空位// (col | pie | na) 可以得到所有被占的空位,取反之后將有效空位置為 1// 與上 (1 << n) - 1,是設定考慮范圍,比如 8 皇后,那么只用考慮低 8 位即可int bit = ((~(col | pie | na)) & ((1 << n) - 1));// bit > 0 表示有空位while (bit > 0) {// 選擇最低位的一個空位int tmp = bit & (-bit);// 構建當前行的答案String str = constructString(tmp, n);result.add(str);// col | tmp 是將 col 中當前選擇的這一列置為 1,也就是無效// (pie | tmp) << 1 是設置之前行和當前行對左下的影響// (na | tmp) >> 1 是設置之前行和當前行對右下的影響dfs(n, col | tmp, (pie | tmp) << 1, (na | tmp) >> 1);result.remove(result.size() - 1);// 將當前選擇的這個最低位置為 0bit &= bit - 1;} }private String constructString(int i, int n) {char[] row = new char[n];Arrays.fill(row, '.');int tmp = 1, indx = 0;while (i != 0) {if ((tmp & i) != 0) {row[indx] = 'Q';}i >>= 1;indx++;}return new String(row); } 復制代碼這樣不但是節省了空間,而且從時間上來看,它只考慮了有效空位,和之前代碼的 for-loop 遍歷一行當中所有位置來比較的話,也有提升
轉載于:https://juejin.im/post/5cbab9a8e51d456e361ed8e5
總結
以上是生活随笔為你收集整理的利用位运算解决 N 皇后问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 宝可梦剑盾苍响怎么进化
- 下一篇: Bugku-CTF之你必须让他停下+头等