回溯 力扣hot100热门面试算法题 面试基础 核心思路 背题 括号生成 单词搜索 分割回文串 N皇后 全排列 子集 电话号码的字母组合 组合总和

回溯

括号生成

https://leetcode.cn/problems/generate-parentheses/

核心思路

  1. 深度优先搜索(DFS):
    • DFS是探索所有可能路径的方法,这里用于生成括号组合。
    • 在每一步中,算法有两个选择:放置左括号'('或右括号')'(受限于当前路径的合法性)。
  2. 剪枝策略:
    • 通过维护当前路径中左括号的数量cnt,算法确保不会放置过多的左括号(不超过n)。
    • 通过比较当前位置i与已放置的左括号数量cnt,算法确保右括号的数量不会超过左括号的数量,从而保持括号组合的合法性。
  3. 终止条件:
    • 当当前路径path被填满时(即i == n * 2),算法检查该路径是否形成了一个合法的括号组合,并将其添加到结果列表中。

示例代码

class Solution {

    private int n; // 括号对的数量
    private final List<String> ans = new ArrayList<>(); // 存储最终生成的合法括号组合
    private char[] path; // 用于存储当前递归路径中的括号组合

    public List<String> generateParenthesis(int n) {
        this.n = n; // 初始化括号对的数量
        path = new char[n * 2]; // 每组合法括号的长度为 n * 2(n 对括号)
        dfs(0, 0); // 从第 0 个字符开始递归,初始左括号计数为 0
        return ans; // 返回生成的合法括号组合列表
    }

    /**
     * 深度优先搜索函数
     * @param i 当前递归到路径的第几个字符(当前填充的索引位置)
     * @param cnt 当前路径中左括号 '(' 的数量
     */
    void dfs(int i, int cnt) {
        // 递归终止条件:当前路径已经填满 n * 2 个字符
        if (i == n * 2) {
            ans.add(new String(path)); // 将当前路径的合法括号组合加入结果列表
            return; // 返回上层递归
        }

        // 选择左括号 '(' 的情况
        if (cnt < n) { // 如果左括号的数量还没有达到 n
            path[i] = '('; // 在当前路径位置填入左括号
            dfs(i + 1, cnt + 1); // 递归到下一个字符位置,并增加左括号计数
        }

        // 选择右括号 ')' 的情况
        // 右括号的数量必须小于左括号数量,确保括号组合合法
        if (i - cnt < cnt) { 
            path[i] = ')'; // 在当前路径位置填入右括号
            dfs(i + 1, cnt); // 递归到下一个字符位置,左括号计数不变
        }
    }
}

单词搜索

https://leetcode.cn/problems/word-search/

核心思路

1.优化一 字母个数够不够

2.头和尾 哪个重复字母数少,哪个在前

示例代码

class Solution {
    char[] word;
    char[][] board;
    int n, m;
    //上下左右四个方向
    int[] dx = {-1,0,1,0};
    int[] dy = {0,1,0,-1};
    int cnt;

    public boolean exist(char[][] board, String word) {
        this.word = word.toCharArray();
        this.board = board;
        n = board.length;
        m = board[0].length;

        // 数组代替哈希表
        int[] cnt = new int[128];
        for (char[] row : board) {
            for (char c : row) {
                cnt[c]++;
            }
        }

        // 优化一 字母个数够不够
        int[] wordCnt = new int[128];
        for (char c : this.word) {
            if (++wordCnt[c] > cnt[c]) {
                return false;
            }
        }

         // 优化二 头和尾 哪个重复字母数少,哪个在前
        if (cnt[this.word[this.word.length - 1]] < cnt[this.word[0]]) {
            this.word = new StringBuilder(word).reverse().toString().toCharArray();
        }
        
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++)
                if (dfs(i, j, 0))
                    return true;

        return false;
    }

    boolean dfs(int i,int j,int cnt){
        if (board[i][j] != word[cnt]) { // 匹配失败
            return false;
        }
        if (cnt == word.length - 1) { // 匹配成功!
            return true;
        }
        board[i][j] = '0'; // 标记访问过
        
        //往四个方向
        for(int k = 0;k<4;k++){
            int xx = i + dx[k];
            int yy = j + dy[k];
            if(xx>=0 && xx < n && yy >= 0 && yy < m && dfs(xx,yy,cnt+1))
                return true;
        }
        board[i][j] = word[cnt]; // 恢复现场
        return false; // 没搜到
    }
}

分割回文串

