Retour en arrière de la "Série d'algorithmes"

Introduction

  L'algorithme de backtracking est un algorithme de recherche en profondeur d'abord , l'algorithme de backtracking a donc les caractéristiques d'une recherche approfondie. Par exemple : 1. C'est un algorithme récursif . Deuxièmement, c'est un algorithme violent . 3. L'essentiel est d'énumérer toutes les possibilités, puis de trouver la réponse souhaitée . En fait, si vous rencontrez une question d’algorithme de retour en arrière lors de l’entretien, vous devriez vous réveiller avec le sourire. Parce que c'est une sorte de question avec un "modèle", qui est unique . Au lieu de questions comme la planification dynamique, il n’y a pas de routine fixe, et cela dépend du destin si vous pouvez le faire ou non. Les questions rétrospectives sont généralement de difficulté moyenne, ce qui est très approprié pour les entretiens et les examens . Vous pouvez les comprendre parfaitement en un jour ou deux. On peut dire que le rapport coût-efficacité est très élevé. Il suffit de mémoriser les modèle! Parfois, une question peut être résolue par la méthode dynamique ou par la méthode du retour en arrière. Pour le moment, nous devrions d'abord envisager d'utiliser la méthode dynamique, car l'efficacité du retour en arrière est encore trop faible .

base théorique

  L'algorithme de retour en arrière consiste à effectuer un parcours en profondeur d'abord sur la structure arborescente ou graphique. En fait, il est similaire au processus de tentative de recherche d'énumération et trouve la solution au problème pendant le processus de parcours. Le parcours en profondeur d'abord a une fonctionnalité : lorsqu'il s'avère que la condition de solution n'est pas remplie, il revient et essaie un autre chemin. À ce stade, la variable de type d'objet doit être réinitialisée pour être la même qu'auparavant, ce qui est appelé state reset . De nombreux problèmes complexes et à grande échelle peuvent utiliser la méthode de retour en arrière, connue sous le nom de méthode générale de résolution de problèmes . En fait, l'algorithme de backtracking est un algorithme de recherche violent . C'est un algorithme utilisé au début de l'intelligence artificielle. Il nous aide à trouver des solutions aux problèmes à l'aide de la puissante puissance de calcul des ordinateurs. En général, le retour en arrière peut être utilisé pour résoudre des problèmes de combinaison , des problèmes de découpage , des problèmes de sous-ensembles , des problèmes de permutation et des problèmes d'échiquier . Les caractéristiques de ces problèmes sont les suivantes : ils peuvent tous être résumés dans une structure arborescente.

modèle de retour en arrière

  Vient ensuite la partie la plus importante du modèle : le modèle de backtracking est en fait une évolution de la récursion de Deep Search. Notez les points suivants :

  • Dans l'algorithme de backtracking, la valeur de retour de la fonction est généralement nulle, mais un jeu de résultats est généralement requis pour stocker le résultat.
  • Les paramètres de l'algorithme de backtracking ne sont généralement pas fixes et vous pouvez transmettre tout ce dont vous avez besoin.
  • Puisqu'il s'agit d'un algorithme récursif, vous devez faire attention aux conditions de terminaison, qui sont généralement requises par le sujet.
  • L'un des cœurs de l'algorithme de backtracking est : la réinitialisation de l'état , c'est-à-dire le retour à l'état avant l'opération.

Implémentation du pseudocode

void backtracking(路径,选择列表,结果集...) {
    if (终止条件) {
        存放结果操作;
        return;
    }

    for (i = start; i <= n && (剪枝操作); i++) { // 剪枝操作不强制要求有
        处理当前节点;
        backtracking(路径,选择列表,结果集...); // 递归
        状态重置,撤销处理结果
    }
}

