【LeetCode HOT 100】详细题解之回溯篇

news2024/12/23 5:32:58

【LeetCode HOT 100】详细题解之回溯篇

  • 回溯法的理论基础
    • 回溯法解决的问题
    • 理解回溯法
    • 回溯法模板
  • 46 全排列
    • 思路
    • 代码
  • 78 子集
    • 思路
    • 代码
  • 17 电话号码的字母组合
    • 思路
    • 代码
  • 39 组合总和
    • 思路
    • 代码
  • 22 括号生成
    • 思路
    • 代码
  • 79 单词搜索
    • 思路
    • 代码
  • 131 分割回文串
    • 思路
    • 代码
  • 51 N皇后
    • 思路
    • 代码

回溯法的理论基础

这里参考代码随想录中的回溯章节。代码随想录 (programmercarl.com)

回溯法是一种搜索的方式,是穷举所有的可能选出我们想要的答案。但是由于回溯常常和递归结合在一起,所以回溯法会比较难以理解。

回溯法解决的问题

使用回溯算法求解的一般有组合,分割,子集,排列,棋盘等问题。

组合问题:N个数里按照一定规则找出k个数的集合

切割问题:一个字符串按照规则存在几种切割方式

子集问题:N个数的集合里有多少符合条件的子集

排列问题:N个数按一定规则全排列,存在几种排列方式

棋盘问题:N皇后,解数独等等

注意排列和组合的区别,组合不强调元素顺序,排列强调元素顺序。举个例子

{1,2},{2,1}为同一个组合,但是为两个不同的排列

理解回溯法

回溯法解决的问题可以抽象为树形结构。因为解决的都是在集合中递归查找子集,集合的大小为树的宽度递归的深度构成树的深度

回溯法模板

  • 回溯函数模板返回值以及参数

在回溯算法中,函数起名字为backtracking,这个起名随意。

回溯算法中函数返回值一般为void。

参数,因为回溯算法需要的参数可不像二叉树递归的时候那么容易一次性确定下来,所以一般是先写逻辑,然后需要什么参数,就填什么参数。

void backtracking(参数)
  • 回溯算法终止条件

既然是树形结构,遍历树形结构一定要有终止条件。

所以回溯也有要终止条件。

什么时候达到了终止条件,树中就可以看出,一般来说搜到叶子节点了,也就找到了满足条件的一条答案,把这个答案存放起来,并结束本层递归。

所以回溯函数终止条件伪代码如下:

if (终止条件) {
    存放结果;
    return;
}
  • 回溯搜索的遍历过程

在上面我们提到了,回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。

在这里插入图片描述

回溯函数遍历过程伪代码如下:

for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
    处理节点;
    backtracking(路径,选择列表); // 递归
    回溯,撤销处理结果
}

for循环就是遍历集合区间,可以理解一个节点有多少个孩子,这个for循环就执行多少次。

backtracking这里自己调用自己,实现递归。

大家可以从图中看出for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,这样就把这棵树全遍历完了,一般来说,搜索叶子节点就是找的其中一个结果了。

  • 模板如下
void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

46 全排列

46. 全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

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

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

思路

先模拟一下全排列的搜索过程。从根节点搜索到叶子节点为递归,纵向遍历的过程。

在这里插入图片描述

1.回溯参数:在全排列问题中,我们需要一个标记数组来对当前数字是否使用过进行标记,因此需要传入的参数包括一个used数组。

2.终止条件:当递归搜索到叶子节点时,说明找到一个符合要求的全排列,可以终止并将当前排列假如结果集中。

3.单层搜索逻辑:如果当前数字未被使用过(used[i]=false),将used[i]置为true,之后继续搜索。如果使用过则跳过当前数字。

代码

最终代码如下。

注意终止条件这里

    //终止条件:递归搜索到叶子节点
    if(path.size()==nums.length){
        res.add(new ArrayList(path)); 
        return; //注意这里要return,因为只有遍历到叶子节点时才会取结果
    }

需要res.add(new ArrayList(path)); 而不是直接res.add(path)

