A summary of dynamic programming

1. The knapsack problem

1 Problem Definition

How to determine whether a problem can be solved with the knapsack problem
The common feature of the knapsack problem: given a knapsack capacity target and an item array nums , can the elements in nums be selected in a certain way to get the target
Note:
1. target and nums may be numbers or strings
2 , target can be explicit (the title has been given), or it can be non-explicit (need to dig out the title information)
3. Common nums selection methods: each element can only be selected once / each element can be selected multiple times / selected elements are arranged

2 Problem classification

Common types of backpacks are as follows:
1. 0/1 knapsack problem : each element can be selected at most once
2. Complete knapsack problem : each element can be selected repeatedly
3. Combination knapsack problem : the order
of items in the knapsack should be considered Iterate through each backpack

The requirements of each knapsack problem are also different. According to the classification of the problem, it can be divided into the following types:
1. The most value problem : the maximum/minimum value is required
2. The existence problem : whether there is... ………
3. Combination problem : Find all permutations and combinations that satisfy…

3 problem solving template

01 The problem of the most valuable backpack

This problem is the simplest and most basic. After understanding this problem, you can learn the remaining knapsack problem with a little modification.

  • There is a backpack that can hold up to bagWeight items, bagWeight=4
  • The weight of each item is expressed as an array weight = {1, 3, 4}
  • The value of each item is expressed as an array value = {15, 20, 30}
  • Q: On the premise of not being overweight, what is the maximum value of items that the backpack can hold
  • Problem-solving ideas:
  1. First of all, there is a boundary that is easy to think about. When the capacity of the backpack is 0, nothing can be put in it, and the maximum value is all 0.
  2. Then consider the easiest sub-problem to solve, assuming there is only one item 1, and the maximum capacity of the backpack is 1~bagWeight, then the best solution can only choose item 1.
  3. Suppose an item 2 is added, and the maximum capacity of the backpack is 1~bagWeight.
    Because the weight of item 2 is 3, when the maximum capacity of the backpack is 1 or 2, only one item 1 can be selected, because there is no room for item 2 at all.
    When the maximum capacity of the backpack is > 2, you need to make a choice. This is the essence of the dynamic rule. You need to compare two schemes, because for item 2, there are only two possibilities, either take it or not, and then start from Choose the most valuable of the two options . For example, when the maximum capacity of the backpack is 4:
    Scheme 1 : Take item 2, then the remaining capacity of the backpack is 4-3=1, then we only need to know what can be obtained when the remaining capacity of the backpack is 1 and there is no item i The maximum value, and then adding the value of item 2, is the total value of the plan;
    plan 2 : without taking item 2, then the total value is actually the maximum value of the backpack when the remaining capacity of the backpack is 4, and only item 1 .
    Always follow this principle, make the maximum capacity of the backpack from 1 to bagWeight, and calculate the maximum value of items that the backpack can hold when there are only items 1 and 2
  4. Traverse the entire item array, and for each sub-array, traverse the backpack capacity from 0 to bagWeight, and finally obtain the maximum value of the complete item array when the backpack capacity is bagWeight, which is the answer
  • Code Implementation
    First, we need a dp two-dimensional array, which uses rows to represent items, uses i to cycle , and columns to represent backpack capacity, (use j to cycle) , dp[i][j] represents when the backpack capacity is j, from i How to choose among the items to get the maximum value. For example, row 2 and column 3 indicate: when the knapsack capacity is 2, how to choose from items 1 and 2 can get the maximum value.
    Continue to analyze according to the above problem-solving ideas:
  1. First of all, there is a boundary that is easy to think about. When the backpack capacity is 0, nothing can be put in, and the maximum value is all 0, that is, the first column of the dp array is all 0, dp[i][0]=0)
  2. Then consider the easiest sub-problem to solve, assuming there is only one item 1, and the maximum capacity of the backpack is 1~bagWeight, then the best solution can only be to choose item 1, that is, dp[0][j]=value[0]
  3. Suppose an item 2 is added, and the maximum capacity of the backpack is 1~bagWeight.
    Because the weight of item 2 is 3 (weight[i]=3), when the maximum capacity of the knapsack is 1, 2 (j=1, j=2), even if only one item 2 is loaded, it cannot be loaded, that is, dp[i ] [j]=dp[i-1][j], if j<weight[i]
    When the maximum capacity of the backpack> 2 (j>2), you need to make a choice. This is the essence of the dynamic gauge. You need to compare the two There are two schemes, because for item 2, there are only two possibilities, either take it or not, and then choose the one with the highest value from the two schemes, that is, max (Scheme 1, Scheme 2) . For example, when the maximum capacity of the backpack is 4 (j=4):
    Scheme 1 : Take item 2, then the remaining capacity of the backpack is 4-3=1, namely j-weight[i] , then we only need to know the remaining capacity of the backpack When it is 1, when there is no item i, the maximum value that can be obtained, that is, dp[i-1][j-weight[i]] , and then adding the value of item 2 (value[i]), is the solution The total value, that is, dp[i-1][j-weight[i]]+value[i] ;
    scheme 2 : do not take item 2, then the total value is actually the case where there is only item 1 when the remaining capacity of the backpack is 4 Next, the maximum value of the knapsack, namely dp[i][j]=dp[i-1][j] .
    Always follow this principle, make the maximum capacity of the backpack from 1 to bagWeight, and calculate the maximum value of items that the backpack can hold when there are only items 1 and 2
  4. Traverse the entire item array, and for each sub-array, traverse the backpack capacity from 0 to bagWeight, and finally get the maximum value of the last item when the backpack capacity is bagWeight, which is the answer, that is, dp[total number of items-1][backpack maximum capacity]