Expérience en résolution de problèmes

  • Pour résoudre les questions de retour en arrière, rappelez-vous que le modèle de retour en arrière est le premier mystère.
  • L'algorithme de retour en arrière est un algorithme de traversée en profondeur d'abord, un algorithme de recherche violente et un algorithme récursif.
  • Étant donné que la question du retour en arrière est une recherche en profondeur d'abord, les méthodes d'optimisation courantes sont en fait l'élagage et la recherche en mémoire.
  • Étant donné que l'algorithme de backtracking est une force brute, il n'est pas très efficace, alors ne le choisissez pas sauf si vous y êtes obligé.
  • Lorsqu'on recherche simplement le nombre de résultats, on utilise généralement DP, mais lorsque le processus est requis, il faut recourir au retour en arrière. C'est aussi un type de question, alors soyez attentif.
  • En général, le retour en arrière peut être envisagé pour les problèmes de combinaison, les problèmes de découpage, les problèmes de sous-ensembles, les problèmes de permutation et les problèmes d’échiquier.
  • Les problèmes qui peuvent être résolus par la méthode de backtracking doivent être résumés dans une structure arborescente.
  • Gardez à l’esprit les précautions indiquées dans le modèle de retour en arrière de la section précédente.

sujet d'algorithme

17. Combinaisons alphabétiques pour les numéros de téléphone

insérer la description de l'image ici
Analyse du problème : utilisez le modèle de backtracking pour le résoudre.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
class Solution {
    public List<String> letterCombinations(String digits) {
        // 结果集
        List<String> res = new ArrayList<>();
        // 若为空,直接返回
        if (digits == null || digits.length() == 0) {
            return res;
        }
        // 拼接字符串用StringBuilder
        StringBuilder temp = new StringBuilder();
        String[] numString = new String[]{"0", "1", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
        // 迭代递归
        backTracking(digits, temp, numString, 0, res);
        return res;
    }

    public void backTracking(String digits, StringBuilder temp, String[] numString, int num, List<String> res) {
        // 递归出口
        if (num == digits.length()) {
            res.add(temp.toString());
            return;
        }
        String str = numString[digits.charAt(num) - '0'];
        for (int i = 0; i < str.length(); i++) {
            temp.append(str.charAt(i));
            backTracking(digits, temp, numString, num + 1, res);
            // 去掉最后一位,继续尝试
            temp.deleteCharAt(temp.length() - 1);
        }
    }
}

22. Génération de parenthèses

insérer la description de l'image ici
Analyse du problème : utilisez le modèle de backtracking pour le résoudre.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> res = new ArrayList<>();
        generate(res, 0, 0, "", n);
        return res;
    }

    public void generate(List<String> res, int l, int r, String tmp, int n) {
        // 当字符串长度为 n*2 时,到达递归出口
        if (tmp.length() == n * 2) {
            res.add(tmp);
            return;
        }

        // 左括号数不能大于总括号数
        if (l < n) {
            generate(res, l + 1, r, tmp + "(", n);
        }

        // 右括号不能大于左括号数
        if (r < l) {
            generate(res, l, r + 1, tmp + ")", n);
        }
    }
}

37. Résoudre le Sudoku

insérer la description de l'image ici

insérer la description de l'image ici
Analyse du problème : il peut être résolu en revenant sur le modèle et en effectuant une opération sur les bits.
le code s'affiche comme ci-dessous :

/**
 * 回溯 + 位运算
 */
class Solution {
    // 储存每一行存在的数字 
    private int[] line = new int[9];
    // 储存 每一列存在的数字
    private int[] column = new int[9];
    // 储存每一个 3*3 宫存在的数字 
    private int[][] block = new int[3][3];
    // 这个布尔数组用来判断是否填完所有空格
    private boolean valid = false;
    // 这个list用来记录所有空格的位置,整数数组第一个位置为行的位置 ,第二个位置为列的位置
    private List<int[]> spaces = new ArrayList<int[]>();

