爲了使腦袋不生鏽,偶爾還是需要給腦子運動一下。今天找了一個leetCode上的一個算法來運動一下。
題目是這樣的:
在 8 X 8 的網格中,放入八個皇后(棋子),滿足的條件是,任意兩個皇后(棋子)都不能處於同一行、同一列或同一斜線上,問有多少種擺放方式?
如果有興趣的同學可以先自己思考一下,這個題目應該如何做。在看可能會更有體會。因爲主要不是代碼,看的還是思路 所以就沒有優化代碼了,可能代碼比較粗糙。進入正題:
題目描述的大概意思是,如何擺放八顆棋子是每顆棋子的上下左右 以及兩邊的正斜對角線上沒有棋子出現。用圖來描述就是
圖1表示的是一個正確的,圖二因爲右下的斜線上出現了同樣的一個所以是不符合條件。
首先說一下解題思路。因爲是一個8*8的格子,又同時需要放八個棋子。所以我們第一個得到的信息是每一行有且僅有一個棋子。因爲斜線部分影響的因素很多所以暫時是不能得到什麼有效的訊息。如果我們首先就將斜線的部分都考慮進去的話。這個干擾因素就會越來越多到最後我們只能選擇放棄。既然不能一下從全局解決這個問題那就只能將複雜問題簡單化,先從局部開始着手。
首先我們從第一行開始分析,因爲是第一個棋子所以在第一行什麼位置都是可以的。但是爲了後面的統一化,我們都是將棋子從最左邊開始放。比如我們將第一個棋子放在(0,0)位置。注意我這裏是將整個棋盤看成一個長度爲8的二維數組。左上角爲(0,0)位置。接下來我們在第二行的最左邊放第二個棋子。注意這時候我們需要判斷這個位置是否符合條件。也就是上下左右以及斜面都不能有棋子。就這樣以此類推。這個複雜的問題就是:我在第每行放棋子的時候進行以此判斷,如果不符合條件自動水平往右移動一位,繼續判斷。直到我在第八行找到合適的位置時,表示已經找到了一個符合條件的正確解。分析到這裏,這個複雜的問題是不是就已經分解成,我只需要每次放棋子的時候判斷棋子的位置時候符合條件的一個遞歸問題了。同時這個遞歸結束的條件就是在第八行找到合適的位置放下棋子就表示這一次尋找結束了。
注意
這裏我們要注意一個問題:就是我們可能第一次放棋子的位置不對導致找不到最優解。這時我們還需要考慮 回溯問題 就是回退到上一個我們的位置繼續尋找。這樣才能保證找到所有的最優解。 接下來就是代碼來演示了:
/**
在 8 X 8 的網格中,放入八個皇后(棋子),
滿足的條件是,任意兩個皇后(棋子)都不能處於同一行、同一列或同一斜線上,問有多少種擺放方式?
*/
public class Def {
public static int[][] checkerboard = new int[8][8];//棋盤
// 打印符合條件的棋子位置
public static void print(){
for(int i=0;i<checkerboard.length;i++){
for(int j=0;j<checkerboard[i].length;j++){
if(checkerboard[i][j] == 0){
System.out.print(checkerboard[i][j]+" ");
}else{
System.out.print(checkerboard[i][j]+" ");
}
}
System.out.println();
}
}
// 檢查當前棋子的位置是否符合條件
public static Boolean check2(int x,int y){
if(x>7 || x<0 || y<0 || y >7) return false;
if(checkerboard[x][y] != 0 ) return false;
for(int i=0;i<8;i++){
if(checkerboard[x][i] != 0) return false;
}
for(int i=0;i<8;i++){
if(checkerboard[i][y] != 0) return false;
}
if(!recursion2(x-1,y-1,"leftTop")) return false;
if(!recursion2(x-1,y+1,"leftDown")) return false;
if(!recursion2(x+1,y+1,"rightDown")) return false;
if(!recursion2(x+1,y-1,"rightTop")) return false;
return true;
}
// 對斜線上的位置進行檢查
public static Boolean recursion2(int x,int y,String direction){
Boolean flag = true;
switch (direction) {
case "leftTop":
while (x>=0 && y>=0){
if(checkerboard[x][y]!=0) {
flag = false;
return flag;
}
x--;
y--;
}
break;
case "leftDown":
while (x>=0 && y<=7){
if(checkerboard[x][y]!=0) {
flag = false;
return flag;
}
x--;
y++;
}
break;
case "rightTop":
while (x<=7 && y>=0){
if(checkerboard[x][y]!=0) {
flag = false;
return flag;
}
x++;
y--;
}
break;
case "rightDown":
while (x<=7 && y<=7){
if(checkerboard[x][y]!=0) {
flag = false;
return flag;
}
x++;
y++;
}
break;
}
return flag;
}
public static int count = 0;
public static void add2(int x){
// 滿足條件的棋子條件
if(x == 8) {
count++;
print();
System.out.println("=============================");
return;
}
for(int i=0;i<8;i++){
if(check2(x,i)){
checkerboard[x][i] = 1;
x++;// 這裏++是推動下一行
add2(x);
x--;// 注意這裏很重要 這就是處理 回溯問題 需要會到上一次的位置
checkerboard[x][i] = 0;//將上一次的記錄抹掉
}
}
}
public static void main(String[] args) {
add2(0);
System.out.println(count);
}
}
整個過程大概就是這樣了。代碼部分的註釋比較清楚,不明白的可以去看一下注釋。