LeetCode入门指南 之 回溯思想

模板

result = {}
void backtrack(选择列表, 路径) {
    if (满足结束条件) {
     	result.add(路径)
        return   
    }
    
    for 选择 in 选择列表 {
     	做选择
        backtrack(选择列表,路径)
        撤销选择   
    }
}

核心就是从选择列表里做一个选择,然后一直递归往下搜索答案,如果遇到路径不通,就返回来撤销这次选择。

推荐阅读回溯思想团灭排列、组合、子集问题

78. 子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

class Solution {
    public List<list<integer>> subsets(int[] nums) {
        LinkedList<integer> track = new LinkedList<>();
        dfs(nums, track, 0);
        return res;
    }

    private List<list<integer>> res = new LinkedList<>();
    private void dfs (int[] nums, LinkedList<integer> track, int start) {
        res.add(new LinkedList<>(track));

        //通过start来控制当前可以选择的列表
        for (int i = start; i < nums.length; i++) {
            //选择
            track.offerLast(nums[i]);
            dfs(nums, track, i + 1);
            //撤销选择
            track.pollLast();
        }
    }
}

//                  {1, 2, 3}对应递归树:
//                                []
//                       1        2        3			
//                   12   13    23
//                  123

90. 子集 II

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

class Solution {
    public List<list<integer>> subsetsWithDup(int[] nums) {
        LinkedList<integer> track = new LinkedList<>();
        //先排序
        Arrays.sort(nums);
        dfs(nums, track, 0);
        return res;
    }

    private List<list<integer>> res = new LinkedList<>();

    private void dfs(int[] nums, LinkedList<integer> track, int start) {
        res.add(new LinkedList<>(track));

        for (int i = start; i < nums.length; i++) {
            //剪枝(跳过重复的)
            if ((i != start) && (nums[i] == nums[i - 1])) {
                continue;
            }

            track.offerLast(nums[i]);
            dfs(nums, track, i + 1);
            track.pollLast();
        }
    }
}

//                {1, 2, 2, 3}对应递归树:
//                                          []
//						1              2          2(剪)       3			
//                 12  12(剪) 13     22  23       23
//                123  123        223

46. 全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

class Solution {
    public List<list<integer>> permute(int[] nums) {
        LinkedList<integer> track = new LinkedList<>();
        boolean[] visited = new boolean[nums.length];

        dfs (nums, track, visited);

        return res;
    }

    private List<list<integer>> res = new LinkedList<>();
    private void dfs(int[] nums, LinkedList<integer> track, boolean[] visited) {
        // base case
        if (track.size() == nums.length) {
            res.add(new LinkedList<integer>(track));
            return;
        }

        for (int i = 0; i < nums.length; i++) {
            // track中已经包含的就跳过
            if (visited[i]) {
                continue;
            }

            // 选择
            track.offerLast(nums[i]);
            visited[i] = true;

            dfs(nums, track, visited);
            
            // 撤销选择
            track.pollLast();
            visited[i] = false;
        }
    }
}

47. 全排列 II

给定一个可包含重复数字的序列 nums按任意顺序 返回所有不重复的全排列。

class Solution {
    public List<list<integer>> permuteUnique(int[] nums) {
        LinkedList<integer> track = new LinkedList<>();
        boolean[] visited = new boolean[nums.length];
        Arrays.sort(nums);

        dfs (nums, track, visited);

        return res;
    }

    private List<list<integer>> res = new LinkedList<>();
    private void dfs(int[] nums, LinkedList<integer> track, boolean[] visited) {
        // base case
        if (track.size() == nums.length) {
            res.add(new LinkedList<integer>(track));
            return;
        }

        for (int i = 0; i < nums.length; i++) {
            // track中已经包含的就跳过
            if (visited[i]) {
                continue;
            }

            // 剪枝
            if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {
                continue;
            }

            // 选择
            track.offerLast(nums[i]);
            visited[i] = true;

            dfs(nums, track, visited);
            
            // 撤销选择
            track.pollLast();
            visited[i] = false;
        }
    }
}

推荐阅读回溯搜索 + 剪枝

39. 组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。
class Solution {
    public List<list<integer>> combinationSum(int[] candidates, int target) {
        LinkedList<integer> track = new LinkedList<>();
        dfs(candidates, track, target, 0);
        return res;
    }

    private List<list<integer>> res = new LinkedList<>();

    private void dfs(int[] candidates, LinkedList<integer> track, int target, int start) {
        //base case
        if (target < 0) {
            return;
        }
        //base case
        if (target == 0) {
            res.add(new LinkedList<>(track));
            return;
        }

        //通过start改变可选列表
        for (int i = start; i < candidates.length; i++) {
            track.offerLast(candidates[i]);
            dfs(candidates, track, target - candidates[i], i);
            track.pollLast();
        }
    }
}

推荐阅读:回溯算法 + 剪枝(回溯经典例题详解)

class Solution {
    public List<list<integer>> combinationSum(int[] candidates, int target) {
        LinkedList<integer> track = new LinkedList<>();
        Arrays.sort(candidates); //排序后可实现进一步剪枝
        dfs(candidates, track, target, 0);
        return res;
    }