    public void solveSudoku(char[][] board) {
        // 遍历所有位置
        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                // 如果位置为空,我们把该位置加入spaces中
                if (board[i][j] == '.') {
                    spaces.add(new int[]{i, j});
                } else {
                    // 不为空则把该数字分别纳入对应的行,列,3*3宫中
                    int digit = board[i][j] - '0' - 1;
                    flip(i, j, digit);
                }
            }
        }
        // 开始搜索
        dfs(board, 0);
    }

    public void dfs(char[][] board, int pos) {
        // 如果spaces被遍历完了,说明我们填完了空格,将valid改为true,通过return结束这个函数
        if (pos == spaces.size()) {
            valid = true;
            return;
        }
        // 获取第一个空格的位置 
        int[] space = spaces.get(pos);
        // i,j分别为该空格的行数和列数 
        int i = space[0], j = space[1];
        // |为or,通过3个或运算我们可以得到一个9位的二进制数字,从右到左分别代表1-9这个9个数是否可以填入该空格,通过~运算(取反),我们用1表示该数字是一个可填入的选项,0x1ff为十六进制 ,等同于111111111)
        int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
        // 当mask不为0并且 valid为false表示还有数待填入,继续这个循环,mask &= (mask - 1)把最低位的1变为0
        for (; mask != 0 && !valid; mask &= (mask - 1)) {
            // 这个操作只保留最低位的1
            int digitMask = mask & (-mask);
            // 最低位的1后面的位数,digitMask-1将原本1后面的0全部变为了1
            int digit = Integer.bitCount(digitMask - 1);
            // 更新行,列,宫
            flip(i, j, digit);
            // 把该数填入板中
            board[i][j] = (char) (digit + '0' + 1);
            // 继续搜索 
            dfs(board, pos + 1);
            // 撤回操作(原理是flip中的~运算,两个 1则为0,0表示这个位置代表的1-9中的那个数 不再是一个可被填入的选项)
            flip(i, j, digit);
        }
    }

    public void flip(int i, int j, int digit) {
        // ^代表异或,只有1个1的时候才为1。比如0011^1001=1010
        // <<代表左移,比如 1<<2=4被加入到下面的三个数组中,在二进制4为100,意味着3这个数字被占用了
        line[i] ^= (1 << digit);
        column[j] ^= (1 << digit);
        block[i / 3][j / 3] ^= (1 << digit);
    }
}

39. Somme combinée

insérer la description de l'image ici
Analyse du problème : utilisez le modèle de backtracking pour le résoudre.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates);
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> temp = new ArrayList<>();
        helper(0,res,temp,candidates,0,target);
        return res;
    }

    private void helper(int curr,List<List<Integer>> res,List<Integer> temp,int[] candidates,int n,int target){
        
        // 1
        if(curr == target){
            res.add(new ArrayList<>(temp));
            return;
        }

        // 2
        for(int i = n;i<candidates.length;i++){
            if(curr + candidates[i] <= target){
                // 2.1
                temp.add(candidates[i]);
                // 2.2
                helper(curr + candidates[i],res,temp,candidates,n,target);
                // 2.3
                temp.remove(temp.size()-1);
            }else{
                break;
            }
            n++;         
        }
    }
}

40. Portefeuille Somme II

insérer la description de l'image ici
Analyse du problème : utilisez le modèle de backtracking pour le résoudre.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
class Solution {
    List<List<Integer>> list=new ArrayList<>();
    List<Integer> path=new ArrayList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        dfs(candidates,target,0);
        return list;
    }
    private void dfs(int[] candidates, int target,int index){
        if(target==0){
            list.add(new ArrayList<>(path));
            return;
        }
        for(int i=index;i<candidates.length;i++){
            if(candidates[i]<=target){
                if(i>index&&candidates[i]==candidates[i-1]){
                    continue;
                }
                path.add(candidates[i]);
                dfs(candidates,target-candidates[i],i+1);
                path.remove(path.size()-1);
            }
        }
    }
}

