跳至主要內容

回溯

holic-x...大约 20 分钟算法算法

难度说明:🟢简单🟡中等🔴困难

回溯

todo:回溯的解题技巧

什么情况下需要递归、回溯

算法核心步骤梳理

解题思路分析

常见题型说明

🟡01-全排列(46)

1.题目内容

​ 给定一个不含重复数字的数组 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]]

2.题解思路

参考题解

​ 思路分析:对于一个长度为n的数组(假设元素互不重复),则其排列方案数共有n×(n−1)×(n−2)…×2×1

​ 排列方案分析:根据数组排列的特点,考虑深度优先搜索所有排列方案。即通过元素交换,先固定第 1 位元素( n 种情况)、再固定第 2 位元素( n−1 种情况)、... 、最后固定第 n 位元素( 1 种情况)

image-20241004105747581

👻方法1:递归

image-20241007191108981

public class Solution {

    // 定义返回结果
    List<List<Integer>> res = new ArrayList<List<Integer>>();

    // 暴力思路:多层嵌套
    public List<List<Integer>> permute(int[] nums) {
        // 调用递归方法进行全排列(此处先将nums转化为List便于处理)
        List<Integer> numsList = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            numsList.add(nums[i]);
        }
        // 调用递归方法进行全排列
        dfs(numsList,0);
        // 返回结果
        return res;
    }

    // 定义两个元素交换的方法
    public void swap(List<Integer> nums, int i, int j) {
        int temp = nums.get(i);
        nums.set(i, nums.get(j));
        nums.set(j, temp);
    }

    // 定义dfs遍历方式
    public void dfs(List<Integer> nums, int x) {
        // 递归出口,遍历到数组尾部结束
        if (x == nums.size() - 1) {
            res.add(new ArrayList<>(nums)); // 添加排列方案(此处需new一个,否则添加的是引用)
            return; // 退出当前方法
        }
        // 执行操作
        for (int i = x; i < nums.size(); i++) {
            // 核心操作:交换(将nums[i]固定在x的位置)、进行全排列算法、复原(通过再次交换,还原成原来的数组便于下一次进行排列)
            swap(nums, i, x); // 交换
            dfs(nums, x + 1); // 进行全排列算法
            swap(nums, i,x); // 复原
        }
    }
}
  • 复杂度分析

    • 时间复杂度:

    • 空间复杂度:

class Solution {
    List<Integer> nums;
    List<List<Integer>> res;

    void swap(int a, int b) {
        int tmp = nums.get(a);
        nums.set(a, nums.get(b));
        nums.set(b, tmp);
    }

    void dfs(int x) {
        if (x == nums.size() - 1) {
            res.add(new ArrayList<>(nums));  // 添加排列方案
            return;
        }
        for (int i = x; i < nums.size(); i++) {
            swap(i, x);              // 交换,将 nums[i] 固定在第 x 位
            dfs(x + 1);              // 开启固定第 x + 1 位元素
            swap(i, x);              // 恢复交换
        }
    }

    public List<List<Integer>> permute(int[] nums) {
        this.res = new ArrayList<>();
        this.nums = new ArrayList<>();
        for (int num : nums) {
            this.nums.add(num);
        }
        dfs(0);
        return res;
    }
}

👻方法2:

  • 复杂度分析

    • 时间复杂度:

    • 空间复杂度:

🟡02-子集(78)

1.题目内容

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

子集

(幂集)。

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

示例 1:

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

示例 2:

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

2.题解思路

👻方法1:回溯

  • 复杂度分析

    • 时间复杂度:

    • 空间复杂度:

class Solution {
    List<List<Integer>> res;
    List<Integer> list = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        res = new ArrayList<>();
        backTracing(nums, 0);
        return res;
    }
    public void backTracing(int[] nums, int start){
        // 每更新一次list  都加入结果集  首次进来加的是空集
        res.add(new ArrayList<>(list));
        // 到数组末尾结束当前递归
        if(start == nums.length){
            return;
        }
        for(int i = start; i < nums.length; i++){
            // 将当前数加入list
            list.add(nums[i]);
            // 递归 不能重复使用当前数 因此下一轮从i+1开始
            backTracing(nums, i+1);
            // 回溯 回退刚刚加的数
            list.remove(list.size()-1);
        }
    }
}

