八皇后问题(Eight Queens Puzzle)是一个经典的回溯算法问题。问题的目标是在 8x8 的棋盘上放置八个皇后,使得它们互不攻击,即任意两个皇后都不在同一行、同一列或同一对角线上。八皇后问题是一个组合问题,也是 N 皇后问题的特例(N 皇后问题即在 N x N 的棋盘上放置 N 个皇后)。
八皇后问题的解法:
- 回溯法:从第一行开始,尝试在每一列放置一个皇后,并递归地解决剩余的皇后放置问题。
- 位运算:使用三个位掩码(一个表示列、一个表示主对角线、一个表示副对角线)来快速判断皇后是否可以放置在当前位置。
- 约束传播:在棋盘上放置一个皇后后,立即排除该皇后攻击到的所有位置,减少后续的搜索空间。
八皇后问题的特点:
- NP完全问题:八皇后问题是 NP 完全问题的一个例子,这意味着没有已知的多项式时间算法可以解决所有 N 皇后问题。
- 多种解法:八皇后问题有多种解法,通常可以通过不同的回溯策略找到所有解。
- 启发式搜索:可以使用启发式搜索算法(如 A* 算法)来优化搜索过程,减少不必要的尝试。
八皇后问题的Java实现(回溯法):
public class EightQueens {
private int[] board; // 存储皇后的位置
private int solutions; // 解法数量
public EightQueens() {
board = new int[8]; // 初始化棋盘,0 表示空,1 表示皇后
solutions = 0;
solveNQ(0); // 从第一行开始解决问题
}
public void solveNQ(int row) {
if (row == 8) { // 所有皇后都已放置
solutions++; // 增加解法数量
printBoard(); // 打印当前解法
return;
}
for (int col = 0; col < 8; col++) {
if (isValid(row, col)) { // 检查当前位置是否有效
board[row] = col; // 放置皇后
solveNQ(row + 1); // 递归解决下一行
}
}
}
public boolean isValid(int row, int col) {
// 检查同一列是否有其他皇后
for (int i = 0; i < row; i++) {
if (board[i] == col) {
return false;
}
}
// 检查主对角线是否有其他皇后
for (int i = row, j = col; i >= 0 && j >= 0; i--, j--) {
if (board[i] == col) {
return false;
}
}
// 检查副对角线是否有其他皇后
for (int i = row, j = col; i >= 0 && j < 8; i--, j++) {
if (board[i] == col) {
return false;
}
}
return true;
}
public void printBoard() {
System.out.println("Solution " + solutions + ":");
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
if (board[i] == j) {
System.out.print("Q "); // 皇后位置
} else {
System.out.print(". "); // 空位置
}
}
System.out.println();
}
System.out.println();
}
public static void main(String[] args) {
new EightQueens(); // 创建八皇后问题实例并开始求解
}
}
在面试中,八皇后问题通常用来考察应聘者的算法设计能力和问题解决技巧。通过实现八皇后问题的解决方案,可以展示你对回溯算法和递归思想的理解和应用。希望这些知识点和示例代码能够帮助你更好地准备面试!
题目 1:全排列
描述:
给定一个不含重复数字的序列,返回其所有可能的全排列。
示例:
输入: [1, 2, 3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,2,1],
[3,1,2]
]
Java 源码:
import java.util.ArrayList;
import java.util.List;
public class Permutations {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
if (nums == null || nums.length == 0) {
return result;
}
boolean used[] = new boolean[nums.length];
backtrack(result, new ArrayList<>(), nums, used, 0);
return result;
}
private void backtrack(
List<List<Integer>> result,
List<Integer> current,
int[] nums,
boolean used[],
int start) {
if (start == nums.length) {
result.add(new ArrayList<>(current));
return;
}
for (int i = 0; i < nums.length; i++) {
if (!used[i]) {
used[i] = true;
current.add(nums[i]);
backtrack(result, current, nums, used, start + 1);
current.remove(current.size() - 1);
used[i] = false;
}
}
}
public static void main(String[] args) {
Permutations solution = new Permutations();
int[] nums = {1, 2, 3};
List<List<Integer>> result = solution.permute(nums);
System.out.println("Permutations: " + result);
}
}
题目 2:组合求和
描述:
给定一个候选数字的集合(候选数字中的每个数字可以多次选择,不必选择所有候选数字),和一个目标数,找出候选数字的组合,使得它们的和为目标数。你可以假设每种输入只会对应一个答案。
示例:
输入: candidates = [2,3,6,7], target = 7
输出:
[
[2,2,3],
[2,5],
[6,1]
]
Java 源码:
import java.util.ArrayList;
import java.util.List;
public class CombinationSum {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
if (candidates == null || candidates.length == 0) {
return result;
}
Arrays.sort(candidates);
backtrack(result, new ArrayList<>(), candidates, target, 0, new boolean[candidates.length]);
return result;
}
private void backtrack(
List<List<Integer>> result,
List<Integer> current,
int[] nums,
int target,
int start,
boolean[] used) {
if (target < 0) {
return;
}
if (target == 0) {
result.add(new ArrayList<>(current));
return;
}
for (int i = start; i < nums.length; i++) {
if (i > start && nums[i] == nums[i - 1]) {
continue; // 跳过重复的候选数字
}
if (target - nums[i] < 0) {
break; // 当前候选数字太大,不可能组合出目标数
}
used[i] = true;
current.add(nums[i]);
backtrack(result, current, nums, target - nums[i], i + 1, used);
current.remove(current.size() - 1);
used[i] = false;
}
}
public static void main(String[] args) {
CombinationSum solution = new CombinationSum();
int[] candidates = {2, 3, 6, 7};
int target = 7;
List<List<Integer>> result = solution.combinationSum(candidates, target);
System.out.println("Combination Sum: " + result);
}
}
题目 3:子集和
描述:
给定一个整数数组和一个目标值,判断是否存在一个子集,其元素之和等于目标值。
示例:
输入: nums = [3, 34, 4, 12, 5, 2], target = 9
输出: true
Java 源码:
public class SubsetSum {
public boolean isSubsetSum(int[] nums, int target) {
boolean[][] dp = new boolean[target + 1][nums.length + 1];
for (int i = 0; i <= nums.length; i++) {
dp[0][i] = true;
}
for (int i = 1; i <= target; i++) {
dp[i][0] = false;
for (int j = 1; j <= nums.length; j++) {
dp[i][j] = dp[i][j - 1];
if (i >= nums[j - 1]) {
dp[i][j] = dp[i][j] || dp[i - nums[j - 1]][j - 1];
}
}
}
return dp[target][nums.length];
}
public static void main(String[] args) {
SubsetSum solution = new SubsetSum();
int[] nums = {3, 34, 4, 12, 5, 2};
int target = 9;
boolean result = solution.isSubsetSum(nums, target);
System.out.println("Is there a subset sum? " + result);
}
}
这些题目和源码展示了回溯算法和递归思想在解决组合问题中的应用。在面试中,能够根据问题的特点选择合适的算法并实现其解决方案是非常重要的。希望这些示例能够帮助你更好地准备面试!