res.add(new ArrayList(path))是添加path的一个副本进入结果集。

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    
    public List<List<Integer>> permute(int[] nums) {
        /**
        用used数组来去重,不能用startIndex来控制不重复
         */
        boolean[] used = new boolean[nums.length];
        backtracking(nums,used);
        return res;
    }
    private void backtracking(int[] nums,boolean[] used){
        
        //终止条件:递归搜索到叶子节点
        if(path.size()==nums.length){
            res.add(new ArrayList(path)); 
            return; //注意这里要return,因为只有遍历到叶子节点时才会取结果
        }
        //3.单层搜索逻辑
        for(int i = 0;i<nums.length;i++){
            if(used[i]){  //用过的话,跳过当前数字。
                continue;
            }
            used[i]=true;
            path.add(nums[i]);
            backtracking(nums,used); //递归
            path.remove(path.size()-1);
            used[i] = false;
        }
    }

}

78 子集

78. 子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的

子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

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

示例 2:

输入:nums = [0]
输出:[[],[0]]

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • nums 中的所有元素 互不相同

思路

和全排列问题不同,全排列问题是收集树的叶子节点。而子集问题是找树的所有节点。本题中,无序并且取过的元素不会重复取,因此回溯遍历的时候,下一层需要从startIndex开始遍历(不能取之前取过的元素。)

在这里插入图片描述

​ 1.回溯传参:startIndex为开始搜索的下标,通过startIndex来记录本层递归中,集合从哪里遍历

​ 2.终止条件: startIndex>=nums.length的时候,return (注意要在终止条件前收集子集,因为每进入新一层的递归,都需要收集子集)

​ 3.单层处理逻辑

​ path.add(i)

    private void backtracking(int[] nums,int startIndex){
        //!!收集子集,每到达递归的新一层,就会生成新的子集。所以要在终止条件之前收集子集
        res.add(new ArrayList(path));
        if(startIndex>nums.length){
            return;
        }
        for(int i = startIndex;i<nums.length;i++){
            path.add(nums[i]); //子集收集元素
            backtracking(nums,i+1); //从i+1开始,元素不重复取
            path.remove(path.size()-1); //回溯
        }
    }

代码

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();

    public List<List<Integer>> subsets(int[] nums) {
        /**
        本题和之前的组合问题相似,由于数组中元素互不相同,所以不用判断相同的情况
        1.回溯传参:startIndex为开始搜索的下标,通过startIndex来记录本层递归中,集合从哪里遍历
        2.终止条件
        startIndex>=nums.length的时候,return
        3.单层处理逻辑
        path.add(i)
         */
        backtracking(nums,0);
        return res;
    }
    private void backtracking(int[] nums,int startIndex){
        //!!收集子集,每到达递归的新一层,就会生成新的子集。所以要在终止条件之前收集子集
        res.add(new ArrayList(path));
        if(startIndex>nums.length){
            return;
        }
        for(int i = startIndex;i<nums.length;i++){
            path.add(nums[i]); //子集收集元素
            backtracking(nums,i+1); //从i+1开始,元素不重复取
            path.remove(path.size()-1); //回溯
        }
    }
}

17 电话号码的字母组合

17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

img

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"]

提示:

  • 0 <= digits.length <= 4
  • digits[i] 是范围 ['2', '9'] 的一个数字。

思路

算法思路

使用回溯算法生成所有可能的字母组合。回溯算法是一种通过试错来找到所有解决方案的算法。在这个问题中,我们需要生成所有可能的字母组合。

算法步骤

  1. 初始化:定义一个字符串数组 num2String 来映射数字到对应的字母集合。
  2. 递归终止条件:如果 path 的长度等于 digits 的长度,说明已经找到了一个完整的组合,将其添加到结果列表中。
  3. 单层递归逻辑:使用 StringBuilder 来动态构建字符串,因为 String 是不可变的,每次修改都需要创建一个新的字符串对象。
  4. 回溯:在递归调用后,使用 path.deleteCharAt() 删除最后一个字符,以便尝试下一个可能的字母。

代码

class Solution {

    List<String> res = new ArrayList<>();
    StringBuilder path = new StringBuilder();
    String[] num2String = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};

    public List<String> letterCombinations(String digits) {
        /**
        回溯
        回溯前需要定义数字->字符集的映射
        暴力搜索所有可能的组合
        1.递归终止条件:path.length() == digits.length()时,说明每个数字对应的字母都选择了一个
        
        2.单层递归逻辑
        注意,在这里由于需要不停的使用字符串拼接操作,所以用StringBuilder来实现
        path.append()
        path.deleteCharAt()

        3.注意String的用法
        String中获取某个位置的元素只能用charAt,数组才可以用下标获取。
         */
         if(digits.length()==0||digits==null){
            return res;
         }
         backtracking(digits,0);
         return res;
    }

    private void backtracking(String digits,int index){ //index为当前digits中下标为index指向的数字
        if( index == digits.length()){ //遍历到digits结尾
            res.add(path.toString());
            return;
        }
        //index = 1,digits = "23" ,那么cur_num = digits[1] = 3
        int cur_num = digits.charAt(index)-'0';
        //遍历当前数组对应的字符集,比如当前数字为3,对应的字符集为“def”
        for(int i = 0;i<num2String[cur_num].length();i++){
            path.append(num2String[cur_num].charAt(i));
            backtracking(digits,index+1);
            path.deleteCharAt(path.length()-1);
        }
    }
}

