代码随想录算法训练营-Day22

题目

77. 组合

题解:

回溯的三部曲:做选择 -> 递归 -> 撤销选择

时间复杂度:叶子个数( C ( n , k ) C(n,k) C(n,k)) × 路径长度( k k k),即 O ( k ∗ C ( n , k ) ) O(k*C(n,k)) O(kC(n,k))

组合型回溯代码。

private void dfs(int i, int k, List<Integer> path, List<List<Integer>> ans) {
    
    // 1. 计算还需要选几个数
    int leftNums = k - path.size(); 
    
    // 2. 终止条件
    if(path.size() == k) {
        // 重要:必须 new 一个新的 list 存进去。
        // 如果直接存 path,因为 path 是引用,后续的修改会把这里存的变成空的。
        ans.add(new ArrayList<>(path)); 
        return;
    }

    // 3. 循环选择(核心逻辑)
    for(int j = i; j >= leftNums; j--) {
        
        path.add(j);           // 【做选择】:把数字 j 放入篮子
        
        dfs(j-1, k, path, ans); // 【递归】:去 1 到 j-1 里继续选剩下的
        
        path.removeLast();     // 【撤销选择】:也就是“回溯”
                               // 把 j 拿出来,为了下一轮循环能尝试别的数字
    }
}

正序写法。

class Solution {
    public List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> ans = new ArrayList<>();
        List<Integer> path = new ArrayList<>();
        dfs(1, n, k, path, ans);
        return ans;
    }

    private void dfs(int start, int n, int k, List<Integer> path, List<List<Integer>> ans) {
        if(path.size() == k) {
            ans.add(new ArrayList<>(path));
            return;
        }

        if(n-start+1 < k - path.size()) return; // 区间剩余的数字 少于 需要凑够的数字

        for(int j = start; j <= n; j++) {
            path.add(j);
            dfs(j+1, n, k, path, ans);
            path.removeLast();
        }
    }
}
216. 组合总和Ⅲ

题解:

解法一。模仿题77的解法。剪枝有限。

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    List<Integer> path = new ArrayList<>();

    public List<List<Integer>> combinationSum3(int k, int n) {
        dfs(1, k, n);
        return ans;
    }

    // k: 总的k个数
    // n: 最终需要的和
    private void dfs(int start, int k, int n) {
        if(path.size()==k) {
            int sum = 0;
            for(int num : path) sum+=num;
            if(sum==n) ans.add(new ArrayList<>(path));
            return;
        }
        
        if(9-start+1 < k-path.size()) return;

        for(int i=start; i<=9; i++) {
            path.add(i);
            dfs(i+1, k, n);
            path.removeLast();
        }
    }
}

解法二。修改dfs方法中的参数的语义。用来避免解法一中的反复求和的开销。

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    List<Integer> path = new ArrayList<>();

    public List<List<Integer>> combinationSum3(int k, int n) {
        dfs(1, k, n);
        return ans;
    }

    // remainCnt 还需要选几个数
    // remainSum   还需要凑多少和
    private void dfs(int start, int remainCnt, int remainSum) {
        // 1. 剪枝:
        // 剩下的候选数字个数 (9 - startIndex + 1) 如果小于 还需要选的个数
        // 说明怎么选都不够了,直接返回
        if(9-start+1 < remainCnt) return;

        // 2. 终止条件:如果不需要再选数了
        if(remainCnt==0) {
            if(remainSum==0) ans.add(new ArrayList<>(path));
            return;
        }
        // 3. 循环选择
        for(int i=start; i<=9; i++) {
            // 如果当前选的数 i 已经大于 还需要凑的和,那后面更大的数肯定也不行,直接断开
            if(i>remainSum) break;

            path.add(i);
            dfs(i+1, remainCnt-1, remainSum-i);
            path.removeLast();
        }
    }
}
17. 电话号码的字母组合

题解:

需要注意,这道题遍历的是String,而不是数字数组。然后依旧遵循 回溯的三部曲:做选择 -> 递归 -> 撤销选择

class Solution {
    List<String> ans = new ArrayList<>();
    StringBuilder path = new StringBuilder();
    Map<Integer, String> num2chars = new HashMap<>();

    public List<String> letterCombinations(String digits) {
        // 0. 特殊情况处理:如果输入为空,直接返回空列表
        if (digits == null || digits.length() == 0) {
            return ans;
        }

        init(); // 初始化字典

        char[] digitsChars = digits.toCharArray();
        int[] nums = new int[digitsChars.length];
        for(int i=0; i<digitsChars.length; i++) {
            nums[i] = digitsChars[i] - '0'; // 利用 ASCII 码特性转 int
        }

        dfs(0, nums);
        return ans;
    }

    private void init() {
        num2chars.put(2, "abc");
        num2chars.put(3, "def");
        num2chars.put(4, "ghi");
        num2chars.put(5, "jkl");
        num2chars.put(6, "mno");
        num2chars.put(7, "pqrs");
        num2chars.put(8, "tuv");
        num2chars.put(9, "wxyz");
    }

    private void dfs(int idx, int[] nums) {
        // 1. 终止条件:如果 idx 走到了数组末尾,说明所有数字都处理完了
        if(idx == nums.length) {
            ans.add(path.toString());
            return;
        }
        // 2. 取出当前这一层(这一个数字)对应的字母集
        int currentNum = nums[idx];
        String letters = num2chars.get(currentNum);

        // 3. 核心循环:遍历的是“字母”,而不是“数字数组”
        for(int i=0; i<letters.length(); i++) {
            char ch = letters.charAt(i);
            path.append(ch);    // 做选择
            dfs(idx+1, nums);   // 递归下一层(处理下一个数字)
            path.deleteCharAt(path.length()-1); // 回溯,撤销选择
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值