46. ​​​​​​Arrangement complet

insérer la description de l'image ici
Analyse du problème : utilisez le modèle de backtracking pour le résoudre.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> list = new ArrayList<>();
        backtrack(res, list, nums);
        return res;        
    }
    
    public void backtrack(List<List<Integer>> res, List<Integer> list, int[] nums) {
        // 1
        if(list.size() == nums.length) {
            res.add(new ArrayList<Integer>(list));
            return;
        }
        // 2
        for(int num : nums) {
            if(!list.contains(num)) {
                // 2.1
                list.add(num);
                // 2.2
                backtrack(res, list, nums);
                // 2.3
                list.remove(list.size() - 1);
            }
        }
    }
}

47. Arrangement complet II

insérer la description de l'image ici
Analyse du sujet : utilisez le modèle de retour en arrière pour résoudre le problème, l'accent est mis sur la déduplication et ceux qui sont passés par la même couche ne partiront pas directement, ignorez-la.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
class Solution {
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> path = new ArrayList<>();

    public List<List<Integer>> permuteUnique(int[] nums) {
        boolean[] used = new boolean[nums.length];
        Arrays.fill(used, false);
        Arrays.sort(nums);
        backTrack(nums, used);
        return result;
    }

    private void backTrack(int[] nums, boolean[] used) {
        // 1
        if (path.size() == nums.length) {
            result.add(new ArrayList<>(path));
            return;
        }
        // 2
        for (int i = 0; i < nums.length; i++) {
            // 与前数相等且used[i - 1] == true,说明同⼀树枝nums[i - 1]使⽤过
            // 与前数相等且used[i - 1] == false,说明同⼀树层nums[i - 1]使⽤过
            // 如果同⼀树层nums[i - 1]使⽤过则直接跳过,所有可能,因为之前跑过一次了
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                continue;
            }
            // 如果同⼀树⽀nums[i]没使⽤过开始处理
            if (used[i] == false) {
                used[i] = true;// 标记同⼀树⽀nums[i]使⽤过,防止同一树支重复使用
                // 2.1
                path.add(nums[i]);
                // 2.2
                backTrack(nums, used);
                // 2.3
                path.remove(path.size() - 1);// 回溯,说明同⼀树层nums[i]使⽤过,防止下一树层重复
                used[i] = false;
            }
        }
    }
}

51. Reine N

insérer la description de l'image ici
Analyse du sujet : utilisez le retour en arrière basé sur un ensemble pour résoudre les problèmes.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
class Solution {
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> solutions = new ArrayList<List<String>>();
        int[] queens = new int[n];
        Arrays.fill(queens, -1);
        Set<Integer> columns = new HashSet<Integer>();
        Set<Integer> diagonals1 = new HashSet<Integer>();
        Set<Integer> diagonals2 = new HashSet<Integer>();
        backtrack(solutions, queens, n, 0, columns, diagonals1, diagonals2);
        return solutions;
    }

    public void backtrack(List<List<String>> solutions, int[] queens, int n, int row, Set<Integer> columns, Set<Integer> diagonals1, Set<Integer> diagonals2) {
        if (row == n) {
            List<String> board = generateBoard(queens, n);
            solutions.add(board);
        } else {
            for (int i = 0; i < n; i++) {
                if (columns.contains(i)) {
                    continue;
                }
                int diagonal1 = row - i;
                if (diagonals1.contains(diagonal1)) {
                    continue;
                }
                int diagonal2 = row + i;
                if (diagonals2.contains(diagonal2)) {
                    continue;
                }
                queens[row] = i;
                columns.add(i);
                diagonals1.add(diagonal1);
                diagonals2.add(diagonal2);
                backtrack(solutions, queens, n, row + 1, columns, diagonals1, diagonals2);
                queens[row] = -1;
                columns.remove(i);
                diagonals1.remove(diagonal1);
                diagonals2.remove(diagonal2);
            }
        }
    }

    public List<String> generateBoard(int[] queens, int n) {
        List<String> board = new ArrayList<String>();
        for (int i = 0; i < n; i++) {
            char[] row = new char[n];
            Arrays.fill(row, '.');
            row[queens[i]] = 'Q';
            board.add(new String(row));
        }
        return board;
    }
}