👻方法2:动态规划思路

核心:在原有的子集中不断追加下一个元素构建成新的子集进行补充(可以结合测试用例进行理解)

​ nums大小不一定为0,解集中一开始只有空集,然后每遍历一个数字,就拷贝当前解集中的所有子集,然后基于此子集不断追加当前数字(即构成新的子集放入解集中),以此类推,直到所有数字遍历完成

​ 此处动态规划思路:包含当前数的子集组合=上一个数的获得的每一个子集组合+当前数。所有子集=遍历的每一个数的获得的子集组合的合集

/**
 * 78.子集
 */
public class Solution {

    // 核心:循环遍历数组元素,每遍历一个数字,就基于当前子集追加当前数字构成新的子集,进而进入下一个数字遍历,以此类推直到所有的数字遍历完成
    public List<List<Integer>> subsets(int[] nums) {
        // 定义结果集合
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        res.add(new ArrayList<>()); // 初始化加入空集(空集也是子集的一部分)
        // 依次遍历数字
        for (int i = 0; i < nums.length; i++) {
            // 计算当前子集大小,遍历每个子集,将元素进行追加并生成新的子集
            int size = res.size(); // 当前子集数
            for (int j = 0; j < size; j++) {
                List<Integer> temp = new ArrayList<>(res.get(j)); // copy一份子集
                temp.add(nums[i]); // 追加元素,生成新的子集
                res.add(temp); // 追加新子集
            }
        }
        return res;
    }

}
  • 复杂度分析

    • 时间复杂度:

    • 空间复杂度:

🟡03-电话号码的字母组合(17)

1.题目内容

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

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

image-20241007194329783

示例 1:

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

示例 2:

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

示例 3:

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

2.题解思路

​ 结合题意进行分析,首先需要存储数字对应的字母序列(可以使用类似数组映射的方式或者哈希表的方式)

  • 方式1:数组映射,数字作为数组的下标索引,数组元素存储对应的字母序列
    • String[] letters = new String[]{"","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"}
  • 方式2:哈希表映射(HashMap存储)
    • Map<Character, String> phoneMap = new HashMap<Character,String>(){...}

​ 随后基于此构建回溯方法(在循环中嵌套了递归调用),可以通过穷举的方式去理解递归的演变

// 假设是"2345"数字序列,那么实际上就是通过构建多层遍历去实现字符串拼接,其实现参考如下
result = List()
for(i=0;i<len("abc");i+=1) {
    for(j=0;j<len("def");j+=1) {
        for(k=0;k<len("ghi");k+=1) {
            for(n=0;n<len("jkl");n+=1)
                tmp = i+j+k+n
                result.add(tmp)
        }
    }
}
return result

// 但显然,如果输入字符串长度不固定,则对应嵌套循环的层数也不固定,因此引入递归来实现

image-20241007220602778

​ 对于打印"2345"这样的字符串:

  • 第一次递归就是上图中最下面的方格,然后处理完第一个字符2之后,将输入的字符改变成"345"并调用第二个递归函数
  • 第二次递归处理3,将字符串改变成"45"后再次递归
  • 第三次递归处理4,将字符串改变成"5"后继续递归
  • 第四次递归处理5,将字符串改变成""后继续递归
  • 最后发现字符串为空了,将结果放到列表中并返回
  • 上面是从函数调用的角度去看的,而每次调用下一层递归时,都需要将本层的一些处理结果放到一个临时变量中,再传递给下一层,从这个变量层层传递的变化看,就像一棵树一样,这个算法的时间复杂度很高,是O(3^n)这个级别的,空间复杂度是O(n)

👻方法1:回溯