https://leetcode.cn/problems/palindrome-partitioning/

核心思路

深度优先搜索(DFS)结合回溯法来探索字符串的所有可能回文分割。从字符串的起始位置开始,递归地尝试在每个位置插入分隔符,但仅当当前子串是回文时才真正插入。每次插入分隔符后,递归地处理剩余部分。若无法继续形成有效分割,则回溯,即移除最后一个加入的子串并尝试其他分割点。当遍历完整个字符串时,若形成了一种有效的回文分割,则将其保存。

示例代码

class Solution {
    private final List<List<String>> ans = new ArrayList<>();
    private final List<String> path = new ArrayList<>();
    private String s;

    public List<List<String>> partition(String s) {
        this.s = s; 
        dfs(0, 0); 
        return ans; 
    }

    //i 当前字符的索引 start 当前子串的起始索引
    private void dfs(int i, int start) {
        // 如果当前索引已经到达字符串末尾,说明形成了一种分割路径
        if (i == s.length()) {
            ans.add(new ArrayList<>(path)); // 将当前路径加入结果集(注意这里需要复制 path)
            return;
        }

        // 不选择在 i 和 i+1 之间插入分隔符(继续往后递归)
        if (i < s.length() - 1) {
            dfs(i + 1, start);
        }

        // 选择在 i 和 i+1 之间插入分隔符(以 s[i] 结尾作为一个子串)
        if (isPalindrome(start, i)) { // 先判断当前子串是否为回文
            path.add(s.substring(start, i + 1)); // 如果是回文,将当前子串加入路径
            dfs(i + 1, i + 1); // 递归处理剩余部分(下一个子串从 i+1 开始)
            path.remove(path.size() - 1); // 恢复现场(回溯,移除最后一个子串)
        }
    }
	//判断是否是回文串
    private boolean isPalindrome(int left, int right) {
        while (left < right) { 
            if (s.charAt(left++) != s.charAt(right--)) 
                return false;
        }
        return true;
    }
}

N皇后

https://leetcode.cn/problems/n-queens/

核心思路

最核心的思路就是怎么进行标记。

正斜线:用直线方程表示: y-x+n=0 (加n是为了有偏移量,因为坐标必须>0,y-x有可能<0)

反斜线:y+x=0

示例代码

class Solution {

    List<List<String>> ans = new ArrayList<>();


    public List<List<String>> solveNQueens(int n) {
        // 创建棋盘,初始化为 '.'
        char[][] g = new char[n][n];
        int[] co = new int[n]; // 列标记数组,co[i] 表示第 i 列是否被占用
        int[] zx = new int[n * 2]; // 左斜对角线标记数组,zx[i] 表示是否被占用
        int[] fx = new int[n * 2]; // 右斜对角线标记数组,fx[i] 表示是否被占用
    
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                g[i][j] = '.';
            }
        }
        dfs(0, n, g, co, zx, fx);

        return ans;
    }

    // 深度优先搜索函数
    void dfs(int u, int n, char[][] g, int[] co, int[] zx, int[] fx) {
        // 如果已经放置了 n 个皇后(到达第 n 行),将当前解加入结果列表
        if (u == n) {
            List<String> list = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                // 将当前棋盘的一行转为字符串并加入结果
                list.add(new String(g[i]));
            }
            ans.add(list);
            return;
        }

        // 尝试将皇后放置在第 u 行的每一列
        for (int i = 0; i < n; i++) {
            // 检查当前位置是否可以放置皇后
            if (co[i] == 0 && zx[u - i + n] == 0 && fx[u + i] == 0) {
                // 标记当前位置为占用状态
                co[i] = zx[u - i + n] = fx[u + i] = 1;
                g[u][i] = 'Q'; // 放置皇后

                // 递归处理下一行
                dfs(u + 1, n, g, co, zx, fx);

                // 回溯:将当前位置恢复为未占用状态
                g[u][i] = '.';
                co[i] = zx[u - i + n] = fx[u + i] = 0;
            }
        }
    }
}

全排列

https://leetcode.cn/problems/permutations/

核心思路

模版题,注释就可以看懂。理解dfs的递归后的"恢复原状"。

示例代码

class Solution {
    private List<List<Integer>> ans = new ArrayList<>();
    int[] nums;
    // 用于记录当前路径的列表
    private List<Integer> path = new ArrayList<>();

    public List<List<Integer>> permute(int[] nums) {
        // 标记每个元素是否已经被使用过
        boolean[] flag = new boolean[nums.length];
        this.nums = nums;
        // 深度优先遍历生成全排列
        dfs(0, nums.length, flag);
        return ans;
    }

