代码随想录day21 Java版

过完年开始刷回溯算法,寒假在家时间多点,争取每天多刷点题

回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案。通常是解决复杂的题。

回溯法解决的问题都可以抽象为树形结构,因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度。

模板

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

77. 组合

重点在调用递归的代码,本质上是取代未知层数的循环

码题时直接add了path,最终 res 列表中所有的元素都会指向同一个对象,会导致结果不正确。

class Solution {
    List> res = new ArrayList<>();
    List path = new ArrayList<>();
    public List> combine(int n, int k) {
        backtrack(n,k,1);
        return res;
    }
    void backtrack(int n, int k, int start) {
        if (path.size() == k) {
            res.add(path);
            path.clear();
            return;
        }
        for (int i = start; i <= n; i++) {
            path.add(i);
            backtrack(n,k,i+1);
            path.removeLast();
        }
    }
}

由于后面有回溯的操作,所以根本不用清空path,每次把path的副本添加到结果列表中。

ArrayList使用removeLast充当pop,new ArrayList<>(path)创建副本。

class Solution {
    List> res = new ArrayList<>();
    List path = new ArrayList<>();
    public List> combine(int n, int k) {
        backtrack(n,k,1);
        return res;
    }
    void backtrack(int n, int k, int start) {
        if (path.size() == k) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = start; i <= n; i++) {
            path.add(i);
            backtrack(n,k,i+1);
            path.removeLast();
        }
    }
}

77. 组合优化

采用剪枝操作减少不必要的穷举:在当前的组合路径 path 中已经有了 path.size() 个元素时,还需要选择 k - path.size() 个元素。当 i 的取值为 n - (k - path.size()) + 1 时,即使后面的数字全都选上,也凑不够 k 个元素,所以循环就可以结束了,不需要再继续遍历了。因此,使用 n - (k - path.size()) + 1 作为循环的结束条件,可以避免不必要的遍历,提高效率。

class Solution {
    List> res = new ArrayList<>();
    List path = new ArrayList<>();
    public List> combine(int n, int k) {
        backtrack(n,k,1);
        return res;
    }
    void backtrack(int n, int k, int start) {
        if (path.size() == k) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = start; i <=n - (k - path.size()) + 1; i++) {
            path.add(i);
            backtrack(n,k,i+1);
            path.removeLast();
        }
    }
}

216.组合总和III

看完发现是k数之和,可以用前面的题魔改(在path满了的时候顺手判断下和是否符合要求),并且已经知道了递归树宽度为9。

class Solution {
    List> res = new ArrayList<>();
    List path = new ArrayList<>();
    public List> combinationSum3(int k, int n) {
        backtrack(n,k,1,0);
        return res;
    }
    void backtrack(int target, int k, int start, int sum) {
        if (path.size() == k) {
            if (sum == target) res.add(new ArrayList<>(path));
            return;
        }
        for (int i = start; i <= 9; i++) {
            path.add(i);
            sum += i;
            backtrack(target, k, i+1,sum);
            path.removeLast();
            sum -= i;
        }
    }
}

这道题的剪枝有两点,首先可以模仿组合优化把for循环里的i边界改了,同时如果发现sum比target大后可以直接return。

class Solution {
    List> res = new ArrayList<>();
    List path = new ArrayList<>();
    public List> combinationSum3(int k, int n) {
        backtrack(n,k,1,0);
        return res;
    }
    void backtrack(int target, int k, int start, int sum) {
        if (sum > target) return;
        if (path.size() == k) {
            if (sum == target) res.add(new ArrayList<>(path));
            return;
        }
        for (int i = start; i <= 9 - (k - path.size()) + 1; i++) {
            path.add(i);
            sum += i;
            backtrack(target, k, i+1,sum);
            path.removeLast();
            sum -= i;
        }
    }
}

你可能感兴趣的:(代码随想录打卡,算法)