核心:解决快速寻找数组中所有存在目标字母的方法: Map加回溯(深度优先搜索思路)

image-20241007215933645

/**
 * 17.电话号码的字母组合
 */
public class Solution {

    // 定义Map哈希表存储数字和对应字母
    Map<Character, String> phoneMap = new HashMap<Character,String>() {{
        put('2', "abc");
        put('3', "def");
        put('4', "ghi");
        put('5', "jkl");
        put('6', "mno");
        put('7', "pqrs");
        put('8', "tuv");
        put('9', "wxyz");
    }};

    // 定义结果集和临时存储的结果字符串
    List<String> res = new ArrayList<>();
    StringBuffer buffer = new StringBuffer();

    // 定义回溯方法
    public void backtrack(String digits, int index) {
        if (index == digits.length()) { // 如果处理完所有的数字,则将当前的字母组合加入到结果集合中
            res.add(buffer.toString()); // 添加满足的序列
        }
        else {
            // 获取当前数字对应的字母序列
            char digit = digits.charAt(index);
            String letters = phoneMap.get(digit);
            // 遍历所有字母,对每一个字母,递归找出剩余数字的所有字母组合
            for (int i = 0; i < letters.length(); i++) {
                buffer.append(letters.charAt(i)); // 将当前字母添加到当前的字母组合中
                backtrack( digits, index+1);// 递归找出剩余数字的所有字母组合
                buffer.deleteCharAt(index);// 回溯,删除当前字母,便于尝试下一个字母
            }
        }
    }

    public List<String> letterCombinations(String digits) {
        // 判断数组是否为空,数组为空直接返回空结果集
        if(digits.isEmpty()) {
            return res;
        }
        // 数组不为空,执行回溯
        backtrack(digits, 0);
        return res;
    }
}
  • 复杂度分析

    • 时间复杂度:

    • 空间复杂度:

👻方法2:队列法

​ 结合思路分析,其本质就是依次遍历每一个数字,然后构建相应的序列,可以采用队列的方式执行(这种解法思路有点类似【子集】的动态规划解法)。以"23"为例分析队列的执行思路:

  • 23对应的字母序列分别为abcdef

  • 初始化结果集为[""](里面只有一个空字符串作为起始)

  • 遍历到第1个元素【数字2】:计算队列长度,依次取出队列元素,随后将当前字符串拼接成新字符串进行入队

    • 取出""空字符串,然后将其与数字2对应的字符a、b、c分别拼接为3个字符串进行依次入队
    • 入队完成,此时队列中有"a""b""c"三个字符串
  • 遍历到第2个元素【数字3】:计算队列长度,依次取出队列元素,随后将当前字符串拼接成新字符串进行入队

    • 先取出"a"字符串,然后将其与数字3对应的字符d、e、f分别拼接为3个字符串进行依次入队 => "ad""ae""af"入队
    • 类似地,取出"b"字符串,随后"bd""be""bf"入队
    • 类似地,取出"c"字符串,随后"cd""ce""cf"入队
  • 以此类推,直到遍历完所有的数字,最终前面的内容都会出队,而最终保留的是多种组合拼接后的字符序列

/**
 * 17.电话号码的字母组合
 * 思路:队列法
 */
public class Solution2 {

    // 定义Map哈希表存储数字和对应字母
    Map<Character, String> phoneMap = new HashMap<Character, String>() {{
        put('2', "abc");
        put('3', "def");
        put('4', "ghi");
        put('5', "jkl");
        put('6', "mno");
        put('7', "pqrs");
        put('8', "tuv");
        put('9', "wxyz");
    }};

