搜索---回溯

Backtracking(回溯)属于 DFS。

普通 DFS 主要用在 可达性问题 ,这种问题只需要执行到特点的位置然后返回即可。
而 Backtracking 主要用于求解 排列组合 问题,例如有 { ‘a’,‘b’,‘c’ } 三个字符,求解所有由这三个字符排列得到的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程。
因为 Backtracking 不是立即返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题:

在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;
但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。

1. 数字键盘组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
在这里插入图片描述

public Map<Character, String> getletterMap() {
        Map<Character, String> map = new HashMap<>();
        map.put('2', "abc");
        map.put('3', "def");
        map.put('4', "ghi");
        map.put('5', "jkl");
        map.put('6', "mno");
        map.put('7', "pqrs");
        map.put('8', "tuv");
        map.put('9', "wxyz");
        return map;
    }

    public List<String> letterCombinations(String digits) {
        List<String> allCombinations = new LinkedList<>();
        if (digits == null || digits.length() == 0) return allCombinations;
        Map<Character, String> letterMap = getletterMap();
        StringBuilder finishStr = new StringBuilder();
        joint(digits, finishStr, letterMap, allCombinations);
        return allCombinations;
    }

    private void joint(String digits, StringBuilder finishStr, Map<Character, String> letterMap, List<String> allCombinations) {
        if (finishStr.length() == digits.length()) {
            allCombinations.add(finishStr.toString());
            return;
        }
        char curNum = digits.charAt(finishStr.length());
        String curLetters = letterMap.get(curNum);
        for (char letter : curLetters.toCharArray()) {
            finishStr.append(letter);
            joint(digits, finishStr, letterMap, allCombinations);
            finishStr.deleteCharAt(finishStr.length() - 1);
        }
    }

2. IP 地址划分

    /*
    * 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
    * 解析:ip地址用三个.分为四段。每一段数字范围是0~255.并且每段不会出现以0开头的,如01,001等
    * */
    public List<String> restoreIpAddresses(String s) {
        List<String> iplist = new LinkedList<>();
        StringBuilder frontS = new StringBuilder();
        restoreIp(0, s, frontS, iplist);
        return iplist;
    }

    public void restoreIp(int k, String s, StringBuilder frontS, List<String> iplist) {
        if (k == 4 || s.length() == 0) {
            if (k == 4 && s.length() == 0) {
                iplist.add(frontS.toString());
            }
            return;
        }
        for (int i = 0; i < s.length() && i <= 2; i++) {
            if (s.charAt(0) == '0' && i != 0) break;//注意这个位置
            String curS = s.substring(0, i + 1);
            int curNum = Integer.valueOf(curS);
            if (curNum >= 0 && curNum <= 255) {
                if (frontS.length() != 0) {
                    curS = "." + curS;
                }
                frontS.append(curS);
                restoreIp(k + 1, s.substring(i + 1), frontS, iplist);
                frontS = frontS.delete(frontS.length() - curS.length(), frontS.length());
            }
        }
        return;
    }

3. 在矩阵中寻找字符串

/*
    * 在矩阵中寻找字符串
    * */
    public boolean exist(char[][] board, String word) {
        if (word == null || word.length() == 0) return true;
        if (board == null || board.length == 0 || board[0].length == 0) return false;
        int m = board.length, n = board[0].length;
        int book[][] = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (findWord(word, 0, i, j, board, book)) return true;
            }
        }
        return false;
    }

    private int direction[][] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};

    private boolean findWord(String word, int curIndex, int i, int j, char[][] board, int[][] book) {
        if (curIndex == word.length()) return true;
        if (i < 0 || j < 0 || i >= board.length || j >= board[0].length || book[i][j] == 1) return false;
        if (word.charAt(curIndex) != board[i][j]) return false;
        book[i][j] = 1;
        for (int k = 0; k < direction.length; k++) {
            int x = i + direction[k][0];
            int y = j + direction[k][1];
            if (findWord(word, curIndex + 1, x, y, board, book)) return true;
        }
        book[i][j] = 0;
        return false;
    }

4. 输出二叉树中所有从根到叶子的路径

 /*
    * 输出二叉树中所有从根到叶子的路径
    * */
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> paths = new LinkedList<>();
        List<Integer> list = new LinkedList<>();
        findAllPath(root, list, paths);
        return paths;
    }

    private void findAllPath(TreeNode root, List<Integer> list, List<String> paths) {
        if (root == null) {
            return;
        }
        list.add(root.val);
        if (root.right == null && root.left == null) {
            paths.add(buildPath(list));
        } else {
            findAllPath(root.left, list, paths);
            findAllPath(root.right, list, paths);
        }
        list.remove(list.size() - 1);
    }

    private String buildPath(List<Integer> list) {
        StringBuilder str = new StringBuilder();
        for (int i = 0; i < list.size(); i++) {
            str.append(list.get(i));
            if (i != list.size() - 1) str.append("->");
        }
        return str.toString();
    }

