搜索---回溯

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題解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章