回溯

模板

不戳我后悔一辈子

 

组合 题目 解析

剪枝挺有必要的,时间快了10倍多。

class Solution {
    private List<List<Integer>> ans = new ArrayList<>();
    private List<Integer> oneans = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        flashback(n, k, 1);
        return ans;
    }
    private void flashback(int n, int k, int index) {
        if (oneans.size() == k) {
            ans.add(new ArrayList<>(oneans));  //这里要特别注意
            return;
        }
        for (int i = index; i <= n-(k-oneans.size())+1; i++) {
            oneans.add(i);
            flashback(n, k, i+1);
            oneans.remove(oneans.size()-1);
        } 
    }
}

 

组合总和Ⅲ 题目 解析

感觉还是二叉树的简单题更难,这直接套模板。

class Solution {
    private List<List<Integer>> ans = new ArrayList<>();
    private List<Integer> oneans = new ArrayList<>();
    public List<List<Integer>> combinationSum3(int k, int n) {
        flashback(k, n, 1);
        return ans;
    }
    private void flashback(int k, int n, int index) {
        if (n == 0 && oneans.size() == k) {
            ans.add(new ArrayList<>(oneans));
            return;
        }
        for (int i = index; i <= 9 && n >= 0; i++) {
            oneans.add(i);
            flashback(k, n-i, i+1);
            oneans.remove(oneans.size()-1);
        }
    }
}

 

电话号码的字母组合 题目 解析

StringBuilder的各种操作和构造map才是难点吧。StringBuilder的append放在递归里是不行的。具体原因自己百度。

class Solution {
    private List<String> ans = new ArrayList<>();
    private StringBuilder oneans = new StringBuilder();
    private String[] map = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    public List<String> letterCombinations(String digits) {
        if (digits == null || digits.length() == 0) return ans;
        flashback(digits, 0);
        return ans;
    }
    private void flashback(String digits, int index) {
        if (index == digits.length()) {
            ans.add(oneans.toString());
            return;
        }
        char digit = digits.charAt(index);
        String key = map[digit-'0'-2];
        for (int i = 0; i < key.length(); i++) {
            oneans.append(key.charAt(i));
            flashback(digits, index+1);
            oneans.deleteCharAt(oneans.length()-1);
        }
    }
}

 

组合总数 题目 解析

这是一个模板题

class Solution {
    private List<Integer> oneans = new ArrayList<>();
    private List<List<Integer>> ans = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        flashback(target, candidates, 0);
        Arrays.sort(candidates);
        return ans;
    }
    private void flashback(int sum, int[] candidates, int index){
        if (sum == 0) {
            ans.add(new ArrayList<>(oneans));
            return;
        }
        if (sum < 0) return;
        for (int i = index; i < candidates.length; i++) {
            oneans.add(candidates[i]);
 //因为下一层也可以包含本身,所以递归是i不是i+1
            flashback(sum-candidates[i], candidates, i);  
            oneans.remove(oneans.size()-1);
        }
    }
}

 

class Solution {
    private List<Integer> oneans = new ArrayList<>();
    private List<List<Integer>> ans = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        flashback(target, candidates, 0);
        Arrays.sort(candidates);
        return ans;
    }
    private void flashback(int sum, int[] candidates, int index){
        if (sum == 0) {
            ans.add(new ArrayList<>(oneans));
            return;
        }
        if (sum < 0 || index == candidates.length) return;

        oneans.add(candidates[index]);
        flashback(sum-candidates[index], candidates, index);
        oneans.remove(oneans.size()-1);

        flashback(sum, candidates, index+1);
    }
}

 

组合总数Ⅱ 题目 解析

遇事不决就画图,剪枝思路和三数之和一样。

class Solution {
    private List<List<Integer>> ans = new ArrayList<>();
    private List<Integer> oneans = new ArrayList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        flashback(candidates, 0, target);
        return ans;
    }
    private void flashback(int[] candidates, int index, int sum) {
        if (sum == 0) {
            ans.add(new ArrayList<>(oneans));
            return;
        }
        for (int i = index; i < candidates.length; i++) {
            if (i != index && candidates[i] == candidates[i-1]) continue;
            if (sum-candidates[i] < 0) break;
            oneans.add(candidates[i]);
            flashback(candidates, i+1, sum-candidates[i]);
            oneans.remove(oneans.size()-1);
        }
    }
}

 

分割回文串 题目 解析

就这,就这?难点是substring记不住吧?