Code implementation (java)

    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        int bagWeight = scanner.nextInt(); // 背包最大容量
        int n = scanner.nextInt(); // 物品数量
        // 物品重量数组
        int[] weight = new int[n];
        for (int i = 0; i < n; i++) {
    
    
            weight[i] = scanner.nextInt();
        }
        // 物品价值数组
        int[] value = new int[n];
        for (int i = 0; i < n; i++) {
    
    
            value[i] = scanner.nextInt();
        }
        // 调用方法求解不超出最大容量的前提下,背包最多能背多大价值的物品
        System.out.println(bags(bagWeight, weight, value));

    }

    public static int bags(int bagWeight, int[] weight, int[] value) {
    
    
        int n = weight.length; // 物品数量
        int[][] dp = new int[n][bagWeight+1]; // dp数组,行表示物品,列表示从0到最大容量
        // 第一列表示背包容量为0时的情况,第一列应该全为0。
        // 由于建dp数组时,java会默认为数组赋0,所以保持第一列为0,更新第二列及以后的即可
        // 从上到下从左到右计算dp,右下角即答案
        for (int i = 0; i < n; i++) {
    
    
            for (int j = 1; j <= bagWeight; j++) {
    
    
                // 第一行表示只能选第一个物品
                if (i == 0) {
    
    
                    dp[i][j] = value[i];
                }
                // 剩余行表示有多个物品可选,需要考虑两种情况
                else {
    
    
                    // 情况1:背包容量就算只装一个物品i也装不下
                    if (j < weight[i]) {
    
    
                        dp[i][j] = dp[i-1][j];
                    }
                    // 情况2:背包容量可以装下物品i,需要考虑两种方案,然后取最大
                    else {
    
    
                        // 方案1:不装物品i
                        // 方案2:装物品i,最大价值为 物品i的价值 加上 去掉物品i的重量后背包剩余容量的最大价值
                        dp[i][j] = Math.max(dp[i-1][j], value[i] + dp[i-1][j-weight[i]]);
                    }
                }
            }
        }
        return dp[n-1][bagWeight]; // 答案是数组的右下角
    }

The resulting dp array and answer:

dp = 
[0, 15, 15, 15, 15]
[0, 15, 15, 20, 35]
[0, 15, 15, 20, 35]
answer = 35
  • Advanced: Observe the calculation process, dp is calculated line by line, in order to save space, we can save only one line of data.
    public static int bags(int bagWeight, int[] weight, int[] value) {
    
    
        int n = weight.length; // 物品数量
        int[] dp = new int[bagWeight+1]; // dp数组,表示从0到最大容量可以装的最大价值
        // 第一个元素表示背包容量为0时的情况。
        // 由于建dp数组时,java会默认为数组赋0,所以保持第一个元素为0,更新第二个元素及以后的即可
        // 从左到右计算dp,最后一个元素即答案
        for (int i = 0; i < n; i++) {
    
    
            // 注意!!!在计算转移方程的过程中,我们需要用到上一次循环得到的dp数组,所以内层循环必须倒序,否则转移方程的dp[j-weight[i]]会被覆盖掉,二维数组不存在这个问题
            for (int j = bagWeight; j > 0; j--) {
    
    
                // 当背包容量可以装下物品i时
                if (j >= weight[i]) {
    
    
                    // 如果只有一个物品可选
                    if (i == 0) {
    
    
                        dp[j] = value[i];
                    }
                    // 如果有多个物品可选
                    else {
    
    
                        // 方案1:不装物品i
                        // 方案2:装物品i,最大价值为 物品i的价值 加上 去掉物品i的重量后背包剩余容量的最大价值
                        dp[j] = Math.max(dp[j], value[i] + dp[j-weight[i]]);
                    }
                }
            }
            System.out.println(Arrays.toString(dp));
        }

        return dp[bagWeight]; // 答案是数组的最后一个元素
    }

        return dp[bagWeight]; // 答案是数组的最后一个元素
    }

