TOP100 回溯

摘在前面(作者:liweiwei1419):

        回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:

        1.找到一个可能存在的正确的答案;
        2.在尝试了所有可能的分步方法后宣告该问题没有答案。

其实应该按照:

这个顺序来学习:

77. 组合 - 力扣(LeetCode)

216. 组合总和 III - 力扣(LeetCode)

17. 电话号码的字母组合 - 力扣(LeetCode)

这篇涉及到去重的题目要注意:

40. 组合总和 II - 力扣(LeetCode)

详细地看看随想录这里的解读:代码随想录 (programmercarl.com)

1.46. 全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

思路:

代码:

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        int len = nums.length;
        // 使用一个动态数组保存所有可能的全排列
        List<List<Integer>> res = new ArrayList<>();
        if (len == 0) {
            return res;
        }
        boolean[] used = new boolean[len];
        Deque<Integer> path = new ArrayDeque<>(len);
        dfs(nums, len, 0, path, used, res);
        return res;
    }

    private void dfs(int[] nums, int len, int depth,
                     Deque<Integer> path, boolean[] used,
                     List<List<Integer>> res) {
        if (depth == len) {//全程path只有一份,因此需要在这里做一次拷贝添加进去
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = 0; i < len; i++) {
            if (!used[i]) {//如果没有访问过
                path.addLast(nums[i]);
                used[i] = true;
                //递归之前
                dfs(nums, len, depth + 1, path, used, res);
                //回退以后,清除位置
                used[i] = false;
                //去除刚刚加进来的最后一个元素
                path.removeLast();
                //递归之后
                }
        }
    }
}

全排列扩展:47全排列Ⅱ

47. 全排列 II - 力扣(LeetCode)

题目:

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

示例 1:

输入:nums = [1,1,2]
输出:
[[1,1,2],
 [1,2,1],
 [2,1,1]]

示例 2:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

提示:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

思考:

这里因为是排列,就涉及到去重,还要再强调一遍:“去重一定要先排序”;
下文的for循环内部呢,就是同一层的选择,back(nums,path,res,used)那一块则是对下一层的遍历,而本问题的去重则是对同一树层,前一位(也就是nums[i-1])如果使用过,那么就进行去重。
!!!
至于代码中提到的used[i-1]或者not used[i-1]都是可以的,是因为这是两种考虑方式,就是去对树枝进行分析,还是对数层进行去重,但无论如何,都需要保持一致性,不能一会是true,一会是false,这样是不可以的,详细见随想录:代码随想录 (programmercarl.com)

代码:

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        res = []
        path = []
        used = [False]*9
        
        def back(nums,path,res,used):
            if len(path)==len(nums):
                res.append(path[:])
                return 
            for i in range(len(nums)):
                if i>0 and nums[i]==nums[i-1] and used[i-1]:
            #去重的关键步骤,其实这里,not used[i-1]也可以过
            # used[i - 1] == true,说明同一树枝nums[i - 1]使用过
            # used[i - 1] == false,说明同一树层nums[i - 1]使用过
            # 如果同一树层nums[i - 1]使用过则直接跳过
                    continue
                if not used[i]: 
                    used[i]=True
                    path.append(nums[i])
                    back(nums,path,res,used)
                    path.pop()
                    used[i] = False
        
        nums.sort()
        back(nums,path,res,used)
        return res

2.78. 子集

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

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

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:

输入:nums = [0]
输出:[[],[0]]

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • nums 中的所有元素 互不相同

思路:

代码:

class Solution {
    List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
    LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
    public List<List<Integer>> subsets(int[] nums) {
        subsetsHelper(nums, 0);
        return result;
    }

    private void subsetsHelper(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]);
            subsetsHelper(nums, i + 1);
            path.removeLast();
        }
    }
}

3.17. 电话号码的字母组合

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

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

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"]

提示:

  • 0 <= digits.length <= 4
  • digits[i] 是范围 ['2', '9'] 的一个数字。

思路:

  • 这是个排列组合问题,可以用树的形式表示出来;
  • 当给定了输入字符串,比如:"23",那么整棵树就构建完成了,如下:

    

算法过程.png

代码:

class Solution {

  
 List<String> result = new ArrayList<>();
 StringBuilder temp = new StringBuilder();
 Map<Character, List<Character>> map = new HashMap<Character, List<Character>>() {
        {
            put('2', Arrays.asList('a', 'b', 'c'));
            put('3', Arrays.asList('d', 'e', 'f'));
            put('4', Arrays.asList('g', 'h', 'i'));
            put('5', Arrays.asList('j', 'k', 'l'));
            put('6', Arrays.asList('m', 'n', 'o'));
            put('7', Arrays.asList('p', 'q', 'r', 's'));
            put('8', Arrays.asList('t', 'u', 'v'));
            put('9', Arrays.asList('w', 'x', 'y', 'z'));
        }
    };
    
    public List<String> letterCombinations(String digits) {
        if (digits == null || digits.length() == 0) {
            return result;
        }
        backTracking(digits, 0);
        return result;
    }
    
    private void backTracking(String digits, int num) {
        if (digits.length() == num) {
            result.add(temp.toString());
            return;
        }

        List<Character> list = map.get(digits.charAt(num));
        if (list == null) {
            return;
        }

        for (int i = 0; i < list.size(); i++) {
            Character cur = list.get(i);
            temp.append(cur);
            backTracking(digits, num + 1);
            temp.deleteCharAt(num);
        }
    }
    

}

用熟悉的套路来完成:

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        dict1={2:"abc",3:"def",4:"ghi",5:"jkl",6:"mno",7:"pqrs",8:"tuv",9:"wxyz"}
        k=len(digits)
        res=[]
        path=[]
        def back(digits,index):#参数里有一个是必须的,那就是比对值,比如总和、拨号键的内容
            if index==len(digits):
                res.append(''.join(path))
                return
                #终止条件
            digit = ord(digits[index]) - ord('0')
            letters = dict1[digit]
            for i in range(len(letters)):
                path.append(letters[i])
                back(digits,index + 1)#注意index+1,一下层要处理下一个数字了
                path.pop()
        if len(digits)==0:
            return res
        back(digits,0)
        return res

注释是需要看懂的,在不同的回溯运用中,函数的传参都不同,但是思想一致,都包含待比对的数据。

  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值