class Solution {
    private List<List<String>> ans = new ArrayList<>();
    private List<String> oneans = new ArrayList<>();
    public List<List<String>> partition(String s) {
        flashback(s, 0);
        return ans;
    }
    private void flashback(String s, int index) {
        if (index == s.length()) {
            ans.add(new ArrayList<>(oneans));
        }
        for (int i = index; i < s.length(); i++) {
            String sb = s.substring(index, i+1);
            if(check(sb)) {
                oneans.add(sb);
                flashback(s, i+1);
                oneans.remove(oneans.size()-1);
            }
        }
    }
    private boolean check(String s){
        int i = 0, j = s.length()-1;
        while (i < j) {
            if (s.charAt(i++) != s.charAt(j--)) {
                return false;
            }
        }
        return true;
    } 
}

 

复原IP地址 题目 解析

难点是各种边界,我在剪枝里判定了一部分边界。试了一下不剪枝,倒在了黎明的前夕(最后一个用例超时)。

class Solution {
    private List<String> ans = new ArrayList<>();
    private List<String> oneans = new ArrayList<>();
    public List<String> restoreIpAddresses(String s) {
        flashback(s, 0);
        return ans;
    }
    private void flashback(String s, int index) {
        int n = s.length();
        if (oneans.size() == 3) {
            String test = s.substring(index, n);
            if (check(test)) {
                StringBuilder sb = new StringBuilder();
                for (String st : oneans) {
                    sb.append(st);
                    sb.append('.');
                }
                sb.append(test);
                ans.add(sb.toString());
            }
            return;
        }
        for (int i = index+1; i <= index + 3 && i <= n; i++) {
            int m = n - i;
            if (m > 3*(3-oneans.size())) continue;
            if (m < 3-oneans.size()) break;  
            String test = s.substring(index, i);
            if (check(test)) {
                oneans.add(test);
                flashback(s, i);
                oneans.remove(oneans.size()-1);
            }
        }
    }
    private boolean check(String s) {
        if (s.length() > 1 && s.charAt(0) == '0') return false; 
        int n = 0;
        for (char c : s.toCharArray()) {
            n = n * 10 + (c - '0');
        }
        if (n > 255) return false;
        return true;
    }
}

 

子集 题目 解析

模板题

这是我写的,求长度为1的子集,为2的子集...这样循环求到n。对于每个长度用一次回溯。

class Solution {
    private List<List<Integer>> ans = new ArrayList<>();
    private List<Integer> oneans = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        for (int i = 0; i <= nums.length; i++) {
            flashback(nums, i, 0);
        }
        return ans;
    }
    private void flashback(int[] nums, int len, int index) {
        if (oneans.size() == len) {
            ans.add(new ArrayList<>(oneans));
            return;
        }
        for (int i = index; i < nums.length; i++) {
            oneans.add(nums[i]);
            flashback(nums, len, i+1);
            oneans.remove(oneans.size()-1);
        }
    }
}

其实画个图就很清晰了,子集不是记录每个叶子结点,而是每个节点都要记录!

class Solution {
    private List<List<Integer>> ans = new ArrayList<>();
    private List<Integer> oneans = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        flashback(nums, 0);
        return ans;
    }
    private void flashback(int[] nums, int index) {
        ans.add(new ArrayList<>(oneans));
        for (int i = index; i < nums.length; i++) { //判断结束回溯的条件其实就在循环里
            oneans.add(nums[i]);
            flashback(nums, i+1);
            oneans.remove(oneans.size()-1);
        }
    }
}

 

子集Ⅱ 题目 解析

一开始我的去重复判断是 i != 0,这是不对的,这样就一直是在第一层去重。在当前层去重应该是 i != index。

class Solution {
    private List<List<Integer>> ans = new ArrayList<>();
    private List<Integer> oneans = new ArrayList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        flashback(nums, 0);
        return ans;
    }
    private void flashback(int[] nums, int index) {
        ans.add(new ArrayList<>(oneans));
        for (int i = index; i < nums.length; i++) {
            if (i != index && nums[i] == nums[i-1]) continue;  //去重
            oneans.add(nums[i]);
            flashback(nums, i+1);
            oneans.remove(oneans.size()-1);
        }
    }
}

 

递增子序列 题目 解析

重点就是剪枝

class Solution {
    private List<List<Integer>> ans = new ArrayList<>();
    private List<Integer> oneans = new ArrayList<>();
    public List<List<Integer>> findSubsequences(int[] nums) {

         flashback(nums, 0);
         return ans;
    }
    private void flashback(int[] nums, int index) {
        if (oneans.size() >= 2) {
            ans.add(new ArrayList<>(oneans));
        }
        Set<Integer> map = new HashSet<>();
        for (int i = index; i < nums.length; i++) {
            if (map.contains(nums[i])) continue;
            if (oneans.size() > 0 && oneans.get(oneans.size()-1) > nums[i]) continue;
            map.add(nums[i]);
            oneans.add(nums[i]);
            flashback(nums, i+1);
            oneans.remove(oneans.size()-1);
        }
    }
}

 