5. 排列

/*
    * 全排列
    * 给定一个没有重复数字的序列,返回其所有可能的全排列。
    * */
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> alllist = new LinkedList<>();
        int n = nums.length;
        if (n == 0) return alllist;
        boolean visited[] = new boolean[n];
        List<Integer> curSeq = new LinkedList<>();
        combin(nums, visited, curSeq, alllist);
        return alllist;
    }

    private void combin(int[] nums, boolean[] visited, List<Integer> curSeq, List<List<Integer>> alllist) {
        if (nums.length == curSeq.size()) {
            alllist.add(new LinkedList<Integer>(curSeq));//!!!!!!重新构造一个 List
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (!visited[i]) {
                visited[i] = true;
                curSeq.add(nums[i]);
                combin(nums, visited, curSeq, alllist);
                curSeq.remove(curSeq.size() - 1);
                visited[i] = false;
            }
        }
    }

6. 含有相同元素求排列

 /*
    *含有相同元素求排列
    * 给定一个可包含重复数字的序列,返回所有不重复的全排列。
    * */
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> alllist = new LinkedList<>();
        int n = nums.length;
        if (n == 0) return alllist;
        boolean visited[] = new boolean[n];
        List<Integer> curSeq = new LinkedList<>();
        Arrays.sort(nums);
        combin2(nums, visited, curSeq, alllist);
        return alllist;
    }

    private void combin2(int[] nums, boolean[] visited, List<Integer> curSeq, List<List<Integer>> alllist) {
        if (nums.length == curSeq.size()) {
            alllist.add(new LinkedList<>(curSeq));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (visited[i]) continue;
            if (i - 1 >= 0 && nums[i] == nums[i - 1] && visited[i - 1]) continue;//去重的三个条件很重要!!!
            visited[i] = true;
            curSeq.add(nums[i]);
            combin2(nums, visited, curSeq, alllist);
            curSeq.remove(curSeq.size() - 1);
            visited[i] = false;
        }

    }

7. 组合

/*
    * 组合
    * 给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
    * */
    public List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> allCombines = new LinkedList<>();
        List<Integer> list = new LinkedList<>();
        if (n < k || k <= 0) return allCombines;
        search(0, n, k, list, allCombines);
        return allCombines;
    }

    private void search(int cur, int n, int k, List<Integer> list, List<List<Integer>> allCombines) {
        if (k == list.size()) {
            allCombines.add(new LinkedList<>(list));
            return;
        }
        for (int i = cur + 1; i <= n; i++) {
            list.add(i);
            search(i, n, k, list, allCombines);
            list.remove(list.size() - 1);
        }
    }

8. 组合求和

/*
    * 组合求和
    * 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
    * candidates 中的数字可以无限制重复被选取。
    * */
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> allCombines = new LinkedList<>();
        List<Integer> list = new LinkedList<>();
        if (target <= 0 || candidates == null || candidates.length == 0) return allCombines;
        searchSum(list, allCombines, 0, 0, target, candidates);
        return allCombines;
    }

    private void searchSum(List<Integer> list, List<List<Integer>> allCombines, int curIndex, int curSum, int target, int candidates[]) {
        if (curSum == target) {
            allCombines.add(new LinkedList<>(list));
            return;
        }
        if (curSum > target) {
            return;
        }
        int tmpSum = curSum;
        for (int i = curIndex; i < candidates.length; i++) {
            list.add(candidates[i]);
            curSum = tmpSum + candidates[i];
            searchSum(list, allCombines, i, curSum, target, candidates);
            list.remove(list.size() - 1);
        }
    }

9. 含有相同元素的组合求和