    public List<String> letterCombinations(String digits) {
        // 如果digits为空直接返回空字符串
        if(digits == null || digits.length() == 0){
            return new ArrayList<>();
        }

        // digits 不为空
        List<String> res = new ArrayList<>();
        res.add(""); // 初始化空字符串入队
        for (int i = 0; i < digits.length(); i++) {
            // 获取当前元素及对应的字母序列
            String letters = phoneMap.get(digits.charAt(i));
            // 获取当前集合的大小(集合大小在后续循环操作过程中入队会变化,此处先记录当次循环结果集的大小)
            int resSize = res.size();
            // 将对应字母依次入队(在原有结果集基础上进行拼接)
            for (int j = 0; j < resSize; j++) {
                String s = res.remove(0); // 取出队首元素,然后依次和对应的字母序列进行拼接并将产生的结果入队
                for (int k = 0; k < letters.length(); k++) {
                    res.add(s+letters.charAt(k));
                }
            }
        }
        // 返回结果集
        return res;
    }
}
  • 复杂度分析

    • 时间复杂度:

    • 空间复杂度:

🟡04-组合总和(39)

1.题目内容

​ 给你一个 无重复元素 的整数数组 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
输出: []

2.题解思路

👻方法1:递归

​ 一些解法中会将ans和path作为方法入参,是为了避免并发场景问题,此处将其抽离为了更好地理解代码逻辑结构

核心:深度遍历得到相应路径,计算路径和是否满足target条件,如果大于则直接剪断,如果等于则满足条件加入结果集,如果小于则继续下一层遍历

public class Solution {

    private List<List<Integer>> ans = new ArrayList<>();
    private List path = new ArrayList<>();
    private int[] candidates;
    private int target;

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        this.candidates = candidates;
        this.target = target;
        dfs(0, 0); // 初始化从0开始,sum初始化为0
        return ans;
    }

    // 深度优先遍历思路,查找满足条件的路径
    private void dfs(int start, int sum) {
        // 如果指定和等于目标值,则加入结果集
        if (sum == target) {
            ans.add(new ArrayList<>(path));
            return;
        }
        // 如果指定和大于目标值则剪断
        if (sum > target) {
            return;
        }

        // 如果指定和小于目标值,则继续深度遍历、回溯
        for (int i = start; i < candidates.length; i++) {
            path.add(candidates[i]); // 将当前位置遍历元素加入路径
            dfs(i, sum + candidates[i]); // 深度遍历
            path.remove(path.size() - 1); // 回溯
        }
    }

}
  • 复杂度分析

    • 时间复杂度:

    • 空间复杂度:

🟡05-括号生成(22)

1.题目内容

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

示例 1:

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

示例 2:

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

2.题解思路

👻方法1:暴力法

​ 最简单粗暴的方式就是通过递归生成所有的括号序列,然后通过自定义方法校验序列是否有效

public class Solution1 {

    // 定义全局结果集合
    private List<String> res = new ArrayList<String>();

    // 核心:通过递归生成所有的括号序列,随后依次调用自定义方法来校验序列的有效性

    public List<String> generateParenthesis(int n) {
        generate(new StringBuilder(),n);
        return res;
    }

    public void generate(StringBuilder cur,int max) {
        // cur:当前生成的括号字符序列,max最大括号对数

        // 校验字符串长度
        if(cur.length() == 2*max) {
            // 校验括号字符序列的有效性,有效则加入结果集
            if(isValid(cur.toString())){
                res.add(cur.toString());
            }
        }else{
            // 加入左括号或者右括号,然后深度遍历到底部校验序列
            cur.append("("); // 加入左括号
            generate(cur,max);
            cur.deleteCharAt(cur.length()-1); // 复原

            cur.append(")"); // 加入右括号
            generate(cur,max);
            cur.deleteCharAt(cur.length()-1); // 复原
        }
    }

    /**
     * 自定义方法校验括号字符序列的有效性
     * @param str
     * @return
     */
    private boolean isValid(String str) {
        int balance = 0; // 定义平衡参数校验括号字符序列的有效性
        for(char c : str.toCharArray()){
            if(c=='('){
                balance++;
            }else if(c==')'){
                balance--;
            }
            if (balance < 0) {
                return false;
            }
        }
        return balance == 0;
    }

}
  • 复杂度分析

    • 时间复杂度:

    • 空间复杂度:

👻方法2:回溯

