【恋上数据结构】回溯、剪枝(八皇后、n皇后)、LeetCode51.N皇后、LeetCode52.N皇后 II

数据结构与算法笔记恋上数据结构笔记目录

回溯(Back Tracking)

回溯可以理解为:通过选择不同的岔路口来通往目的地(找到想要的结果)

  • 每一步都选择一条路出发,能进则进,不能进则退回上一步(回溯),换一条路再试

树的先序遍历图的深度优先搜索(DFS)八皇后走迷宫都是典型的回溯应用

下图中红色代表实际路线绿色代表回溯
在这里插入图片描述
不难看出来,回溯很适合使用递归

提出八皇后问题(Eight Queens)

在这里插入图片描述

初步思路一:暴力出奇迹

从 64 个格子中选出任意 8 个格子摆放皇后,检查每一种摆法的可行性
一共 C 64 8 C_{64}^{8} C648 种摆法(大约4.4 * 109 种摆法)
在这里插入图片描述

初步思路二:根据题意减少暴力程度

很显然,每一行只能放一个皇后,所以共有 88 种摆法(16777216 种),检查每一种摆法的可行性

初步思路三:回溯法(回溯+剪枝)

在解决八皇后问题之前,可以先缩小数据规模,看看如何解决四皇后问题

四皇后 - 回溯法图示

在这里插入图片描述
每次走到死路:

  • 回溯到上次路口,走另一条路;
    如果上次路口的全部路都是死路:
    • 回溯到上上次路口…

回溯途中夹杂着剪枝操作:即不走确定是死路的路,走到每个路口的时候,先判断一下这条路口的路中哪些确定是死路,就可以直接跳过这些路。
在这里插入图片描述

八皇后 - 回溯法图示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

n皇后实现

合法性检查

// 存放每一个皇后的列号(在第几列)
// cols[row] = col 表示第col行第row列摆放了皇后
int[] cols;
// 一共有多少种合理的摆法
int ways = 0;
/**
 * 判断第row行第col列是否可以摆放皇后
 */
 boolean isValid(int row, int col) {
	for (int i = 0; i < row; i++) {
		// 第col行第row列已经摆放了皇后
		if (cols[i] == col) return false;
		// 第i行的皇后根第row行第col列格子处在同一斜线上
		// 45度角斜线: y-y0 = (x-x0), 则 (y-y0)/(x-x0) = 1, 表示为45度角的斜线
		if (Math.abs(col - cols[i]) == row -i) return false;
	}
	return true;
}

从某一行开始摆放皇后

/**
 * 从第 row 行开始摆放皇后
 */
void place(int row) {
	// 如果已经放到了第n行,说明找到了一种n皇后的解法
	if (row == cols.length) {
		ways++;
		return;
	}
	for (int col = 0; col < cols.length; col++) {
		if (isValid(row, col)) {
			// 将row行col列摆放上皇后
			cols[row] = col; 
			place(row + 1);
		}
	}
}

摆放所有皇后

/** 
 * n皇后, 摆放所有皇后
 */
void placeQueens(int n) {
	if (n < 1) return;
	// 初始化
	cols = new int[n];
	place(0); // 从第0行开始放置皇后
	System.out.println(n + "皇后一共有" + ways + "种摆法");
}

打印

void show() {
	for (int row = 0; row < cols.length; row++) {
		for (int col = 0; col < cols.length; col++) {
			if (cols[row] == col) { // 摆放了皇后
				System.out.print("1 ");
			} else {
				System.out.print("0 ");
			}
		}
		System.out.println();
	}
	System.out.println("--------------------------");
}

n皇后 - 完整实现

public class Queens {
	public static void main(String[] args) {
		new Queens().placeQueens(8);
	}
	
	// cols[row] = col 表示第col行第row列摆放了皇后
	int[] cols;
	// 一共有多少种合理的摆法
	int ways = 0;
	
	/** 
	 * n皇后, 摆放所有皇后
	 */
	void placeQueens(int n) {
		if (n < 1) return;
		// 初始化
		cols = new int[n];
		place(0); // 从第0行开始放置皇后
		System.out.println(n + "皇后一共有" + ways + "种摆法");
	}
	/**
	 * 从第 row 行开始摆放皇后
	 */
	void place(int row) {
		// 如果已经放到了第n行,说明找到了一种n皇后的解法
		if (row == cols.length) {
			ways++;
			return;
		}
		for (int col = 0; col < cols.length; col++) {
			if (isValid(row, col)) {
				// 将row行col列摆放上皇后
				cols[row] = col; 
				place(row + 1);
			}
		}
	}
	/**
	 * 判断第row行第col列是否可以摆放皇后
	 */
	 boolean isValid(int row, int col) {
		for (int i = 0; i < row; i++) {
			// 第col行第row列已经摆放了皇后
			if (cols[i] == col) return false;
			// 第i行的皇后根第row行第col列格子处在同一斜线上
			// 45度角斜线: y-y0 = (x-x0), 则 (y-y0)/(x-x0) = 1, 表示为45度角的斜线
			if (Math.abs(col - cols[i]) == row -i) return false;
		}
		return true;
	}
}