    private List<list<integer>> res = new LinkedList<>();

    private void dfs(int[] candidates, LinkedList<integer> track, int target, int start) {
        //不会再出现target小于0的情况
        //base case
        if (target == 0) {
            res.add(new LinkedList<>(track));
            return;
        }

        //通过start改变可选列表
        for (int i = start; i < candidates.length; i++) {
            //由于此时candidates是有序的,candidates[i] 后面的元素都大于target,直接忽略
            if (target - candidates[i] < 0) {
                break;
            }
            
            track.offerLast(candidates[i]);
            dfs(candidates, track, target - candidates[i], i);
            track.pollLast();
        }
    }
}

17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

LeetCode入门指南 之 回溯思想

class Solution {
    public List<string> letterCombinations(String digits) {
        if (digits == null || digits.length() <= 0) {
            return new LinkedList<string>();
        }
        
        StringBuilder track = new StringBuilder();
        dfs(digits, 0, track);
        return res;
    }

    private Map<character, string[]=""> map = new HashMap<>() {
        {
            put('2', new String[] {"a", "b", "c"});
            put('3', new String[] {"d", "e", "f"});
            put('4', new String[] {"g", "h", "i"});
            put('5', new String[] {"j", "k", "l"});
            put('6', new String[] {"m", "n", "o"});
            put('7', new String[] {"p", "q", "r", "s"});
            put('8', new String[] {"t", "u", "v"});
            put('9', new String[] {"w", "x", "y", "z"});
        }
    };

    private List<string> res = new LinkedList<>();
    private void dfs(String digits, int index, StringBuilder track) {
        if (index == digits.length()) {
            res.add(track.toString());
            return;
        }

        String[] alphabets = map.get(digits.charAt(index));
        for (int i = 0; i < alphabets.length; i++) {
            // 选择
            track.append(alphabets[i]);

            dfs(digits, index + 1, track);

            // 撤销选择
            track.deleteCharAt(track.length() - 1);
        }
    }

}

131. 分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

回文串 是正着读和反着读都一样的字符串。

class Solution {
    public List<list<string>> partition(String s) {
        
        // 先使用动态规划获得任意两个区间的字符串是否为回文字符串
        boolean[][] isPalindrome = getPalindrome(s);

        LinkedList<string> track = new LinkedList<>();
        dfs(s, 0, track, isPalindrome);

        return res;

    }

    private List<list<string>> res = new LinkedList<>();
    private boolean[][] getPalindrome(String s) {
        int len = s.length();

        // 区间i,j的字符串是否为回文字符串(左右都为闭区间)
        boolean[][] dp = new boolean[len][len];

        for (int j = 0; j < len; j++) {
            for (int i = 0; i <= j; i++) {
                if (s.charAt(i) == s.charAt(j) && (j - i <= 2 || dp[i + 1][j - 1])) {
                    dp[i][j] = true;
                }
            }
        }

        return dp;
    }

    private void dfs(String s, int start, LinkedList<string> track, boolean[][] isPalindrome) {
        if (start == s.length()) {
            res.add(new LinkedList<>(track));
            return;
        }

        for (int i = start; i < s.length(); i++) {
            // 如果区间[start, i]的字符串不为回文字符串就可以剪枝
            if (!isPalindrome[start][i]) {
                continue;
            }

            // 选择
            track.offerLast(s.substring(start, i + 1));

            dfs(s, i + 1, track, isPalindrome);

            // 撤销选择
            track.pollLast();
        }
    }
}

93. 复原 IP 地址

给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 IP 地址 。你可以按任何顺序返回答案。

有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

例如:"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 无效 IP 地址。

class Solution {
    public List<string> restoreIpAddresses(String s) {
        int len = s.length();
        if (len < 4 || len > 12) return new LinkedList<>();

        LinkedList<string> track = new LinkedList<>();
        dfs(s, 0 ,track);

        return res;
    }

    private List<string> res = new LinkedList<>();
    private void dfs(String s, int start, LinkedList<string> track) {
        // 只有track已经有4段,且没有剩余字符时符合条件
        if (track.size() == 4) {
            if (start == s.length()) {
                res.add(toStr(track));
            }

            return;
        }
        

        for (int i = start; i < start + 3 && i < s.length(); i++) {
            String buf = s.substring(start, i + 1);

            if (!isValid(buf)) {
                continue;
            }

            track.offerLast(buf);

            dfs(s, i + 1, track);

            track.pollLast();
        }
    }

    private String toStr(LinkedList<string> list) {
        return String.join(".", list);
    }

    private boolean isValid(String str) {
        int len = str.length();
        int buf = Integer.parseInt(str);

        // 排除 09 等
        if (len == 2 && buf < 10) {
            return false;
        // 排除 099、256 等
        } else if (len == 3 && (buf < 100 || buf > 255)) {
            return false;
        }

        return true;
    }
}
上一篇:【笔记】with torch.no_grad( ):上下文管理器,被该语句wrap起来的部分将不会track梯度


下一篇:LeetCode_47. 全排列Ⅱ(回溯\递归\去重)