​ 基于深度遍历的思路,结合方法一进行优化,可以根据左括号的个数来决定放右括号,递归函数核心分析如下所示

  • 递归出口:如果当括号字符串长度等于2*max说明符合条件接入结果集
  • 如果左括号<max,说明还可以继续放左括号,基于此进行放入左括号、递归、回溯
  • 如果右括号<左括号,说明需要补足右括号,基于此放入右括号,递归、回溯
/**
 * 22.括号生成
 */
public class Solution {

    // 定义全局结果集合
    private List<String> res = new ArrayList<String>();

    public List<String> generateParenthesis(int n) {
        backtrack(0,0,n,new StringBuilder());
        return res;
    }

    // 思路:回溯法(深度遍历,根据左括号来决定放右括号)
    public void backtrack(int left, int right, int max, StringBuilder cur) {
        // left:左括号个数、right:右括号个数、max最大括号对数、cur当前构建的字符串序列

        // 如果当前括号字符串序列长度=2*max,说明符合条件(左右匹配规则在后面进行限定、剪枝,如果到达叶子节点则说明是满足的序列)
        if (cur.length() == 2 * max) {
            res.add(cur.toString());
            return;
        }

        // 如果左括号个数不足max说明还可继续添加左括号
        if (left < max) {
            cur.append("("); // 添加左括号
            backtrack(left + 1, right, max, cur); // 递归
            cur.deleteCharAt(cur.length() - 1); // 回溯
        }

        // 如果右括号小于左括号说明需要补足右括号
        if (right < left) {
            cur.append(")");
            backtrack(left, right + 1, max, cur);// 递归
            cur.deleteCharAt(cur.length() - 1); // 回溯
        }
    }

}
  • 复杂度分析

    • 时间复杂度:

    • 空间复杂度:

🟡06-单词搜索(79)

1.题目内容

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

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

image-20241008073857590

2.题解思路

👻方法1:回溯

核心:循环遍历一个二维数组元素,由其为起点出发进行dfs操作,校验结果即可

  • 终止条件:
    • 如果遇到边界、当前矩阵元素和目标字符不符、当前矩阵元素已经被访问过这几种情况则返回false
    • 如果遍历到目标字符串的最后一位则说明全部匹配,返回true
  • 递推工作:
    • 将当前矩阵元素标记为已访问(例如'/0')
    • 随后四个方向执行dfs操作(搜索下一单元格,向四个方向进行检索),如果四个方向中存在一个满足条件则直接返回(或操作,不需要全部找出)
    • 还原矩阵元素(将矩阵元素复原至原来的值)
/**
 * 79.单词搜索
 */
public class Solution {
    public boolean exist(char[][] board, String word) {
        // 调用递推方法实现检索:依次遍历二维数组每个元素
        int m = board.length;
        int n = board[0].length;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                // 调用递归方法校验是否存在路径,如果存在则直接记录结果返回
                if (dfs(board, word, 0, i, j)) {
                    return true;
                }
            }
        }
        return false;
    }


    /**
     * dfs检索
     */
    public boolean dfs(char[][] board, String word, int idx, int row, int col) {
        // 结束条件(遇到边界(索引为负数或者超出长度)、当前元素与目标元素不符、当前元素已被访问则返回false;如果遍历到word的最后一位则说明存在匹配word则返回true)
        // 返回false的情况
        if (row >= board.length || row < 0 || col >= board[row].length || col < 0 || board[row][col] != word.charAt(idx) || board[row][col] == '0') {
            return false;
        }
        // 返回true的情况
        if (idx == word.length() - 1) {
            return true;
        }
        // 进入递推(将元素进行标记,随后往四个方向进行递推,最终复原元素)
        char ch = board[row][col]; // 记录当前遍历元素,并标记为已访问
        board[row][col] = '0';
        if (dfs(board, word, idx + 1, row - 1, col) || dfs(board, word, idx + 1, row + 1, col) || dfs(board, word, idx + 1, row, col - 1) || dfs(board, word, idx + 1, row, col + 1)) {
            return true; // 如果四个方向中有满足条件的则返回true
        }
        board[row][col] = ch; // 复原现场
        return false;
    }
}
  • 复杂度分析

    • 时间复杂度:最坏情况下需要遍历字符串长度为K的所有方案,时间复杂度为O(3KMN)(一共有M×N个起点,每个起点对应方案数为3K(dfs搜索中有4个方向可以选择,舍弃回头的上个字符的方向,剩下3种选择))

    • 空间复杂度:O(K) 递归深度不超过K,因此系统累计使用的栈空间占用为O(K)(因为函数返回之后,对应的栈空间也会释放),最坏情况下K=MN(递归深度为MN,即所有节点都走一遍)