在这里插入图片描述

n皇后优化 - 合法性检查优化

合法性检查优化 O(n) -> O(1)

之前的合法性检查需要通过遍历数组来实现,现在使用3个boolean数组分别表示:

  • 某一是否有皇后:boolean[] cols;
  • 某一对角线是否有皇后(左上角->右下角):boolean[] leftTop;
  • 某一对角线是否有皇后 (右上角->左下角):boolean[] rightTop;

用这个进行合法性检查只需要 O(1) 的时间复杂度。

需要知道一个小技巧:根据对角线索引(左上、右上情况不同)
在这里插入图片描述

for (int col = 0; col < cols.length; col++) {
	// 第col列已经有皇后, 继续下一轮
	if (cols[col]) continue;
	int ltIndexl = row - col + cols.length - 1; // 左上角->右下角的对角线索引
	// 左上->右下已经有皇后, 继续下轮
	if (leftTop[ltIndexl]) continue;
	int rtIndex = row + col;  // 右上角->左下角的对角线索引
	// 右上->左下已经有皇后, 继续下轮
	if (rightTop[rtIndex]) continue;
	// 给该列摆上皇后
	cols[col] = leftTop[ltIndexl] = rightTop[rtIndex] = true;
	place(row + 1); // 该列摆已经摆好了皇后,继续下一行
	// 这一步很关键, 列、对角线都是牵一发而动全身的影响, 需要重置
	cols[col] = leftTop[ltIndexl] = rightTop[rtIndex] = false;
}

完整实现

public class Queens2 {
	public static void main(String[] args) {
		new Queens2().placeQueens(8);
	}
	
	// 该变量不是必须, 仅仅是为了打印
	int[] queens;
	// 标记着某一列是否有皇后了
	boolean[] cols;
	// 标记着某一对角线是否有皇后了(左上角->右下角)
	boolean[] leftTop;
	// 标记着某一对角线是否有皇后了(右上角->左下角)
	boolean[] rightTop;
	// 一共有多少种合理的摆法
	int ways = 0;
	
	/**
	 * n皇后
	 */
	void placeQueens(int n) {
		if (n < 1) return;
		
		// 初始化
		queens = new int[n];
		cols = new boolean[n]; // 总共有n列
		leftTop = new boolean[(n << 1) - 1]; // n条对角线
		rightTop = new boolean[leftTop.length]; // 上面已经做过一次运算,无需再做
		
		place(0); // 从第0行开始摆放皇后
		System.out.println(n + "皇后一共有" + ways + "种摆法");
	}
	/**
	 * 从第 row 行开始摆放皇后
	 */
	void place(int row) {
		// 如果已经放到第n行,说明找到了一种n皇后的摆法
		if (row == cols.length) {
			ways++;
			show();
			return;
		}
		for (int col = 0; col < cols.length; col++) {
			if (cols[col]) continue; // 第col列已经有皇后, 继续下一轮
			int ltIndexl = row - col + cols.length - 1;
			if (leftTop[ltIndexl]) continue;
			int rtIndex = row + col;
			if (rightTop[rtIndex]) continue;
			queens[row] = col;
			cols[col] = leftTop[ltIndexl] = rightTop[rtIndex] = true;
			place(row + 1); // 这一列摆了皇后,继续下一列
			cols[col] = leftTop[ltIndexl] = rightTop[rtIndex] = false;
		}
	}
	
	void show() {
		for (int row = 0; row < queens.length; row++) {
			for (int col = 0; col < queens.length; col++) {
				if (queens[row] == col) { // 摆放了皇后
					System.out.print("1 ");
				} else {
					System.out.print("0 ");
				}
			}
			System.out.println();
		}
		System.out.println("--------------------------");
	}
	
}

n皇后优化 - 位运算

可以利用位运算进一步压缩八皇后的空间复杂度

/**
 * 八皇后优化 - 位运算
 */
public class Queens {
	public static void main(String[] args) {
		new Queens().place8Queens();
	}
	
	// 该变量不是必须, 仅仅是为了打印
	int[] queens;
	// 标记着某一列是否有皇后了
	// 比如 00100111 代表0、1、2、5列已经有皇后
	byte cols; // byte是8位
	// 标记着某一对角线是否有皇后了(左上角->右下角)
	short leftTop; // short是16位
	// 标记着某一对角线是否有皇后了(右上角->左下角)
	short rightTop; // short是16位
	// 一共有多少种合理的摆法
	int ways = 0;
	