Get the dp array and answer for each loop:

dp = [0, 15, 15, 15, 15]
dp = [0, 15, 15, 20, 35]
dp = [0, 15, 15, 20, 35]
answer = 35

It can be found that the essence of thinking is the same as the calculation process, but only saves space

remaining knapsack problem

The analysis routine is basically the same as the 01 most value knapsack problem, with the following differences:

  • cycle
  1. 0/1 knapsack: the outer loop item array, the inner loop knapsack capacity, if a rolling one-dimensional array is used, the inner loop is reversed from the maximum capacity;
  2. Complete backpack: outer loop item array, inner loop backpack capacity, inner loop positive sequence
  3. Combined backpack: outer loop backpack capacity, inner loop item array, outer loop positive sequence
  4. Grouped backpacks: This is quite special and requires triple loops: the number of backpacks in the outer loop is bags, and the inner two-layer loop is converted into templates for 1, 2, and 3 backpack types according to the requirements of the topic
  • state transition equation
  1. Most value problem: dp[i] = max/min(dp[i], dp[i-nums]+1) or dp[i] = max/min(dp[i], dp[i-num]+nums );
  2. There is a problem (bool): dp[i] = dp[i]||dp[i-num];
  3. Combination problem: dp[i] += dp[i-num];

4 Example Analysis

LeetCode1049. The Weight of the Last Stone II

  • Abstract the problem into a knapsack problem (the difficulty is here)
  1. Title description : From a pile of stones, take two stones of weight x and y each time. If x=y, both stones will be crushed; if x<y, the two stones will become one weight of yx Stone, find the minimum weight of the last remaining stone. It is easy to think that the minimum value is a non-negative number, the minimum is 0
  2. Problem conversion : Divide a pile of stones into two piles, and find the minimum weight difference between the two piles of stones (explain in detail: each time you get two stones, throw one at the same time, and finally you can get two piles of stones. The weights of the two piles of stones are respectively x, y, if x=y, the two piles of stones are crushed; if x<y, the two piles of stones become a stone with a weight of yx, find the minimum value of the weight difference between the two piles of stones)
  3. Continue to convert : the total weight sum of this pile of stones remains unchanged. The most perfect situation is that the weights of the two piles of stones are the same, and the offset is 0. To make the weight of the two piles of stones as equal as possible, it is necessary to make the weight of the first pile of stones as close to half the total weight as possible, which is sum/2. This sum/2 is the maximum capacity of the backpack, and the selected items are all the stones, and the weight of each stone is the value of the item.
  4. Continue to convert to 01 knapsack maximum value problem : There is a knapsack with a maximum weight of sum/2, and you are given a bunch of stones with different weights. Find the maximum weight of stones that can be loaded without exceeding the maximum weight of the knapsack. ?
  5. Calculate the answer : Assume 4. The answer obtained is maxWeight, then return to the original question, the weight of the first pile of stones is maxWeight, and the weight of the second pile of stones is sum-maxWeight. The weight of the second pile must be greater than or equal to the first pile, so the weight difference between the two piles of stones is (sum-maxWeight)-maxWeight.
    To simplify, the answer is sum-2*maxWeight, and maxWeight can be abstracted into the 01 most value knapsack problem for solving.
  • Code implementation (java)
    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] stones = new int[n];
        for (int i = 0; i < stones.length; i++) {
    
    
            stones[i] = scanner.nextInt();
        }
        System.out.println(lastStoneWeightII(stones));
    }

    // 动规
    public static int lastStoneWeightII(int[] stones) {
    
    
        // 非显式的背包最大容量,需要计算
        int sum = 0;
        for (int i = 0; i < stones.length; i++) {
    
    
            sum += stones[i];
        }
        int maxWeight = sum/2; // 背包可以承受的最大重量
        
        // 把石头分成两堆,计算第一堆石头不超出sum/2的最大重量
        // dp表示i个石头时,最大容量为j时,背包最多可以装的重量
        int[] dp = new int[maxWeight+1];
        for (int i = 0; i < stones.length; i++) {
    
    
            // 注意!!用一维数组时,内循环必须倒序,否则状态转移方程用到的dp[j-stones[i]]已经被覆盖掉了
            for (int j = maxWeight; j > 0; j--) {
    
    
                // 边界,第一行,只有一个石头
                if (i == 0 && j >= stones[i]) {
    
    
                    dp[j] = stones[i];
                }
                // 有两个及以上石头
                else {
    
    
                    if (j >= stones[i]) {
    
    
               			// 两种方案(拿石头i或者不拿石头i)取最大重量
                        dp[j] = Math.max(dp[j], stones[i] + dp[j-stones[i]]);
                    }
                }
            }
        }
        // 计算两堆石头的差值,即答案
        return sum-2*dp[maxWeight];
    }