    // 深度优先遍历方法
    private void dfs(int i, int len, boolean[] flag) {
        // 如果当前路径长度等于数组长度,说明产生了一种全排列
        if (path.size() == len) {
            // 将当前路径的副本加入结果列表
            ans.add(new ArrayList<Integer>(path));
            return;
        }
        
        // 遍历数组中的每个元素
        for (int j = 0; j < nums.length; j++) {
            // 如果当前元素还没有被使用
            if (!flag[j]) {
                // 将当前元素标记为已使用
                flag[j] = true;
                // 将当前元素加入路径列表
                path.add(nums[j]);
                // 递归调用下一层,继续生成下一个元素
                dfs(j + 1, len, flag);
                // 回溯:移除当前路径中的最后一个元素
                path.remove(path.size() - 1);
                // 回溯:将当前元素标记为未使用
                flag[j] = false;
            }
        }
    }
}

子集

https://leetcode.cn/problems/subsets/

核心思路

模版题

回溯:
1.边界条件
2.递归参数
3.恢复原状

示例代码

class Solution {

    private List<List<Integer>> ans = new ArrayList<>(); // 存储所有子集的结果
    private int[] nums; // 输入的数组
    private List<Integer> path = new ArrayList<>(); // 当前子集的路径

    // 主函数:返回所有子集
    public List<List<Integer>> subsets(int[] nums) {
        this.nums = nums; // 初始化 nums 数组
        // 遍历所有可能的子集长度,从 0 到 nums.length
        for(int i = 0; i <= nums.length; i++) {
            dfs(0, i); // 通过 dfs 寻找当前长度的所有子集
        }
        return ans; // 返回所有子集
    }

    // 深度优先搜索函数:找到所有长度为 len 的子集
    private void dfs(int i, int len) {
        // 递归结束条件:如果当前子集路径的长度等于目标长度
        if(path.size() == len) {
            ans.add(new ArrayList<Integer>(path)); // 将当前路径添加到结果中
            return;
        }
        
        // 遍历从 i 开始的所有元素
        for(int j = i; j < nums.length; j++) {
            path.add(nums[j]); // 将当前元素加入子集
            dfs(j + 1, len); // 递归,继续构建子集
            path.remove(path.size() - 1); // 回溯,移除最后加入的元素
        }
    }
}

电话号码的字母组合

https://leetcode.cn/problems/letter-combinations-of-a-phone-number/

核心思路

模版题,经典题

示例代码

class Solution {
    private String[] map = new String[]{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

    private List<String> ans = new ArrayList<>();
    
    // 用于临时存储当前递归路径的字符数组
    private char[] path;
    
    // 用于存储输入的数字字符串
    private char[] digits;
    public List<String> letterCombinations(String digits) {
        int n = digits.length();
        
        if(n == 0) return List.of();
        this.digits = digits.toCharArray();
        path = new char[n];
        
        dfs(0);
        return ans;
    }

    // 深度优先搜索方法,递归生成字母组合
    private void dfs(int i){
        // 如果当前索引等于数字字符串的长度,说明一组组合已生成,加入结果列表
        if(i == digits.length){
            ans.add(new String(path));
            return;
        }
        
        // 获取当前数字对应的字母集合
        for(char ch : map[digits[i]-'0'].toCharArray()){
            // 将当前字符赋值到路径数组.直接覆盖,不用恢复原状
            path[i] = ch;
            
            // 递归调用,继续处理下一个数字
            dfs(i+1);
        }
    }
}

组合总和

https://leetcode.cn/problems/combination-sum/

核心思路

常规dfs题,但是此题最重要的就是剪枝,因为已经排序,当第i个数使sum > target,则数组接下来的数都没必要进行计算。

示例代码

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

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates);
        this.candidates = candidates;
        this.target = target;
        dfs(0, candidates.length, 0);

        return ans;
    }

    private boolean dfs(int i, int len, int sum) {
        if (sum > target)
            return false;
        if (sum == target) {
            ans.add(new ArrayList<Integer>(path));
            return true;
        }
        for (int t = i; t < len; t++)
        {
            path.add(candidates[t]);
            if(!dfs(t, len, sum +candidates[t])){
                path.remove(path.size() - 1);
                break;
            }
            path.remove(path.size() - 1);
            
        }
        return true;
    }
}

你可能感兴趣的:(算法,leetcode,面试)