回溯法经典难题:N 皇后问题 深度解析 + 二分查找入门(搜索插入位置)

目录

[一、N 皇后问题(LeetCode 51・困难)](#一、N 皇后问题(LeetCode 51・困难))

题目描述

解题思路

[Java 代码实现(标准回溯版)](#Java 代码实现(标准回溯版))

复杂度分析

[优化版:用 Set 记录冲突,O (1) 校验](#优化版:用 Set 记录冲突,O (1) 校验)

核心知识点总结

[二、搜索插入位置(LeetCode 35・简单)](#二、搜索插入位置(LeetCode 35・简单))

题目描述

解题思路

[Java 代码实现(标准二分版)](#Java 代码实现(标准二分版))

复杂度分析


大家好,今天我们来拆解两道经典算法题:一道是回溯法的天花板级难题 ------N 皇后问题 ,另一道是二分查找的入门题搜索插入位置。前者帮你彻底吃透回溯剪枝的核心思想,后者带你入门二分查找的标准模板,非常适合作为算法学习的进阶内容~


一、N 皇后问题(LeetCode 51・困难)

题目描述

按照国际象棋的规则,皇后可以攻击与之处在同一行、同一列或同一斜线上的棋子。

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数 n,返回所有不同的 n 皇后问题的解决方案。

每一种解法包含一个不同的 n 皇后问题的棋子放置方案,该方案中 'Q''.' 分别代表皇后和空位。你可以按任意顺序返回答案。

示例(n=4):

plaintext

复制代码
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:4 皇后问题存在两个不同的解法

解题思路

N 皇后问题是回溯法 + 剪枝的经典应用,核心逻辑可以拆解为 3 步:

  1. 逐行放置皇后:因为同一行只能放一个皇后,所以我们按行遍历,每行只放一个皇后,避免行冲突。
  2. 合法性校验 :对于当前要放置皇后的位置 (row, col),需要校验 3 个条件:
    • 同一列是否有皇后
    • 左上到右下的对角线是否有皇后(row - col 相等)
    • 右上到左下的对角线是否有皇后(row + col 相等)
  3. 回溯枚举:遍历当前行的每一列,若位置合法则放置皇后,递归下一行;递归结束后回溯,撤销皇后,尝试其他列的位置。
  4. 终止条件 :当 row == n 时,说明已经放完所有皇后,将当前棋盘加入结果集。

Java 代码实现(标准回溯版)

java

运行

复制代码
import java.util.ArrayList;
import java.util.List;

public class NQueens {
    List<List<String>> result = new ArrayList<>();

    public List<List<String>> solveNQueens(int n) {
        // 初始化棋盘:全为 '.'
        char[][] board = new char[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                board[i][j] = '.';
            }
        }
        // 从第0行开始回溯
        backtrack(board, 0, n);
        return result;
    }

    /**
     * 回溯函数
     * @param board 当前棋盘状态
     * @param row 当前放置的行号
     * @param n 棋盘大小
     */
    private void backtrack(char[][] board, int row, int n) {
        // 终止条件:所有行都放完了,将棋盘转为List<String>加入结果
        if (row == n) {
            result.add(boardToList(board));
            return;
        }

        // 遍历当前行的每一列,尝试放置皇后
        for (int col = 0; col < n; col++) {
            // 校验当前位置是否合法
            if (isValid(board, row, col, n)) {
                // 做选择:放置皇后
                board[row][col] = 'Q';
                // 递归:放置下一行
                backtrack(board, row + 1, n);
                // 撤销选择:回溯,移除皇后
                board[row][col] = '.';
            }
        }
    }

    /**
     * 校验位置(row, col)是否可以放置皇后
     */
    private boolean isValid(char[][] board, int row, int col, int n) {
        // 1. 校验同一列:从第0行到当前行,是否有皇后
        for (int i = 0; i < row; i++) {
            if (board[i][col] == 'Q') {
                return false;
            }
        }

        // 2. 校验左上到右下的对角线
        for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
            if (board[i][j] == 'Q') {
                return false;
            }
        }

        // 3. 校验右上到左下的对角线
        for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
            if (board[i][j] == 'Q') {
                return false;
            }
        }

        return true;
    }

    /**
     * 将char[][]棋盘转为List<String>
     */
    private List<String> boardToList(char[][] board) {
        List<String> list = new ArrayList<>();
        for (char[] row : board) {
            list.add(new String(row));
        }
        return list;
    }
}

复杂度分析

  • 时间复杂度:O(n!),第一行有 n 种选择,第二行最多 n-1 种,第三行最多 n-2 种,以此类推,总共有n!种可能,剪枝后实际复杂度远低于理论值。
  • 空间复杂度:O(n2),棋盘占用n2空间,递归栈深度为n。

优化版:用 Set 记录冲突,O (1) 校验

上面的代码每次校验都要遍历,我们可以用 3 个 Set 分别记录已占用的列、左上 - 右下对角线、右上 - 左下对角线,实现 O (1) 时间复杂度的合法性校验,大幅提升效率:

java

运行

复制代码
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class NQueensOpt {
    List<List<String>> result = new ArrayList<>();
    Set<Integer> colSet = new HashSet<>();    // 已占用的列
    Set<Integer> diag1Set = new HashSet<>();  // 左上-右下对角线 (row - col)
    Set<Integer> diag2Set = new HashSet<>();  // 右上-左下对角线 (row + col)

    public List<List<String>> solveNQueens(int n) {
        char[][] board = new char[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                board[i][j] = '.';
            }
        }
        backtrack(board, 0, n);
        return result;
    }

    private void backtrack(char[][] board, int row, int n) {
        if (row == n) {
            result.add(boardToList(board));
            return;
        }

        for (int col = 0; col < n; col++) {
            int diag1 = row - col;
            int diag2 = row + col;
            // O(1)校验:列、两条对角线是否被占用
            if (!colSet.contains(col) && !diag1Set.contains(diag1) && !diag2Set.contains(diag2)) {
                // 做选择
                board[row][col] = 'Q';
                colSet.add(col);
                diag1Set.add(diag1);
                diag2Set.add(diag2);

                // 递归
                backtrack(board, row + 1, n);

                // 撤销选择
                board[row][col] = '.';
                colSet.remove(col);
                diag1Set.remove(diag1);
                diag2Set.remove(diag2);
            }
        }
    }

    private List<String> boardToList(char[][] board) {
        List<String> list = new ArrayList<>();
        for (char[] row : board) {
            list.add(new String(row));
        }
        return list;
    }
}

核心知识点总结

  1. 回溯法核心:逐行枚举、合法校验、回溯撤销,是解决排列组合、棋盘类问题的通用思路。
  2. 对角线规律
    • 左上→右下:row - col 为定值
    • 右上→左下:row + col 为定值
  3. 剪枝优化:提前排除不合法的位置,避免无效递归,是回溯法的核心优化手段。

二、搜索插入位置(LeetCode 35・简单)

题目描述

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例:

plaintext

复制代码
输入: nums = [1,3,5,6], target = 5
输出: 2

输入: nums = [1,3,5,6], target = 2
输出: 1

解题思路

这道题是二分查找的标准入门题,核心是在有序数组中查找目标值,若不存在则返回插入位置,完全符合二分查找的适用场景:

  1. 初始化左右指针left = 0right = nums.length - 1
  2. 循环二分 :当 left <= right 时,计算中间位置 mid = left + (right - left) / 2(避免溢出)
  3. 比较判断
    • nums[mid] == target:直接返回 mid
    • nums[mid] < target:目标在右半区,left = mid + 1
    • nums[mid] > target:目标在左半区,right = mid - 1
  4. 循环结束 :此时 left > rightleft 就是目标值的插入位置(因为循环结束时,left 指向第一个大于 target 的元素)

Java 代码实现(标准二分版)

java

运行

复制代码
public class SearchInsert {
    public int searchInsert(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;

        while (left <= right) {
            // 计算中间位置,避免(left + right)溢出
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                // 找到目标值,直接返回索引
                return mid;
            } else if (nums[mid] < target) {
                // 目标在右半区,左指针右移
                left = mid + 1;
            } else {
                // 目标在左半区,右指针左移
                right = mid - 1;
            }
        }

        // 循环结束,left就是插入位置
        return left;
    }
}