2. Interval dynamic programming

1 problem solving template

The interval DP actually seeks the optimal value in an interval.
Generally, when setting the state of this kind of problem, you can set f[i][j] as the optimal value of interval ij and
f[i][j ], which is obtained by merging two small intervals. In order to divide these two smaller intervals, we need to use a loop variable k to enumerate, and the general state transition equation is:
f [i][j] = max/min (f[i][j], f[i][k]+f[k][j]+something) We need to adapt according to the actual meaning of
this topic .The
approximate template of the interval dp is:

for (int len=2;len<=n;len++)
    for (int i=1;i+len-1<=n;i++)
    {
    
    
        int j=i+len-1;
        for (int k=i;k<=j;k++)
            f[i][j]=max/min(f[i][j],f[i][k]+f[k][j]+something)
    }

len enumerates the length of the interval, i and j are the starting point and end point of the interval, and k is used to divide the interval.

2 Example Analysis

Niuke. Shizi Merger

  • topic description

Please add a picture description

  • convert problem to interval dp
  1. Suppose there are 5 piles of sand, and the weight of the sand is represented by the array nums=[1, 3, 4, 2, 5]. Now the minimum combined cost of these 5 piles of sand is required (the answer is 34). The following two merging schemes are given for comparison
    insert image description here
    , and the calculation process of the final total cost is obtained backwards, and the rule is found :
    the minimum merging cost of 5 piles of sand = the sum of the weights of 5 piles of sand + the minimum merging cost of the two sub-heaps merged last time
  2. An example to explain the law : For the first scheme, we can imagine that there is a dividing line between 2 and 5, which divides the 5 piles of sand into two parts. Then the minimum merge cost of 5 piles of sand (nums=[1, 3, 4, 2, 5]) = (1+3+4+2+5) + 4 piles of sand (nums=[1, 3, 4, 2 ]) + the minimum combined cost of 1 pile of sand (nums=[5])
    But in fact, we can split in half from 2 and 5 (that is, the first scheme), or from 4 and 2 Divided into two halves (that is, the second scheme), it can also be divided into two halves from 3 and 4, and so on. . . So we need to enumerate all the situations that can divide 5 piles of sand into two halves, and then use the rules we summarized to calculate the combined cost of each situation, and then take the minimum. (The concept of interval is reflected here, using a pointer to divide an entire array into two intervals)
  3. Imagine recursion : For this rule, "the minimum merge cost of the two sub-heaps merged last time" can be calculated by recursion, which is equivalent to the entry of recursion. The recursive exit is the boundary, and there are two boundaries here:
    boundary 1: there is only 1 pile of sand, and the merging cost is 0;
    Boundary 2: there are only 2 piles of sand, and the merging cost is the sum of the weights of the 2 piles of sand;
  4. Example to explain recursion : For the example in 2., the minimum combined cost of 4 piles of sand can be recursively entered and calculated in the same way. If the pointer divides the 4 piles of sand into 1, 3 and 4, 2 piles, then the minimum merge cost of 4 piles of sand (nums=[1, 3, 4, 2]) = (1+3+4+2)+ The minimum merge cost of 2 piles of sand (nums=[1, 3]) + the minimum merge cost of 2 piles of sand (nums=[4, 2]), and 2 piles of sand are recursive exits, which can be calculated directly with nums
  5. Change the recursion into a dynamic rule : the recursive calculation is from the outside to the inside, entering the depth of the recursion layer by layer, and only when it encounters the exit will it be calculated layer by layer to the outermost layer, and there will be many repeated calculations. The dynamic rule is actually the idea of ​​sacrificing space for time, calculating from the inside out, filling in the form while calculating, and avoiding a lot of double calculation.
    Therefore, the calculation of the dynamic gauge starts from the boundary, that is, first fill the minimum combined cost of 1 pile of sand and 2 piles of sand, and calculate the minimum combined cost of 3 piles of sand based on 1 pile and 2 piles, then 4 piles, then 5 piles...
  6. The complete process of the dynamic gauge :
    calculate the sum in advance : sum represents the sum of the weights of the sub-pile sands, for example sum[2][4] represents the sum of the weights of the sub-pile sands nums=[4,2,5]