全排列 题目 解析

一开始用set去重的,其实根本没必要。用一个boolean数组就好了。用set更加耗时。

class Solution {
    private List<List<Integer>> ans = new ArrayList<>();
    private List<Integer> oneans = new ArrayList<>();
    public List<List<Integer>> permute(int[] nums) {
        boolean[] used = new boolean[nums.length]; 
        flashback(nums, used);
        return ans;
    }
    private void flashback(int[] nums, boolean[] used) {
        if (oneans.size() == nums.length) {
            ans.add(new ArrayList<>(oneans));
            return;    //这时的return是必要的,因为下面循环里没有递归的终止条件
        }
        for (int i = 0; i < nums.length; i++) {
            if (used[i]) continue;
            oneans.add(nums[i]);
            used[i] = true;
            flashback(nums, used);
            oneans.remove(oneans.size()-1);
            used[i] = false;
        }
    }
}

 

重新安排行程 题目

太恐怖了,先埋坑。打破了我回溯不看题解的快乐,这是看了题解也搞不懂。

 

N皇后 题目 解析

难点在于判断条件和输出格式。这里用一维数组存储存储位置。下标是行数,数据是列数。

class Solution {
    private List<List<String>> ans = new ArrayList<>();
    public List<List<String>> solveNQueens(int n) {
        int[] mark = new int[n];
        flashback(mark, 0, n);
        return ans;
    }
    private void flashback(int[] mark, int index, int n) {
        if (index == n) {
            List<String> oneans = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                StringBuilder sb = new StringBuilder();
                for (int j = 0; j < n; j++) {
                    if (j == mark[i]) {
                        sb.append('Q');
                    }
                    else {
                        sb.append('.');
                    }
                }
                oneans.add(sb.toString());
            }
            ans.add(oneans);
            return;    //这个return不可省,循环里没有终止条件
        }
        for (int i = 0; i < n; i++) {
            mark[index] = i;
            if (check(mark, index)) {
                flashback(mark, index+1, n);
            }
        }
    }
    private boolean check(int[] mark, int index) {
        for (int i = 0; i < index; i++) {
            for (int j = i + 1; j <= index; j++) {
                if (mark[i] == mark[j]) return false;
                if (mark[i] - i == mark[j] - j) return false;
                if (mark[i] + i == mark[j] + j) return false; 
            }
        }
        return true;

    }
}

 

解数独 题目 解析

大意了,递归里面不能用index++,都要结束了才犯这种错误是不是太晚了?

这题是个二维数组,用双层循环,也可以用转化为一维数组。因为是有一个解就返回了,所以是用boolean类型。

class Solution {
    public void solveSudoku(char[][] board) {
        flashback(board, 0);
    }
    private boolean flashback(char[][] board, int index) {
        if (index == 81) return true;
        int row = index / 9;
        int col = index % 9;
        if (board[row][col] != '.') {
            return flashback(board, index + 1);
        }
        else {
            for (char ch = '1'; ch <= '9'; ch++) {
            if (check(board, row, col, ch)) {
                board[row][col] = ch;
                if (flashback(board, index + 1)) return true;
                board[row][col] = '.';
                }
            }
        }
        return false;
    }
    private boolean check(char[][] board, int row, int col, char val) {
        for (int k = 0; k < 9; k++) {
            if (board[k][col] == val) return false;
            if (board[row][k] == val) return false;
            int i = row / 3 * 3 + k / 3;
            int j = col / 3 * 3 + k % 3;
            if (board[i][j] == val) return false;
        }
        return true;
    }
}

用二维,双层循环结束相当于上面的index == 81。所以最后是return true。如果9个字符都不匹配记得要return false。可以和上面两相印证。

class Solution {
    public void solveSudoku(char[][] board) {
        flashback(board);
    }
    private boolean flashback(char[][] board) {
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (board[i][j] != '.') continue;
                for (char ch = '1'; ch <='9'; ch++) {
                    if (check(board, i, j, ch)) {
                        board[i][j] = ch;
                        if (flashback(board)) return true;
                        board[i][j] = '.';
                    }
                }
                return false;
            }
        }
        return true;
    }
    private boolean check(char[][] board, int row, int col, char val) {
        for (int k = 0; k < 9; k++) {
            if (board[k][col] == val) return false;
            if (board[row][k] == val) return false;
            int i = row / 3 * 3 + k / 3;
            int j = col / 3 * 3 + k % 3;
            if (board[i][j] == val) return false;
        }
        return true;
    }
}

 

回溯因为之前刻意练习了,所以这次写得格外丝滑,飞机票那个除外。最后解数独翻车了,因为我竟然在回溯函数里用了++!

posted @ 2020-11-13 22:39  CPJ31415  阅读(121)  评论(0)    收藏  举报