/*
    * 含有相同元素的组合求和
    * 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
    * candidates 中的每个数字在每个组合中只能使用一次。
    * */
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> allCombines = new LinkedList<>();
        List<Integer> list = new LinkedList<>();
        if (target <= 0 || candidates == null || candidates.length == 0) return allCombines;
        boolean visited[] = new boolean[candidates.length];
        Arrays.sort(candidates);
        searchSum2(list, allCombines, visited, 0, 0, target, candidates);
        return allCombines;
    }

    private void searchSum2(List<Integer> list, List<List<Integer>> allCombines, boolean visited[], int curIndex, int curSum, int target, int[] candidates) {
        if (curSum == target) {
            allCombines.add(new LinkedList<>(list));
            return;
        }
        if (curSum > target) return;
        int tmpSum = curSum;
        for (int i = curIndex; i < candidates.length; i++) {
            if (visited[i]) continue;
            if (i != 0 && candidates[i - 1] == candidates[i] && !visited[i - 1])
                continue;//前面那个相同的元素已经访问过了,这个元素才可以被访问。避免重复
            visited[i] = true;
            list.add(candidates[i]);
            curSum = tmpSum + candidates[i];
            searchSum2(list, allCombines, visited, i + 1, curSum, target, candidates);
            list.remove(list.size() - 1);
            visited[i] = false;
        }
    }

10. 1-9 数字的组合求和

    /*
    * 1-9 数字的组合求和
    * 找出所有相加之和为 n 的 k 个数的组合。
    * 组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
    * */
    public List<List<Integer>> combinationSum3(int k, int n) {
        List<List<Integer>> allCombines = new LinkedList<>();
        List<Integer> list = new LinkedList<>();
        if (k <= 0 || n <= 0) return allCombines;
        search2(1, 0, 0, n, k, list, allCombines);
        return allCombines;
    }

    private void search2(int curIndex, int curSum, int count, int n, int k, List<Integer> list, List<List<Integer>> allCombines) {
        if (count == k && curSum == n) {
            allCombines.add(new LinkedList<>(list));
            return;
        }
        if (curSum < n && count < k) {
            int tmpSum = curSum;
            for (int i = curIndex; i <= 9; i++) {
                list.add(i);
                curSum = tmpSum + i;
                search2(i + 1, curSum, count + 1, n, k, list, allCombines);
                list.remove(list.size() - 1);
            }
        }
    }

11. 子集

 /*
    * 子集
    * 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
    * 说明:解集不能包含重复的子集。
    * */
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> allSubsets = new LinkedList<>();
        List<Integer> list = new LinkedList<>();
        searchAllSubsets(0, nums, list, allSubsets);
        return allSubsets;
    }

    private void searchAllSubsets(int curIndex, int[] nums, List<Integer> list, List<List<Integer>> allSubsets) {
        allSubsets.add(new LinkedList<>(list));
        if (curIndex == nums.length) return;
        for (int i = curIndex; i < nums.length; i++) {
            list.add(nums[i]);
            searchAllSubsets(i + 1, nums, list, allSubsets);
            list.remove(list.size() - 1);
        }
    }

12. 含有相同元素求子集

/*
    * 含有相同元素求子集
    * 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
    * 说明:解集不能包含重复的子集。
    * */
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        List<List<Integer>> allSubsets = new LinkedList<>();
        List<Integer> list = new LinkedList<>();
        Arrays.sort(nums);
        boolean visits[] = new boolean[nums.length];
        searchAllSubsets2(0, visits, nums, list, allSubsets);
        return allSubsets;
    }

    private void searchAllSubsets2(int curIndex, boolean visited[], int[] nums, List<Integer> list, List<List<Integer>> allSubsets) {
        allSubsets.add(new LinkedList<>(list));
        if (curIndex == nums.length) return;
        for (int i = curIndex; i < nums.length; i++) {
            if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) continue;
            if (visited[i]) continue;
            visited[i] = true;
            list.add(nums[i]);
            searchAllSubsets2(i + 1, visited, nums, list, allSubsets);
            list.remove(list.size() - 1);
            visited[i] = false;
        }
    }

13. 分割字符串使得每个部分都是回文数

 /*
    * 分割回文串
    * 给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
    * 返回 s 所有可能的分割方案。
    * */
    public List<List<String>> partition(String s) {
        List<List<String>> allScheme = new LinkedList<>();
        List<String> strlist = new LinkedList<>();
        if (s == null || s.length() == 0) return allScheme;
        segStr(s, allScheme, strlist);
        return allScheme;
    }

    public void segStr(String curStr, List<List<String>> allScheme, List<String> strlist) {
        if (curStr.length() == 0) {
            allScheme.add(new LinkedList<String>(strlist));
            return;
        }
        for (int i = 0; i < curStr.length(); i++) {
            if (isPalindrome(curStr.substring(0, i + 1))) {
                strlist.add(curStr.substring(0, i + 1));
                segStr(curStr.substring(i + 1), allScheme, strlist);
                strlist.remove(strlist.size() - 1);
            }
        }
    }

    private boolean isPalindrome(String s) {//判断是否为回文串
        int start = 0;
        int end = s.length() - 1;
        while (start <= end) {
            if (s.charAt(start++) != s.charAt(end--)) return false;
        }
        return true;
    }