52. Impératrice N II

insérer la description de l'image ici
Analyse du sujet : utilisez le retour en arrière basé sur un ensemble pour résoudre les problèmes.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
class Solution {
    public int totalNQueens(int n) {
        Set<Integer> columns = new HashSet<Integer>();
        Set<Integer> diagonals1 = new HashSet<Integer>();
        Set<Integer> diagonals2 = new HashSet<Integer>();
        return backtrack(n, 0, columns, diagonals1, diagonals2);
    }

    public int backtrack(int n, int row, Set<Integer> columns, Set<Integer> diagonals1, Set<Integer> diagonals2) {
        if (row == n) {
            return 1;
        } else {
            int count = 0;
            for (int i = 0; i < n; i++) {
                if (columns.contains(i)) {
                    continue;
                }
                int diagonal1 = row - i;
                if (diagonals1.contains(diagonal1)) {
                    continue;
                }
                int diagonal2 = row + i;
                if (diagonals2.contains(diagonal2)) {
                    continue;
                }
                columns.add(i);
                diagonals1.add(diagonal1);
                diagonals2.add(diagonal2);
                count += backtrack(n, row + 1, columns, diagonals1, diagonals2);
                columns.remove(i);
                diagonals1.remove(diagonal1);
                diagonals2.remove(diagonal2);
            }
            return count;
        }
    }
}

77. Combinaison

insérer la description de l'image ici
Analyse du problème : utilisez le modèle de backtracking pour le résoudre.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> combine(int n, int k) {
        combineHelper(n, k, 1);
        return result;
    }

    private void combineHelper(int n, int k, int startIndex){
        //终止条件
        // 1
        if (path.size() == k){
            result.add(new ArrayList<>(path));
            return;
        }
        //2
        for (int i = startIndex; i <= n - (k - path.size()) + 1; i++){
            // 2.1
            path.add(i);
            // 2.2
            combineHelper(n, k, i + 1);
            // 2.3
            path.removeLast();
        }
    }
}

78. Sous-ensemble

insérer la description de l'image ici
Analyse du problème : utilisez le modèle de backtracking pour le résoudre.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
class Solution {
    List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
    LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
    public List<List<Integer>> subsets(int[] nums) {
        if (nums.length == 0){
            result.add(new ArrayList<>());
            return result;
        }
        helper(nums, 0);
        return result;
    }

    private void helper(int[] nums, int startIndex){
        result.add(new ArrayList<>(path));// 遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合
        if (startIndex >= nums.length){ // 终止条件可不加
            return;
        }
        for (int i = startIndex; i < nums.length; i++){
            path.add(nums[i]);
            helper(nums, i + 1);
            path.removeLast();
        }
    }
}

79. Recherche de mots

insérer la description de l'image ici
Analyse du problème : utilisez le modèle de backtracking pour le résoudre.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
class Solution {