🟡07-分割回文串(131)

1.题目内容

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

示例 1:

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

示例 2:

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

2.题解思路

👻方法1:

核心:求分割子集、判断回文,当分割的指针走到最后一个元素则将当前结果加入结果集

/**
 * 131.分割回文串
 */
public class Solution {

    // 定义结果集
    List<List<String>> res = new ArrayList<List<String>>();
    // 定义临时子集
    List<String> temp = new ArrayList<>();

    /**
     * 核心:求子集、判断回文
     *
     * @param s
     * @return
     */
    public List<List<String>> partition(String s) {
        dfs(0, s); // 执行dfs操作,起始从0开始
        return res; // 返回结果
    }

    /**
     * 构造递推方法(深度遍历)
     * idx 为当前切割位置,s为目标字符串
     */
    public void dfs(int idx, String s) {
        // 如果切割位置和字符串长度相同,则更新结果集并返回
        if (idx == s.length()) {
            res.add(new ArrayList<>(temp)); // 此处需设定new一个子集
            return;
        }
        for (int i = idx; i < s.length(); i++) {
            // 判断是否为回文字符串
            String subStr = s.substring(idx, i + 1);
            if (isHuiWen(subStr)) {
                // 加入回文字符串,然后进行递推操作,最后复原现场
                temp.add(subStr); // 加入子集
                dfs(i + 1, s); // 执行递推
                temp.remove(temp.size() - 1); // 复原现场
            }
        }
    }

    // 双指针校验回文:校验效率较高(参考整体响应8ms)
    public boolean isHuiWen1(String str) {
        // 通过双指针来校验回文,指针相遇则说明满足回文
        int start = 0;
        int end = str.length() - 1;
        while (start < end) {
            if (str.charAt(start) != str.charAt(end)) {
                return false;
            }
            // 指针移动
            start++;
            end--;
        }
        return true;
    }

    // 字符串反转校验回文,校验效率较低(参考整体响应16ms)
    public boolean isHuiWen(String str) {
        // 通过字符串反转来校验回文
        String reverseStr = new StringBuilder(str).reverse().toString();
        return str.equals(reverseStr);
    }
}
  • 复杂度分析

    • 时间复杂度:

    • 空间复杂度:

// 参考简单写法
class Solution {
    List<List<String>> ans = new ArrayList<List<String>>();
    List<String> temp = new ArrayList<String>();
    public List<List<String>> partition(String s) {
        dfs(s,0);
        return ans;
    }
    public void dfs(String s,int index){
        if(index == s.length()){
           ans.add(new ArrayList<String>(temp));
        }
        for(int i = index;i < s.length();i++){
            String str = s.substring(index,i+1);
            String reversedStr = new StringBuilder(str).reverse().toString();
            if(reversedStr.equals(str)){
                temp.add(str);
                dfs(s,i+1);
                temp.remove(temp.size()-1);
            }
        }
        
    }
}

🔴08-N皇后(51)

1.题目内容

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

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

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

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

image-20241008085859242

2.题解思路

👻方法1:

  • 复杂度分析

    • 时间复杂度:

    • 空间复杂度:

🟡01-全排列(46)

1.题目内容

2.题解思路

👻方法1:

  • 复杂度分析

    • 时间复杂度:

    • 空间复杂度:

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3