14. 数独

 /*
    * 解数独
    * 编写一个程序,通过已填充的空格来解决数独问题。
    * 一个数独的解法需遵循如下规则:
    * 数字 1-9 在每一行只能出现一次。
    * 数字 1-9 在每一列只能出现一次。
    * 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
    * 空白格用 '.' 表示。
    * */
    boolean rowHas[][];
    boolean colHas[][];
    boolean blockHas[][];

    public void solveSudoku(char[][] board) {
        rowHas = new boolean[9][10];//rowHas[i][j]=true表示第i行已经存在数字j
        colHas = new boolean[9][10];//colHas[i][j]=true表示第i列已经存在数字j
        blockHas = new boolean[9][10];//blockHas[i][j]=true表示第i个块已经存在数字j
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                int curElem = board[i][j] - '0';
                if (curElem >= 1 && curElem <= 9) {
                    rowHas[i][curElem] = true;
                    colHas[j][curElem] = true;
                    blockHas[(i / 3) * 3 + j / 3][curElem] = true;
                }
            }
        }
        fillNum(board, 0, 0);
    }

    private boolean fillNum(char[][] board, int row, int col) {
        //从左到右,从上到下找到 需要填数字的地方
        while (row < 9 && board[row][col] != '.') {
            row = col == 8 ? row + 1 : row;
            col = col == 8 ? 0 : col + 1;
        }
        if (row == 9) return true;//(如果都填满了就返回)
        //逐一尝试1-9,回溯法确定填这个数字是否合适
        for (int num = 1; num <= 9; num++) {
            if (rowHas[row][num] || colHas[col][num] || blockHas[(row / 3) * 3 + col / 3][num]) continue;
            rowHas[row][num] = true;
            colHas[col][num] = true;
            blockHas[(row / 3) * 3 + col / 3][num] = true;
            board[row][col] = (char) (num + '0');
            if (fillNum(board, row, col)) return true;
            board[row][col] = '.';
            rowHas[row][num] = false;
            colHas[col][num] = false;
            blockHas[(row / 3) * 3 + col / 3][num] = false;
        }
        return false;
    }

15. N 皇后

/*
    * 题目:N 皇后
    * 题目描述:在 n*n 的矩阵中摆放 n 个皇后,并且每个皇后不能在同一行,同一列,同一对角线上,求所有的 n 皇后的解。
    * 一行一行地摆放,在确定一行中的那个皇后应该摆在哪一列时,需要用三个标记数组来确定某一列是否合法,这三个标记数组分别为:列标记数组、45 度对角线标记数组和 135 度对角线标记数组。
    * */
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> allScheme = new LinkedList<>();
        List<String> list = new LinkedList<>();
        if (n == 0) return allScheme;
        boolean acrossCorner45[] = new boolean[2 * n ];//标记第i个45度对角上是否有皇后
        boolean acrossCorner135[] = new boolean[2 * n ];//标记第i个135度对角上是否有皇后
        boolean col[] = new boolean[n];//标记第i列上是否有皇后
        placeQueens(allScheme, list, 0, col, acrossCorner45, acrossCorner135, n);
        return allScheme;
    }

    private void placeQueens(List<List<String>> allScheme, List<String> list, int rowIndex, boolean[] col, boolean[] acrossCorner45, boolean[] acrossCorner135, int n) {
        if (rowIndex == n) {
            allScheme.add(new LinkedList<String>(list));
            return;
        }
        for (int j = 0; j < n; j++) {
            if (col[j] || acrossCorner45[j + rowIndex] || acrossCorner135[n - j + rowIndex]) {
                continue;
            }
            String s=create(n,j);
            list.add(s);
            col[j]=true;
            acrossCorner45[j + rowIndex]=true;
            acrossCorner135[n - j + rowIndex]=true;
            placeQueens(allScheme,list,rowIndex+1,col,acrossCorner45,acrossCorner135,n);
            col[j]=false;
            acrossCorner45[j + rowIndex]=false;
            acrossCorner135[n - j + rowIndex]=false;
            list.remove(list.size()-1);
        }
    }

    private String create(int n,int j) {
        char str[]=new char[n];
        Arrays.fill(str,'.');
        str[j]='Q';
        return String.valueOf(str);
    }

参考:github—CyC2018题解

发布了184 篇原创文章 · 获赞 60 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/StubbornAccepted/article/details/103080119