回溯与DP算法总结

回溯算法

  • 常见参数

    • start:起始搜索位置(从哪个位置开始遍历);常用于组合问题;多个集合时不需要使用,因为互不影响。
  • 去重

    • 去重(组合总和II)
      同层去重:candidates[i] == candidates[i - 1] && used[i - 1] == false(前一个树枝使用过该数)
      树枝去重(从头到叶):candidates[i] == candidates[i - 1] && used[i - 1] == true(同一树枝的前面节点使用过)

      回溯与DP算法总结_第1张图片

1. 组合问题

  • N个数中,按一定规则出k个数的集合

  • 类型总结

    • 同一个集合有放回的抽取(start控制)
    • 同一个集合无放回的抽取(start控制)
    • 不同的集合从每个集合中取一个数优化。(不同集合,所以不需要start)
void backtracking(参数) {
//参数:start(遍历开始未知),u(遍历到第几位),tar(目标值),其他数

    if (终止条件) {
        存放结果;
        return;
    }
    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(start,选择列表); // 这里的start控制一个元素是否可以重复选取
        //可以重复,start=i  否则  i+1
        回溯,撤销处理结果
    }
}

例子

39. 组合总和 - 力扣(LeetCode)

class Solution {
    List<List<Integer>> ans=new ArrayList<>();
    List<Integer> path=new ArrayList<>();
    int[] can;
    int tar;
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        can=candidates;
        tar=target;
        dfs(0,0);
        return ans;

    }
    void dfs(int sum,int start){
        if(sum>tar)return;
        if(sum==tar){
            ans.add(new ArrayList<>(path));
            return;
        }
        for(int i=start;i<can.length;i++){
            path.add(can[i]);
            
            sum=sum+can[i];
            dfs(sum,i);
            sum=sum-can[i];//为什么需要复原?
            /*sum不是直接传入dfs了么,函数直接复制了一份啊?
            *例如:第一个数0,sum=2;
            *如果不复原,第一个数取1时,答案就会为,5,导致结果不一样
            *这里复原是为了,同一个数取i+1时准备(同一层)
            */
            path.remove(path.size()-1);

        }


    }
}

2. 分割问题

  • 参数

    • start:代表分割线:下一层遍历的起始位置。

    • 分割线>=边界时加入答案

    for (int i = startIndex; i < s.size(); i++) {//截取[start,i]对应的元素
        if(i>start && s.charAt(start)=='0'){//枚举同一个数所以不能由前导0
                    break;
                }
    }
    

3.子集问题

回溯与DP算法总结_第2张图片

  • 和组合问题相似,但是每一个节点都要加入答案中
  • 不过子集问题是无序的

4. 全排列

回溯与DP算法总结_第3张图片

  • 参数:第一层后后边依旧会处理 前面的数,所以不需要start参数

动态规划

背包问题

  • 如果求组合数就是外层for循环遍历物品,内层for遍历背包

  • 如果求排列数就是外层for遍历背包,内层for循环遍历物品。(题377)

  • 最小值,最大值,注意数组的原始数据的填充

1. 01背包问题

集合分析法

  • 状态
    • 集合
    • 属性
  • 更新公式:分割集合的方法求解
  • 倒序,先价值后体积
for(int num:nums){//枚举物品的价值
    for(int j=m;j>=num;j--){//枚举当前的体积J
        dp[j]=Math.max(dp[j-num]+v[i]);//体积j可以装的最大价值
    }

}

1.这里为什么要倒序?

回溯与DP算法总结_第4张图片

二维21背包

  • 容量加一维,其他同理

    d p [ i ] [ j ] = d p [ i − a ] [ j − b ] + 1 dp[i][j]=dp[i-a][j-b]+1 dp[i][j]=dp[ia][jb]+1

2.完全背包

  • 物品可以选任意个
  • 背包容量不需要倒序,正序遍历即可
/ 先遍历物品,再遍历背包
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

    }
}

3.树形DP

回溯与DP算法总结_第5张图片

  • dp[u,0]:不选这个节点的最大值

  • dp[u,1]:选这个节点的最大值

  • 每个根节点的最大值与两个子树的关系,所以可以省略一维

int[] dfs(TreeNode root){
        if(root==null)return new int[]{0,0};
        //注意这里是一个后端遍历的过程,所以我们先递归,在写本轮处理过程
        int[] x=dfs(root.left);
        int[] y=dfs(root.right);
        //不选该节点,0的位置
        int t=Math.max(x[0],x[1])+Math.max(y[0],y[1]);
        //选root节点
        int a=x[0]+y[0]+root.val;
        return new int[]{t,a};


    }

4.状态机DP

回溯与DP算法总结_第6张图片

买入:0,卖出:1,,冷冻:2,冷冻后保持卖出:3;

1.分析每个状态可能有那几个状态到达

0:前一天买入,保持不变;冷冻后今天买入;冷冻第二天及以后的买入

1:今天卖出;

2:冷冻:有卖出可到

3:冷冻后第二天开始进入,自己保持

你可能感兴趣的:(算法)