    public boolean exist(char[][] board, String word) {
        if (board == null) {
            return false;
        }

        // 该字母是否已遍历到
        boolean[][] used = new boolean[board.length][board[0].length];

        // 遍历所有坐标点,以作为起始点
        for (int row = 0; row < board.length; row++) {
            for (int column = 0; column < board[0].length; column++) {
                // 找到则立即返回
                if (helper(board, used, word, row, column, 0)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * @param board  字母二维数组
     * @param used   该字母是否使用表
     * @param word   查找单词
     * @param row    当前字母所在行
     * @param column 当前字母所在列
     * @param index  当前字母所在索引
     * @return
     */
    public boolean helper(char[][] board, boolean[][] used, String word, int row, int column, int index) {

        // 超过边界、已使用过和与word不匹配,立即返回,不再进行其它操作
        if (row < 0 || row >= board.length || column < 0 || column >= board[0].length
                || used[row][column] || board[row][column] != word.charAt(index)) {
            return false;
        }

        // 1
        // 查到最后一个字母,即查到该单词
        if (index == word.length() - 1) {
            return true;
        }

        // 2
        // 2.1 添加状态
        used[row][column] = true;
        // 2.2 执行
        boolean res = helper(board, used, word, row + 1, column, index + 1)
                || helper(board, used, word, row - 1, column, index + 1)
                || helper(board, used, word, row, column + 1, index + 1)
                || helper(board, used, word, row, column - 1, index + 1);
        // 2.3 撤销状态
        used[row][column] = false;
        return res;
    }
}

90. Sous-ensemble II

insérer la description de l'image ici
Analyse du sujet : résolvez-le avec un modèle de retour en arrière et utilisez un tableau booléen pour la déduplication.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
class Solution {
    List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
   LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
   boolean[] used;
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        if (nums.length == 0){
            result.add(path);
            return result;
        }
        Arrays.sort(nums);
        used = new boolean[nums.length];
        subsetsWithDupHelper(nums, 0);
        return result;
    }
    
    private void subsetsWithDupHelper(int[] nums, int startIndex){
        result.add(new ArrayList<>(path));
        // 1
        if (startIndex >= nums.length){
            return;
        }
        // 2
        for (int i = startIndex; i < nums.length; i++){
            if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]){
                continue;
            }
            // 2.1           
            path.add(nums[i]);
            used[i] = true;
            // 2.2
            subsetsWithDupHelper(nums, i + 1);
            // 2.3
            path.removeLast();
            used[i] = false;
        }
    }
}

93. Restauration des adresses IP

insérer la description de l'image ici
Analyse du sujet : résolvez-le avec un modèle de retour en arrière. La difficulté ici est que vous devez résumer la question dans une structure arborescente, puis utiliser la méthode de retour en arrière pour la résoudre, ce qui est difficile à imaginer.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
 class Solution {
    List<String> result = new ArrayList<>();

    public List<String> restoreIpAddresses(String s) {
        if (s.length() > 12) return result; // 算是剪枝了
        backTrack(s, 0, 0);
        return result;
    }

    // startIndex: 搜索的起始位置, pointNum:添加逗点的数量
    private void backTrack(String s, int startIndex, int pointNum) {
        if (pointNum == 3) {// 逗点数量为3时,分隔结束
            // 判断第四段⼦字符串是否合法,如果合法就放进result中
            if (isValid(s,startIndex,s.length()-1)) {
                result.add(s);
            }
            return;
        }
        for (int i = startIndex; i < s.length(); i++) {
            if (isValid(s, startIndex, i)) {
                s = s.substring(0, i + 1) + "." + s.substring(i + 1);    // 在str的后⾯插⼊⼀个逗点
                pointNum++;
                backTrack(s, i + 2, pointNum);// 插⼊逗点之后下⼀个⼦串的起始位置为i+2
                pointNum--;// 回溯
                s = s.substring(0, i + 1) + s.substring(i + 2);// 回溯删掉逗点
            } else {
                break;
            }
        }
    }