39 组合总和

39. 组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

输入: candidates = [2], target = 1
输出: []

提示:

  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • candidates 的所有元素 互不相同
  • 1 <= target <= 40

思路

算法思路

无重复元素的整数数组candidates,但是其中的同一个数字可以被无限制重复选取。因此,本题仍然需要startIndex来横向控制往后的遍历。但是纵向就不需要向前,因为仍然可以使用当前指向的元素。backtracking(candidates,target,i); //注意!因为可重复选,所以这里递归传进的是i而非i+1

    for(int i = startIndex;i<candidates.length;i++){

        //2.单层搜索逻辑
        target -=candidates[i];
        path.add(candidates[i]);
        backtracking(candidates,target,i); //注意!因为可重复选,所以这里递归传进的是i而非i+1
        path.remove(path.size()-1);
        target += candidates[i];
    }

1.递归函数参数:使用两个全局变量。二维数组res存放结果集,数组path存放符合条件的结果。题目给出的参数,集合candidates和目标值target,每次用target减去当前的数字,当target==0说明找到结果。以及startIndex控制for循环的起始位置。

2.递归终止条件:当target<0时,再搜索下去没有意义,target=0时,需要收集结果。

3.单层搜索逻辑:从startIndex开始,搜索candidates集合。

在这里插入图片描述

代码

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        /**
        本题和77组合总和的区别在于本题中的元素可重复选取,并且没有对组合元素数量进行限制
        通过组合的和来限制树的深度
        
        for控制的是横向遍历,递归控制的是纵向遍历
        举个例子[2,5,3]
        在这里,可重复选说明
        递归的话,第一次在[2,5,3]中选,递归时仍然在[2,5,3]中选。
        那如何控制元素的往前遍历呢,通过for循环控制。
         */
        backtracking(candidates,target,0);
        return res;
    }
    private void backtracking(int[] candidates,int target,int startIndex){
        //1.返回结果
        if(target<0){
            return;
        }
        if(target==0){
            res.add(new ArrayList(path));
            return;
        }
        for(int i = startIndex;i<candidates.length;i++){

            //2.单层搜索逻辑
            target -=candidates[i];
            path.add(candidates[i]);
            backtracking(candidates,target,i); //注意!因为可重复选,所以这里递归传进的是i而非i+1
            path.remove(path.size()-1);
            target += candidates[i];
        }
    }
}

22 括号生成

22. 括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:

输入:n = 1
输出:["()"]

提示:

  • 1 <= n <= 8

思路

  • 回溯,通过记录左右括号的数量判断当前应该尝试放左括号还是右括号
  • 回溯参数:左括号数量,右括号数量
  • 终止条件:左括号数量为0且右括号数量为0,说明放完了
  • 单层回溯逻辑:如果左括号数量大于0,放左括号,之后同样的逻辑放右括号

代码

class Solution {
    List<String> res = new ArrayList<>();
    StringBuilder path = new StringBuilder();
    public List<String> generateParenthesis(int n) {
        /**
        回溯,通过记录左右括号的数量判断当前应该尝试放左括号还是右括号
        回溯参数:左括号数量,右括号数量
        终止条件:左括号数量为0且右括号数量为0,说明放完了
        单层回溯逻辑:如果左括号数量大于0,放左括号,之后同样的逻辑放右括号
         */
        backtracking(n,n);
        return res;
    }
    private void backtracking(int leftCount,int rightCount){
        if(leftCount == 0 && rightCount==0){
            res.add(path.toString());
            return;
        }

        //假如leftCount >rightCount,说明剩余的左括号数量大于右括号
        //只有剩余左括号数量<=右括号数量,才有可能组成合法的括号
        // (( ) 此时剩余左括号为1,右括号为2,有可能组成合法的括号
        // (())) 此时剩余左括号为1,右括号为0,不可能组成合法的括号
        if((leftCount != 0 || rightCount != 0) && leftCount <= rightCount){
            if(leftCount!=0){
                path.append('(');
                leftCount--;
                backtracking(leftCount,rightCount);
                leftCount++;
                path.deleteCharAt(path.length()-1);
            }

            if(rightCount!=0){
                path.append(')');
                rightCount--;
                backtracking(leftCount,rightCount);
                rightCount++;
                path.deleteCharAt(path.length()-1);
            }
        }
    }

}