0 1 2 3 4
0 0 4 8 10 15
1 - 0 7 9 14
2 - - 0 6 11
3 - - - 0 7
4 - - - - 0

Initialize dp : the two-dimensional array dp of the dynamic gauge, which represents the minimum merge cost of the sub-pile sand, for example, dp[2][4] represents the minimum merge cost of the sub-pile sand nums=[4,2,5].
If the diagonal line is 1 pile of sand, and the sub-diagonal line is 2 piles of sand, the remaining numbers are all super-large numbers (because the minimum cost is sought, if the maximum cost is sought, the remainder should be all ultra-small numbers, which is convenient Compare)

0 1 2 3 4
0 0 4 max max max
1 - 0 8 max max
2 - - 0 7 max
3 - - - 0 6
4 - - - - 0

Fill in the table with the state transition equation :
fill in the table from bottom to top and from left to right, dp[0][4] in the upper right corner of the dp array indicates the minimum merge cost of nums=[1,3,4,2,5], This is the answer we want.
Use i and j to represent the two sides of the sub-array, use k to represent the pointer that can divide the sub-array into two intervals, enumerate all the cases of k, calculate the combined cost, and take the minimum. The state transition equation is as follows:
dp[i][j] = min (dp[i][j], sum[i][j] + dp[i][k] + dp[k+1][j])

0 1 2 3 4
0 0 4 12 20 34
1 - 0 7 15 28
2 - - 0 6 17
3 - - - 0 7
4 - - - - 0
  • Code implementation (java)
    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        int N = scanner.nextInt();
        int[] nums = new int[N];
        for (int i = 0; i < nums.length; i++) {
    
    
            nums[i] = scanner.nextInt();
        }
        System.out.println(stonesCombine(N, nums));

    }

    static int stonesCombine(int N,int[] nums) {
    
    
        if (N == 0) {
    
    
            return -1; // 边界,0堆沙子
        }
        int[][] dp = new int[N][N]; // 从i到j的子数组的最小代价
        int[][] sum = new int[N][N]; // 从i到j的子数组的总代价
        // 初始化dp全为最大值,斜对角线全为0,副对角线全为两堆沙子之和
        for (int i = 0; i < N; i++) {
    
    
            for (int j = 0; j < N; j++) {
    
    
                if (i == j) {
    
    
                    dp[i][j] = 0; // 边界1,只有1堆沙子
                }
                else if (i+1 == j) {
    
    
                    dp[i][j] = nums[i] + nums[j]; // 边界2,只有2堆沙子
                }
                else {
    
    
                    dp[i][j] = Integer.MAX_VALUE; // 求最小值,初始化为最大值
                }
            }
        }
        // 计算sum
        for (int i = 0; i < N; i++) {
    
    
            for (int j = i+1; j < N; j++) {
    
    
                if (j == i+1) {
    
    
                    sum[i][j] = nums[i] + nums[j]; // 特殊情况,2堆沙子,1堆沙子总代价为0
                }
                else {
    
    
                    sum[i][j] = sum[i][j-1] + nums[j];
                }
            }
        }
        // 计算dp剩余部分,从下到上,从左到右
        for (int i = N-3; i >= 0; i--) {
    
    
            for (int j = i+2; j < N; j++) {
    
    
                // 枚举所有指针分割成两个区间的情况,取最小
                for (int k = i; k < j; k++) {
    
    
                    dp[i][j] = Math.min(dp[i][j], sum[i][j]+dp[i][k]+dp[k+1][j]);
                }
            }
        }
        // dp右上角即答案
        return dp[0][N-1];
    }
  • Advanced
    The space complexity can be further optimized, and the sum array representing the sum of the weights of the sub-arrays can be one-dimensional

Summary and Analysis

  1. Dynamic programming is actually an idea of ​​sacrificing space for time, which is equivalent to recording the results of recursion and avoiding repeated calculations
  2. The difficulty of the knapsack problem is to be able to see whether a problem has the characteristics of the knapsack problem, and abstract it into a knapsack problem to solve
  3. The first step of filling the dynamic gauge group is to fill in the boundary first, and calculate the rest according to the boundary and the state transition equation. The boundary here is actually equivalent to the exit of recursion, and the state transition equation is equivalent to the entrance of recursion.
  4. The calculation of recursion is from the outside to the inside, and the calculation of the dynamic rule is from the inside to the outside

Reference URL

Guess you like

Origin blog.csdn.net/weixin_46838605/article/details/130422867