	/**
	 * n皇后
	 */
	void place8Queens() {
		queens = new int[8];
		
		place(0); // 从第0行开始摆放皇后
		System.out.println("八皇后一共有" + ways + "种摆法");
	}
	/**
	 * 从第 row 行开始摆放皇后
	 */
	void place(int row) {
		// 如果已经放到第8行,说明找到了一种8皇后的摆法
		if (row == 8) {
			ways++;
			show();
			return;
		}
		for (int col = 0; col < 8; col++) {
			int colV = 1 << col; // 00000001
			if((cols & colV) != 0) continue; // col列已经有皇后
			int ltV = 1 << (row - col + 7);
			if ((leftTop & ltV) != 0) continue;
			int rtV = 1 << (row + col);
			if ((rightTop & rtV) != 0) continue;
			
			queens[row] = col;
			
			cols |= colV;
			leftTop |= ltV;
			rightTop |= rtV;
			place(row + 1); // 这一列摆了皇后,继续下一列
			cols &= ~colV;
			leftTop &= ~ltV;
			rightTop &= ~rtV;
		}
	}
	
	void show() {
		for (int row = 0; row < 8; row++) {
			for (int col = 0; col < 8; col++) {
				if (queens[row] == col) { // 摆放了皇后
					System.out.print("1 ");
				} else {
					System.out.print("0 ");
				}
			}
			System.out.println();
		}
		System.out.println("--------------------------");
	}
	
}

LeetCode 51.N皇后

leetcode_51_N皇后:https://leetcode-cn.com/problems/n-queens/

class Solution {
     public List<List<String>> solveNQueens(int n) {
    	return placeQueens(n);
    }

    int[] cols; // cols[row] = col; 表示row行col列摆放了皇后
    List<List<String>> queens;
    List<List<String>> placeQueens(int n) {
    	if (n < 1) return null;
    	cols = new int[n];
    	queens = new ArrayList<>();
    	place(0); // 从第0行开始摆
    	return queens;
    }
    
    // 在第row行摆放皇后
    void place(int row) {
    	if (row == cols.length) {
    		queens.add(put());
    		return;
    	}
    	for (int col = 0; col < cols.length; col++) {
			if (isValid(row, col)) {
				cols[row] = col; // 摆放皇后
				place(row + 1); // 去row+1行摆放皇后
			}
		}
    }
    
    boolean isValid(int row, int col) {
    	for (int i = 0; i < row; i ++) {
    		if (cols[i] == col) return false;
    		// 看作两个点: (row, col)、(i, cols[i]), 斜率为1则在斜对角 
    		if (row - i == Math.abs(cols[i] - col)) return false;
    	}
    	return true;
    }	
    
    // 将结果放入List中
    List<String> put() {
    	List<String> list = new ArrayList<>();
    	StringBuilder sb;
    	for (int row = 0; row < cols.length; row++) {
    		sb = new StringBuilder();
    		for (int col = 0; col < cols.length; col++) {
    			if (col == cols[row]) {
    				sb.append("Q");
    			} else {
    				sb.append(".");
    			}
			}
    		list.add(sb.toString());
		}
    	return list;
    }
}

leetcode 的排名看看就好,有点玄学
在这里插入图片描述

LeetCode 52.N皇后 II

leetcode_52_N皇后 II: https://leetcode-cn.com/problems/n-queens-ii/

class Solution {
  public int totalNQueens(int n) {
    	return placeQueens(n);
    }
    
    boolean[] cols; 	// 列上是否有皇后
    boolean[] leftTop; 	// 对角线左上角->右下角是否有皇后
    boolean[] rightTop; // 对角线右上角->左下角是否有皇后
    int ways = 0; 		// 摆列次数
    
    int placeQueens(int n) {
    	if (n < 1) return 0;
    	cols = new boolean[n];
    	leftTop = new boolean[(n << 1) - 1];
    	rightTop = new boolean[leftTop.length];
    	place(0);
    	return ways;
    }
    
    void place(int row) {
    	if (row == cols.length) {
    		ways++;
    		return;
    	}
    	for (int col = 0; col < cols.length; col++) {
    		if (cols[col]) continue;
    		int ltIndex = row - col + cols.length - 1 ;
    		if (leftTop[ltIndex]) continue;
    		int rtIndex = row + col;
    		if (rightTop[rtIndex]) continue;
    		cols[col] = leftTop[ltIndex] = rightTop[rtIndex] = true;
    		place(row + 1);
    		cols[col] = leftTop[ltIndex] = rightTop[rtIndex] = false;
		}
    }
}

在这里插入图片描述

无敌解法

评论区看到这个代码,把我给整笑了,😄

class Solution{
    public int totalNQueens(int n) {
        int[] rs = new int[]{0,1,0,0,2,10,4,40,92,352,724,2680};
        return rs[n];
    }
}

真就面向测试案例编程啊!
在这里插入图片描述

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萌宅鹿同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值