79 单词搜索

79. 单词搜索

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例 1:

在这里插入图片描述

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true

示例 2:

在这里插入图片描述

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE"
输出:true

示例 3:

在这里插入图片描述

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCB"
输出:false

提示:

  • m == board.length
  • n = board[i].length
  • 1 <= m, n <= 6
  • 1 <= word.length <= 15
  • boardword 仅由大小写英文字母组成

**进阶:**你可以使用搜索剪枝的技术来优化解决方案,使其在 board 更大的情况下可以更快解决问题?

思路

​ 深搜
​ 以board中的每个位置为起点,向上下左右四个方向进行深度搜索
​ dfs参数:除了board,word,还包括当前匹配到的word中的index,以及搜索方向x,y
​ 返回条件:x,y溢出边界,board[x] [y]!=word[index],board[x] [y]=‘.‘说明访问过
​ index = word.length()-1说明匹配成功
​ 单层搜索逻辑:匹配上了后给当前位置做个访问过的标记,即将当前位置元素置’.’,回溯完后再修改为原来的值

代码

class Solution {
    public boolean exist(char[][] board, String word) {
        /**
        回溯
        以board中的每个位置为起点,向上下左右四个方向进行深度搜索
        回溯参数:除了board,word,还包括当前匹配到的word中的index,以及搜索方向x,y
        返回条件:x,y溢出边界,board[x][y]!=word[index],board[x][y]='.'说明访问过
        index = word.length()-1说明匹配成功
        单层搜索逻辑:匹配上了后给当前位置做个访问过的标记,即将当前位置元素置'.',回溯完后再修改为原来的值
         */
        for(int i = 0;i<board.length;i++){
            for(int j = 0;j<board[0].length;j++){
                if(dfs(board,word,0,i,j)){
                    return true;
                }
            }
        }
        return false;
        
    }

    private boolean dfs(char[][] board,String word,int index,int x,int y){
        //1.x,y溢出边界,当前元素不等于word中的字母,访问过
        if(x<0 || x>board.length-1 || y<0 || y>board[0].length-1 || word.charAt(index)!=board[x][y] || board[x][y]=='.' ){
            return false;
        }
        if(index == word.length()-1){
            return true;
        }
        char temp = board[x][y];
        board[x][y] = '.';
        boolean res = dfs(board,word,index+1,x-1,y) || dfs(board,word,index+1,x+1,y)
        ||dfs(board,word,index+1,x,y-1) || dfs(board,word,index+1,x,y+1);
        board[x][y] = temp;
        return res;
    }
}

131 分割回文串

131. 分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

示例 1:

输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]

示例 2:

输入:s = "a"
输出:[["a"]]

提示:

  • 1 <= s.length <= 16
  • s 仅由小写英文字母组成

思路

​ 分割问题也是用回溯来解决,具体如何回溯呢
​ 以aab为例,是要模拟切割线
​ aab 开始切割
​ 第一层 a|ab aa|b aab|
​ 第二层 a|a|b aa|b| 终止
​ 第三层 a|a|b| 终止
​ 可以看到startIndex此时为切割线在字符串中的位置
​ 1.回溯的参数:结果集,路径集,切割的位置
​ 2.回溯终止条件
​ 当切割线移动到String的末尾时,切割停止
​ 3.单层处理逻辑
​ 截取当且子串[startIndex,i],如果当前子串是回文串,则将结果加入到路径中,递归(i+1)

回溯搜索图如下。

在这里插入图片描述

代码