复杂度分析

  • 时间复杂度:O(logn),二分查找每次将搜索范围缩小一半,时间复杂度为对数级。
  • 空间复杂度:O(1),仅使用常数级额外空间。
相关推荐
leo_messi942 小时前
2026版商城项目(三)-- ES+认证服务
后端·python·django
李小枫2 小时前
webflux接收application/x-www-form-urlencoded参数
android·java·开发语言
沐知全栈开发2 小时前
NumPy 字节交换
开发语言
NPE~2 小时前
[App逆向]环境搭建下篇 — — 逆向源码+hook实战
android·javascript·python·教程·逆向·hook·逆向分析
夜珀2 小时前
OpenTiny NEXT 从入门到精通·第 2 篇
开发语言·前端框架
财经资讯数据_灵砚智能2 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(夜间-次晨)2026年4月7日
人工智能·python·信息可视化·自然语言处理·ai编程
deephub2 小时前
向量数据库对比:Pinecone、Chroma、Weaviate 的架构与适用场景
人工智能·python·大语言模型·embedding·向量检索
星马梦缘2 小时前
强化学习实战5——BaseLine3使用自定义环境训练【输入状态向量】
pytorch·python·jupyter·强化学习·baseline3·gymnasium
sg_knight2 小时前
如何实现“秒传”与“断点续传”?MinIO + Java 实战进阶篇
java·开发语言·文件管理·minio·ftp·oss·文件传输