    // 判断字符串s在左闭⼜闭区间[start, end]所组成的数字是否合法
    private Boolean isValid(String s, int start, int end) {
        if (start > end) {
            return false;
        }
        if (s.charAt(start) == '0' && start != end) { // 0开头的数字不合法
            return false;
        }
        int num = 0;
        for (int i = start; i <= end; i++) {
            if (s.charAt(i) > '9' || s.charAt(i) < '0') { // 遇到⾮数字字符不合法
                return false;
            }
            num = num * 10 + (s.charAt(i) - '0');
            if (num > 255) { // 如果⼤于255了不合法
                return false;
            }
        }
        return true;
    }
}

95. Différents arbres de recherche binaires II

insérer la description de l'image ici
Analyse du sujet : Le simple fait de demander le numéro peut utiliser DP, mais pour le processus, vous devez utiliser le backtracking. C'est aussi un type de question, alors faites attention.
le code s'affiche comme ci-dessous :

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
 /**
  * 回溯法
  */
class Solution {
    public List<TreeNode> generateTrees(int n) {
        return helper(1,n);
    }
    public List<TreeNode> helper(int lo, int hi){
        List<TreeNode> res=new LinkedList<>();
        // 1
        if(lo>hi){
            res.add(null);
            return res;
        }
        // 2
        for(int i = lo; i <= hi; i++){
            List<TreeNode> left = helper(lo, i-1);
            List<TreeNode> right = helper(i+1, hi);
            for(TreeNode le : left){
                for(TreeNode ri : right){
                    TreeNode root=new TreeNode(i);
                    root.left=le;
                    root.right=ri;
                    res.add(root);
                }
            }
        }
        return res;
    }
}

126. Mot Solitaire II

insérer la description de l'image ici
Analyse du problème : utilisez le modèle de backtracking pour le résoudre.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
class Solution {

    public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
        List<List<String>> res = new ArrayList<>();
        Set<String> dict = new HashSet<>(wordList);
        if (!dict.contains(endWord)) {
            return res;
        }
        dict.remove(beginWord);

        Map<String, Integer> steps = new HashMap<>();
        steps.put(beginWord, 0);
        Map<String, Set<String>> from = new HashMap<>();
        boolean found = bfs(beginWord, endWord, dict, steps, from);

        if (found) {
            Deque<String> path = new ArrayDeque<>();
            path.add(endWord);
            dfs(from, path, beginWord, endWord, res);
        }
        return res;
    }


    private boolean bfs(String beginWord, String endWord, Set<String> dict, Map<String, Integer> steps, Map<String, Set<String>> from) {
        int wordLen = beginWord.length();
        int step = 0;
        boolean found = false;

        Queue<String> queue = new LinkedList<>();
        queue.offer(beginWord);
        while (!queue.isEmpty()) {
            step++;
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                String currWord = queue.poll();
                char[] charArray = currWord.toCharArray();
                for (int j = 0; j < wordLen; j++) {
                    char origin = charArray[j];
                    for (char c = 'a'; c <= 'z'; c++) {
                        charArray[j] = c;
                        String nextWord = String.valueOf(charArray);
                        if (steps.containsKey(nextWord) && steps.get(nextWord) == step) {
                            from.get(nextWord).add(currWord);
                        }

                        if (!dict.contains(nextWord)) {
                            continue;
                        }
                        dict.remove(nextWord);
                        queue.offer(nextWord);
                        from.putIfAbsent(nextWord, new HashSet<>());
                        from.get(nextWord).add(currWord);
                        steps.put(nextWord, step);
                        if (nextWord.equals(endWord)) {
                            found = true;
                        }
                    }
                    charArray[j] = origin;
                }
            }
            if (found) {
                break;
            }
        }
        return found;
    }

    private void dfs(Map<String, Set<String>> from, Deque<String> path, String beginWord, String cur, List<List<String>> res) {
        if (cur.equals(beginWord)) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (String precursor : from.get(cur)) {
            path.addFirst(precursor);
            dfs(from, path, beginWord, precursor, res);
            path.removeFirst();
        }
    }
}

131. Palindrome divisé