class Solution {
    List<List<String>> res = new ArrayList<>();
    List<String> path = new ArrayList<>();
    public List<List<String>> partition(String s) {
        backtracking(s,0);
        return res;
    }
    private void backtracking(String s,int startIndex){
        //因为从起始位置一个个加的,所以结束时start一定=s.length
        if(startIndex==s.length()){
            //这里要创建path的copy版本
            res.add(new ArrayList(path));
            return;
        }
        for(int i = startIndex;i<s.length();i++){
            String substr = s.substring(startIndex,i+1);//截取子串[startIndex,i]
            if(check(substr)){
                path.add(substr);
                backtracking(s,i+1); //注意这里[startIndex,i]之间的已经被截取了,下一步要截取的为i+1开始的
                path.remove(path.size()-1);
            }
        }
    }

    //检查是否回文
    private boolean check(String s){
        for(int i = 0;i<s.length()/2;i++){
            if(s.charAt(i)!=s.charAt(s.length()-1-i)){
                return false;
            }
        }
        return true;
    }

}

51 N皇后

51. N 皇后

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

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q''.' 分别代表了皇后和空位。

示例 1:

在这里插入图片描述

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。

示例 2:

输入:n = 1
输出:[["Q"]]

提示:

  • 1 <= n <= 9

思路

本题中的回溯搜索为:for循环来搜索棋盘的每一列,递归来搜索棋盘的每一行。

  • 回溯函数参数:row:当前搜索到第row行,board[] []为当前棋盘,n为棋盘总行数。

  • 递归终止条件:当搜索到第n行时返回。

  • 单层搜索逻辑:每一层的列都要从新一行的起始位置开始搜索,所以for循环从0开始。

  • 判断合法位置

        //1.要在[row,col]位置填入Q
        //1.判断第row行其他位置是否存在Q
        // 在for循环中,每一行只会同时放一个Q,所以不用判断row行的其他位置是否存在Q
        //2.判断第col列其他位置是否存在Q,注意,由于此时row,n之间的行还没有遍历,所以不用考虑
        //3.判断斜对角线位置是否存在Q,注意,由于row+1还没有遍历到,所以只需要遍历row-1的情况
        //4.判断斜对角线位置是否存在Q
    
        //合法位置的判断
        private boolean isValid(char[][] board,int row,int col){
            int n = board[0].length;
            //1.要在[row,col]位置填入Q
            //1.判断第row行其他位置是否存在Q
            // 在for循环中,每一行只会同时放一个Q,所以不用判断row行的其他位置是否存在Q
            //2.判断第col列其他位置是否存在Q,注意,由于此时row,n之间的行还没有遍历,所以不用考虑
            for(int i = 0;i<row;i++){
                if(board[i][col]=='Q'){
                    return false;
                }
            }
            //3.判断斜对角线位置是否存在Q,注意,由于row+1还没有遍历到,所以只需要遍历row-1的情况
            for(int i = row-1,j = col-1;i>=0&&j>=0;i--,j--){
                if(board[i][j]=='Q' && i!=row && j != col){
                    return false;
                }
            }
            //4.判断斜对角线位置是否存在Q
    
            for(int i = row-1,j = col+1;i>=0&&j<n;i--,j++){
                if(board[i][j]=='Q' && i!=row && j != col){
                    return false;
                }
            }
            return true;
        }
    

回溯搜索过程如下。

在这里插入图片描述

代码

class Solution {
    List<List<String>> res = new ArrayList<>();
    
    public List<List<String>> solveNQueens(int n) {
        /**
        回溯参数:n一共有n行,row:当前搜索到第row行,board[][]为棋盘
        返回:当搜索到第n行时返回
         */
        char[][] board = new char[n][n];
        for(char[] c: board){
            Arrays.fill(c,'.');
        }    
        backtracking(n,0,board);
        return res;
    }
    private void backtracking(int n,int row,char[][] board){
        if(row==n){
            List<String> tmp = Array2List(board);
            res.add(tmp);
            return;
        }
        for(int col = 0;col<n;col++){
            if(isValid(board,row,col)){
                board[row][col] = 'Q';
                backtracking(n,row+1,board);
                board[row][col] = '.';
            }
        }
    }

    //将得到的char[][]数组转换为List<String>
    private List<String> Array2List(char[][] board){
        List<String> temp = new ArrayList<>();
        for(char[] row:board){
            temp.add(new String(row));
        }
        return temp;
    }