insérer la description de l'image ici
Analyse du sujet : résolvez le problème avec le modèle de retour en arrière, et le problème de coupe peut être résumé en un problème de combinaison, qui est également difficile à imaginer.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
 class Solution {
    List<List<String>> lists = new ArrayList<>();
    Deque<String> deque = new LinkedList<>();

    public List<List<String>> partition(String s) {
        backTracking(s, 0);
        return lists;
    }

    private void backTracking(String s, int startIndex) {
        // 如果起始位置大于s的大小,说明找到了一组分割方案
        if (startIndex >= s.length()) {
            lists.add(new ArrayList(deque));
            return;
        }
        for (int i = startIndex; i < s.length(); i++) {
            // 如果是回文子串,则记录
            if (isPalindrome(s, startIndex, i)) {
                String str = s.substring(startIndex, i + 1);
                deque.addLast(str);
            } else {
                continue;
            }
            // 起始位置后移,保证不重复
            backTracking(s, i + 1);
            deque.removeLast();
        }
    }
    // 判断是否是回文串
    private boolean isPalindrome(String s, int startIndex, int end) {
        for (int i = startIndex, j = end; i < j; i++, j--) {
            if (s.charAt(i) != s.charAt(j)) {
                return false;
            }
        }
        return true;
    }
}

216. Somme combinée III

insérer la description de l'image ici
Analyse du problème : utilisez le modèle de backtracking pour le résoudre.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
class Solution {
	List<List<Integer>> result = new ArrayList<>();
	LinkedList<Integer> path = new LinkedList<>();

	public List<List<Integer>> combinationSum3(int k, int n) {
		helper(n, k, 1, 0);
		return result;
	}

	private void helper(int targetSum, int k, int startIndex, int sum) {
		// 减枝
		if (sum > targetSum) {
			return;
		}

        // 1
		if (path.size() == k) {
			if (sum == targetSum) result.add(new ArrayList<>(path));
			return;
		}
		
		// 减枝 9 - (k - path.size()) + 1
        //2
		for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {
            //2.1
			path.add(i);
			sum += i;
			helper(targetSum, k, i + 1, sum);
            // 2.2
			//回溯
			path.removeLast();
			//回溯
			sum -= i;
		}
	}
}

282. Ajouter des opérateurs aux expressions

insérer la description de l'image ici
Analyse du problème : utilisez le modèle de backtracking pour le résoudre.
le code s'affiche comme ci-dessous :

/**
 * 回溯法
 */
class Solution {
    int n;
    String num;
    int target;
    List<String> ans;

    public List<String> addOperators(String num, int target) {
        this.n = num.length();
        this.num = num;
        this.target = target;
        this.ans = new ArrayList<String>();
        StringBuffer expr = new StringBuffer();
        backtrack(expr, 0, 0, 0);
        return ans;
    }

    public void backtrack(StringBuffer expr, int i, long res, long mul) {
        if (i == n) {
            if (res == target) {
                ans.add(expr.toString());
            }
            return;
        }
        int signIndex = expr.length();
        if (i > 0) {
            expr.append(0); // 占位,下面填充符号
        }
        long val = 0;
        // 枚举截取的数字长度(取多少位),注意数字可以是单个 0 但不能有前导零
        for (int j = i; j < n && (j == i || num.charAt(i) != '0'); ++j) {
            val = val * 10 + num.charAt(j) - '0';
            expr.append(num.charAt(j));
            if (i == 0) { // 表达式开头不能添加符号
                backtrack(expr, j + 1, val, val);
            } else { // 枚举符号
                expr.setCharAt(signIndex, '+');
                backtrack(expr, j + 1, res + val, val);
                expr.setCharAt(signIndex, '-');
                backtrack(expr, j + 1, res - val, -val);
                expr.setCharAt(signIndex, '*');
                backtrack(expr, j + 1, res - mul + mul * val, mul * val);
            }
        }
        expr.setLength(signIndex);
    }
}

Retour à la page d'accueil

Quelques sentiments sur le brossage des questions Leetcode 500+

Suivant

Gourmand dans "Série d'algorithmes"

Je suppose que tu aimes

Origine blog.csdn.net/qq_22136439/article/details/126683802
conseillé
Classement