    //合法位置的判断
    private boolean isValid(char[][] board,int row,int col){
        int n = board[0].length;
        //1.要在[row,col]位置填入Q
        //1.判断第row行其他位置是否存在Q
        // 在for循环中,每一行只会同时放一个Q,所以不用判断row行的其他位置是否存在Q
        //2.判断第col列其他位置是否存在Q,注意,由于此时row,n之间的行还没有遍历,所以不用考虑
        for(int i = 0;i<row;i++){
            if(board[i][col]=='Q'){
                return false;
            }
        }
        //3.判断斜对角线位置是否存在Q,注意,由于row+1还没有遍历到,所以只需要遍历row-1的情况
        for(int i = row-1,j = col-1;i>=0&&j>=0;i--,j--){
            if(board[i][j]=='Q' && i!=row && j != col){
                return false;
            }
        }
        //4.判断斜对角线位置是否存在Q

        for(int i = row-1,j = col+1;i>=0&&j<n;i--,j++){
            if(board[i][j]=='Q' && i!=row && j != col){
                return false;
            }
        }
        return true;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2201045.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

教程:宏基因组数据分析教程

Orchestrating Microbiome Analysis Orchestrating Microbiome Analysis是一套包含宏基因组各种数据分析的教程&#xff0c;非常安利大家学习。 16S-analysis 16S-analysis是一本用于扩增子16s等微生物数据分析的教程&#xff0c;很适合新手入门学习。 Introduction to micro…

Android targetSdkVersion 升级为34 问题处理

原因是发布到GooglePlay遭到拒绝&#xff0c;需要最低API level为34。之前为31&#xff0c;感觉还挺高的&#xff0c;但是GooglePlay需要的更高。 记录下处理问题&#xff1a; 1.升级gradle版本为8.0.2 之前是&#xff1a; classpath com.android.tools.build:gradle:7.1.0-…

Git进行版本控制操作流程

目录 一、初始化仓库 操作流程 二、添加到缓存区 三、提交到版本库 四、推送至远程仓库 生成SSH密钥 将本地库中内容推送至已经创建好的远程库 推送 推送错误 第一种&#xff1a; 五、克隆 克隆整个项目 拉去最新代码 六、分支 1. 初始化仓库或克隆远端仓库 2…

新赚米渠道,天工AI之天工宝典!

新赚米渠道&#xff0c;天工AI之天工宝典&#xff01; 引言 随着人工智能和数字创作工具的发展&#xff0c;内容创作的门槛不断降低&#xff0c;为普通用户提供了更多的赚钱机会。在这样的背景下&#xff0c;天工AI应运而生&#xff0c;凭借其强大的创作能力和最新更新的“天…

「Ubuntu」文件权限说明(drwxr-xr-x)

我们在使用Ubuntu 查看文件信息时&#xff0c;常常使用 ll 命令查看&#xff0c;但是输出的详细信息有些复杂&#xff0c;特别是 类似与 drwxr-xr-x 的字符串&#xff0c;在此进行详细解释下 属主&#xff1a;所属用户 属组&#xff1a;文件所属组别 drwxr-xr-x 7 apps root 4…

MySql 之 Binglog 复制

复制是一种将数据从一个 MySQL 数据库服务器异步复制到另一个的技术。使用 MySQL 复制选项&#xff0c;您可以复制所有数据库、选定的数据库甚至选定的表&#xff0c;具体取决于您的使用情况。 前提条件 确保在源服务器上启用了二进制日志记录。确保复制配置中的所有服务器都有…

《云原生安全攻防》-- K8s攻击案例:从Pod容器逃逸到K8s权限提升

在本节课程中&#xff0c;我们将介绍一个完整K8s攻击链路的案例&#xff0c;其中包括了从web入侵到容器逃逸&#xff0c;再到K8s权限提升的过程。通过以攻击者的视角&#xff0c;可以更全面地了解K8s环境中常见的攻击技术。 在这个课程中&#xff0c;我们将学习以下内容&#…

使用 Go 和 Gin 框架构建简单的用户和物品管理 Web 服务

使用 Go 和 Gin 框架构建简单的用户和物品管理 Web 服务 在本项目中&#xff0c;我们使用 Go 语言和 Gin 框架构建了一个简单的 Web 服务&#xff0c;能够管理用户和物品的信息。该服务实现了两个主要接口&#xff1a;根据用户 ID 获取用户名称&#xff0c;以及根据物品 ID 获…

spring boot itext7的生成一个pdf(hello,world),并且相关一些简单的使用方法及相关说明

1、我们经常会碰到生成Pdf的场景&#xff0c;比如说有很多题目&#xff0c;又比如说来个质检的报告&#xff0c;我们都需要导出为pdf&#xff0c;那这种情况有二种方法&#xff0c;一种是通过报表来实现&#xff0c;一种就是通过itext来生成。一般我们会通过报表来直接导出pdf。…

Liquid AI与液态神经网络:超越Transformer的大模型架构探索

1. 引言 自2017年谷歌发表了开创性的论文《Attention Is All You Need》以来&#xff0c;基于Transformer架构的模型迅速成为深度学习领域的主流选择。然而&#xff0c;随着技术的发展&#xff0c;挑战Transformer主导地位的呼声也逐渐高涨。最近&#xff0c;由麻省理工学院(M…

简述何为多态

1.多态的概念 多态是什么?首先我们从概念讲起,简单来讲,多态就是多种形态,当你要去完成同一件事情的时候,不同的人去完成这件事情会有不同的结果. 比如在买票的时候,如果是成人去买票,则会买到成人票;如果是学生,则会买到学生票. 2.多态的实现以及构成条件 首先,多态的实现…

【Flutter、Web——前端个人总结】分享从业经历经验、自我规范准则,纯干货

前言 hi&#xff0c;正式接触web前端已经经过了两年的时间&#xff0c;从大学的java后端转型到web前端&#xff0c;再到后续转战Flutter&#xff0c;逐渐对前端有了一些心得体会&#xff0c;其实在当下前端的呈现形式一直在变化&#xff0c;无论你是用原生、还是web还是混编的…

Django 1.2标准日志模块出现奇怪行为时的解决方案

在 Django 1.2 中&#xff0c;标准日志模块有时会出现意想不到的行为&#xff0c;例如日志消息未按预期记录、日志级别未正确应用或日志格式错乱等。这些问题可能源于日志配置不当、日志模块被多次初始化、或日志模块被其他包覆盖等原因。下面是一些常见问题的排查方法和解决方…

力扣21~25题

21题&#xff08;简单&#xff09;&#xff1a; 分析&#xff1a; 按要求照做就好了&#xff0c;这种链表基本操作适合用c写&#xff0c;python用起来真的很奇怪 python代码&#xff1a; # Definition for singly-linked list. # class ListNode: # def __init__(self, v…

二、MySQL的数据目录

文章目录 1. MySQL8的主要目录结构1.1 数据库文件的存放路径1.2 相关命令目录1.3 配置文件目录 2. 数据库和文件系统的关系2.1 查看默认数据库2.2 数据库在文件系统中的表示2.3 表在文件系统中的表示2.3.1 InnoDB存储引擎模式2.3.2 MyISAM存储引擎模式 2.4 小结 1. MySQL8的主要…

宝塔docker中如何修改应用配置文件参数

今天在宝塔docker安装了kkfileview&#xff0c;相修改应用里的application.properties&#xff0c;却找不到在哪&#xff0c;如何修改&#xff1f; 下面教大家应用找文件修改。 docker安装好对应容器后&#xff0c;是这样 在这里是找不到对应修改的地方&#xff0c;其实docker…

Linux WIFI 驱动实验

直接参考【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.81 本文仅作为个人笔记使用&#xff0c;方便进一步记录自己的实践总结。 WIFI 的使用已经很常见了&#xff0c;手机、平板、汽车等等&#xff0c;虽然可以使用有线网络&#xff0c;但是有时候很多设备存在布线困难的情况&…

Windows10的MinGW安装和VS Code配置C/C++编译环境

1. MinGW下载安装 首先需要说明的是VS Code是一个编辑器&#xff0c;而不是编译器。‌ 编辑器和编译器是有很明显的区别 1.1 编辑器和编译器区别 编辑器‌是一种用于编写和编辑文本的应用软件&#xff0c;主要用于编写程序的源代码。编辑器提供基本的文本编辑功能&#xff0c;…

面试题:Redis(三)

1. 面试题 背景 问题&#xff0c;上面业务逻辑你用java代码如何写&#xff1f; 2. 缓存双写一致性谈谈你的理解&#xff1f; 3. 双检加锁策略 多个线程同时去查询数据库的这条数据&#xff0c;那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。 其他的线程走到这…

内核编译 设备驱动 驱动程序

内核编译 一、内核编译的步骤 编译步骤&#xff1a; (linux 内核源码的顶层目录下操作 ) 1. 拷贝默认配置到 .config cp config_mini2440_td35 .config 2. make menuconfig 内核配置 make menuconfig 3. make uImage make u…