动态规划(Dynamic Programming)

动态规划(Dynamic Programming)

  • 动态规划(Dynamic Programming),简称 DP
    • 是求解最优化问题的一种常用策略
  • 通常的使用套路(一步一步优化):
    • ① 暴力递归(自顶向下,出现了重叠子问题)
  • ② 记忆化搜索(自顶向下
  • ③ 递推(自底向上
  • 直接学习动态规划的概念有点难以理解,先理解透一道例题再学习概念

练习1:找零钱

leetcode_322_零钱兑换:https://leetcode-cn.com/problems/coin-change/

  • 假设有25分、20分、5分、1分的硬币,现要找给客户41分的零钱,如何办到硬币个数最少?
    • 此前用贪心策略得到的并非是最优解(贪心得到的解是 5 枚硬币:25、5、5、5、1)
  • 假设 dp( n ) 是凑到 n 分需要的最少硬币个数
    • 如果第 1 次选择了 25 分的硬币,那么 dp( n ) = dp( n - 25 ) + 1
    • 如果第 1 次选择了 20 分的硬币,那么 dp( n ) = dp( n - 20 ) + 1
    • 如果第 1 次选择了 5 分的硬币,那么 dp( n ) = dp( n - 5 ) + 1
    • 如果第 1 次选择了 1 分的硬币,那么 dp( n ) = dp( n - 1 ) + 1
    • 所以 dp( n ) = min { dp( n - 25 ), dp( n - 20 ), dp( n - 5 ), dp( n - 1 ) } + 1

找零钱 - 暴力递归

/**
 * 暴力递归(自顶向下的调用, 出现了重叠子问题)
 *
 * @param n
 * @return
 */
static int coins(int n) {

    //递归基
    if (n < 1) {
        return Integer.MAX_VALUE;
    }
    if (n == 25 || n == 20 || n == 5 || n == 1) {
        return 1;
    }

    // 求出四种取法的最小值
    int min1 = Math.min(coins(n - 1), coins(n - 5));
    int min2 = Math.min(coins(n - 20), coins(n - 25));
    return Math.min(min1, min2) + 1;
}

找零钱 - 记忆化搜索

/**
 * 记忆化搜索(自顶向下的调用)
 *
 * @param n
 * @return
 */
static int coins2(int n) {
    if (n < 1) {
        return -1; //处理非法数据
    }
    int[] dp = new int[n + 1];
    int[] faces = {1, 5, 20, 25}; // 给定的面值数组
    for (int face : faces) { // 如果我要凑的钱是20元, 那么我肯定用不到25元面值
        if (face > n) {
            break; // 用不到的面值不用初始化
        }
        dp[face] = 1; // 初始化可能用到的面值
    }
    return coins2(n, dp);
}

static int coins2(int n, int[] dp) {
    // 递归基
    if (n < 1) {
        return Integer.MAX_VALUE;
    }
    if (dp[n] == 0) { // 记忆化搜索, dp[n] == 0 表示以前没有算过, 那便初始化一下
        int min1 = Math.min(coins2(n - 1, dp), coins2(n - 5, dp));
        int min2 = Math.min(coins2(n - 20, dp), coins2(n - 25, dp));
        dp[n] = Math.min(min1, min2) + 1;
    }
    return dp[n];
}

找零钱 - 递推

/**
 * 递推(自底向上)
 *
 * @param n
 * @return
 */
static int coins3(int n) {
    if (n < 1) {
        return -1;
    }
    int[] dp = new int[n + 1];
    for (int i = 1; i <= n; i++) {
        int min = Integer.MAX_VALUE;
        if (i >= 1) {
            min = Math.min(dp[i - 1], min);
        }
        if (i >= 5) {
            min = Math.min(dp[i - 5], min);
        }
        if (i >= 20) {
            min = Math.min(dp[i - 20], min);
        }
        if (i >= 25) {
            min = Math.min(dp[i - 25], min);
        }
        dp[i] = min + 1;
    }
    return dp[n];
}
  • 可以修改一下写法:
/**
     * 递推(自底向上)
     *
     * @param n
     * @return
     */
    static int coins3(int n) {
        if (n < 1) {
            return -1;
        }
        int[] dp = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            int min = dp[i - 1]; // 由于下面两行是必然执行的, 直接这么写就行了
//            if (i >= 1) {
//                min = Math.min(dp[i - 1], min);
//            }
            if (i >= 5) {
                min = Math.min(dp[i - 5], min);
            }
            if (i >= 20) {
                min = Math.min(dp[i - 20], min);
            }
            if (i >= 25) {
                min = Math.min(dp[i - 25], min);
            }
            dp[i] = min + 1;
        }
        return dp[n];
    }

思考题:输出找零钱的具体方案(具体是用了哪些面值的硬币)

static int coins4(int n) {
    if (n < 1) {
        return -1;
    }
    int[] dp = new int[n + 1];
    // faces[i]是凑够i分时最后选择的那枚硬币的面值
    int[] faces = new int[dp.length]; // 存放硬币面值(为了输出)
    for (int i = 1; i <= n; i++) {
        int min = Integer.MAX_VALUE;
        if (i >= 1 && dp[i - 1] < min) {
            min = dp[i - 1];
            faces[i] = 1;
        }
        if (i >= 5 && dp[i - 5] < min) {
            min = dp[i - 5];
            faces[i] = 5;
        }
        if (i >= 20 && dp[i - 20] < min) {
            min = dp[i - 20];
            faces[i] = 20;
        }
        if (i >= 25 && dp[i - 25] < min) {
            min = dp[i - 25];
            faces[i] = 25;
        }
        dp[i] = min + 1;
        print(faces, i); // 打印凑够面值 1 ~ n 的方案
    }
    // print(faces, n); // 打印凑够面值 n 的方案
    return dp[n];
}

// 打印凑够面值 n 的方案
static void print(int[] faces, int n) {
    System.out.print("[" + n + "] = ");
    while (n > 0) {
        System.out.print(faces[n] + " ");
        n -= faces[n];
    }
    System.out.println();
}
  • 尝试打印了 n = 41 的情况,打印出了凑够 1~41 所有面值的情况:
[1] = 1 
[2] = 1 1 
[3] = 1 1 1 
[4] = 1 1 1 1 
[5] = 5 
[6] = 1 5 
[7] = 1 1 5 
[8] = 1 1 1 5 
[9] = 1 1 1 1 5 
[10] = 5 5 
[11] = 1 5 5 
[12] = 1 1 5 5 
[13] = 1 1 1 5 5 
[14] = 1 1 1 1 5 5 
[15] = 5 5 5 
[16] = 1 5 5 5 
[17] = 1 1 5 5 5 
[18] = 1 1 1 5 5 5 
[19] = 1 1 1 1 5 5 5 
[20] = 20 
[21] = 1 20 
[22] = 1 1 20 
[23] = 1 1 1 20 
[24] = 1 1 1 1 20 
[25] = 25 
[26] = 1 25 
[27] = 1 1 25 
[28] = 1 1 1 25 
[29] = 1 1 1 1 25 
[30] = 5 25 
[31] = 1 5 25 
[32] = 1 1 5 25 
[33] = 1 1 1 5 25 
[34] = 1 1 1 1 5 25 
[35] = 5 5 25 
[36] = 1 5 5 25 
[37] = 1 1 5 5 25 
[38] = 1 1 1 5 5 25 
[39] = 1 1 1 1 5 5 25 
[40] = 20 20 
[41] = 1 20 20 
3

找零钱 - 通用实现

static int coins5(int n, int[] faces) {
    if (n < 1 || faces == null || faces.length == 0) {
        return -1;
    }
    int[] dp = new int[n + 1];
    for (int i = 1; i <= n; i++) {
        int min = Integer.MAX_VALUE;
        for (int face : faces) {
            // 假如给我的面值是20, 要凑的是15, 则跳过此轮循环
            if (i < face) { // 如果给我的面值比我要凑的面值还大, 跳过此轮循环
                continue;
            }
            min = Math.min(dp[i - face], min);
        }
        dp[i] = min + 1;
    }
    return dp[n];
}
  • 改进,如果不能凑成则返回 -1:
static int coins5(int n, int[] faces) {
    if (n < 1 || faces == null || faces.length == 0) {
        return -1;
    }
    int[] dp = new int[n + 1];
    for (int i = 1; i <= n; i++) {
        int min = Integer.MAX_VALUE;
        for (int face : faces) {
            // 假如给我的面值是20, 要凑的是15, 则跳过此轮循环
            if (face > i) {
                continue; // 如果给我的面值比我要凑的面值还大, 跳过此轮循环
            }
            // 比如给的面值是{4}, 要凑的是6, 先给出一张4, 再看6-4=2, 是否能凑成
            // 2无法凑成, 则跳过此轮循环
            int v = dp[i - face];
            if (v < 0 || v >= min) {
                continue;
            }
            min = v;
        }
        // 说明上面的循环中每次都是continue, 要凑的面值比给定的所有面值小
        if (min == Integer.MAX_VALUE) {
            dp[i] = -1;
        } else {
            dp[i] = min + 1;
        }
    }
    return dp[n];
}

动态规划(Dynamic Programming)

动态规划的常规步骤

  • 动态规划,简称 DP
    • 是求解最优化问题的一种常用策略
  • 通常的使用套路(一步一步优化):
    • ① 暴力递归(自顶向下,出现了重叠子问题)
    • ② 记忆化搜索(自顶向下
    • ③ 递推(自底向上
  • 动态规划中的 “动态” 可以理解为是 “会变化的状态”
    • ① 定义状态(状态是原问题、子问题的解)
      • 比如定义 dp( i ) 的含义
    • ② 设置初始状态(边界)
      • 比如设置 dp( 0 ) 的值
    • ③ 确定状态转移方程
      • 比如确定 dp( i ) 和 dp( i - 1 ) 的关系

动态规划的一些概念

维基百科的解释

Dynamic Programming is a method for solving a complex problem by breaking it down into a collection of simpler subproblems, solving each of those subproblems just once, and storing their solutions.

  • ① 将复杂的原问题拆解成若干个简单的子问题
  • ② 每个子问题仅仅解决1次,并保存它们的解
  • ③ 最后推导出原问题的解
  • 可以用动态规划来解决的问题,通常具备2个特点:
    • 最优子结构最优化原理):通过求解子问题的最优解,可以获得原问题的最优解
  • 无后效性
    • 某阶段的状态一旦确定,则此后过程的演变不再受此前各状态及决策的影响(未来与过去无关
  • 在推导后面阶段的状态时,只关心前面阶段的具体状态值,不关心这个状态是怎么一步步推导出来的

有后效性与无后效性

  • 首先了解一下什么是有后效性

Untitled

  • 然后再去理解什么是无后效性

Untitled

练习2:最大连续子序列和

题目:给定一个长度为 n 的整数序列,求它的最大连续子序列和

比如 –2、1、–3、4、–1、2、1、–5、4 的最大连续子序列和是 4 + (–1) + 2 + 1 = 6

  • 状态定义:
    • 假设 dp(i) 是以 nums[i] 结尾的最大连续子序列和(nums是整个序列)
      • nums[0] –2 结尾的最大连续子序列是 –2,所以 dp(0) = –2
      • nums[1] 1 结尾的最大连续子序列是 1,所以 dp(1) = 1
      • nums[2] –3 结尾的最大连续子序列是 1、–3,所以 dp(2) = dp(1) + (–3) = –2
      • nums[3] 4 结尾的最大连续子序列是 4,所以 dp(3) = 4
      • nums[4] –1 结尾的最大连续子序列是 4、–1,所以 dp(4) = dp(3) + (–1) = 3
      • nums[5] 2 结尾的最大连续子序列是 4、–1、2,所以 dp(5) = dp(4) + 2 = 5
      • nums[6] 1 结尾的最大连续子序列是 4、–1、2、1,所以 dp(6) = dp(5) + 1 = 6
      • nums[7] –5 结尾的最大连续子序列是 4、–1、2、1、–5,所以 dp(7) = dp(6) + (–5) = 1
      • nums[8] 4 结尾的最大连续子序列是 4、–1、2、1、–5、4,所以 dp(8) = dp(7) + 4 = 5

状态转移方程和初始状态

  • 状态转移方程:
    • 如果 dp(i – 1) ≤ 0,那么 dp(i) = nums[i]
    • 如果 dp(i – 1) > 0,那么 dp(i) = dp(i – 1) + nums[i]
  • 初始状态:
    • dp(0) 的值是 nums[0]
  • 最终的解:
    • 最大连续子序列和是所有 dp(i) 中的最大值 max { dp(i) },i ∈ [0, nums.length)

最大连续子序列和 – 实现

static int maxSubArray(int[] nums) {
    if (nums == null || nums.length == 0) {
        return 0;
    }
    int[] dp = new int[nums.length];
    dp[0] = nums[0];
    int max = dp[0];
    for (int i = 1; i < nums.length; i++) {
        if (dp[i - 1] <= 0) {
            dp[i] = nums[i];
        } else {
            dp[i] = dp[i - 1] + nums[i];
        }
        max = Math.max(dp[i], max);
    }
    return max;
}

最大连续子序列和 – 优化

static int maxSubArray2(int[] nums) {
    if (nums == null || nums.length == 0) {
        return 0;
    }
    int dp = nums[0];
    int max = dp;
    for (int i = 1; i < nums.length; i++) {
        if (dp <= 0) {
            dp = nums[i];
        } else {
            dp = dp + nums[i];
        }
        max = Math.max(dp, max);
    }
    return max;
}

练习3:最长上升子序列(LIS)

最长上升子序列最长递增子序列,Longest Increasing Subsequence,LIS)

leetcode_300_最长上升子序列: https://leetcode-cn.com/problems/longest-increasing-subsequence/

  • 题目:给定一个无序的整数序列,求出它最长上升子序列的长度(要求严格上升)
    • 比如 [10, 2, 2, 5, 1, 7, 101, 18] 的最长上升子序列是 [2, 5, 7, 101]、[2, 5, 7, 18],长度是 4
  • 假设数组是 nums, [10, 2, 2, 5, 1, 7, 101, 18]
    • dp(i) 是以 nums[i] 结尾的最长上升子序列的长度,i ∈ [0, nums.length)
      • nums[0] 10 结尾的最长上升子序列是 10,所以 dp(0) = 1
  • nums[1] 2 结尾的最长上升子序列是 2,所以 dp(1) = 1
  • nums[2] 2 结尾的最长上升子序列是 2,所以 dp(2) = 1
  • nums[3] 5 结尾的最长上升子序列是 2、5,所以 dp(3) = dp(1) + 1 = dp(2) + 1 = 2
  • nums[4] 1 结尾的最长上升子序列是 1,所以 dp(4) = 1
  • nums[5] 7 结尾的最长上升子序列是 2、5、7,所以 dp(5) = dp(3) + 1 = 3
  • nums[6] 101 结尾的最长上升子序列是 2、5、7、101,所以 dp(6) = dp(5) + 1 = 4
  • nums[7] 18 结尾的最长上升子序列是 2、5、7、18,所以 dp(7) = dp(5) + 1 = 4
  • 最长上升子序列的长度是所有 dp(i) 中的最大值 max { dp(i) },i ∈ [0, nums.length)
  • 状态转移方程

Untitled

  • 状态的初始值
    • dp(0) = 1
    • 所有的 dp(i) 默认都初始化为 1
  • 最终的解
    • 最长上升子序列的长度是所有 dp(i) 中的最大值 max { dp(i) },i ∈ [0, nums.length)

最长上升子序列 – 实现

static int lengthOfLIS(int[] nums) {
    if (nums == null || nums.length == 0) {
        return 0;
    }
    int[] dp = new int[nums.length];
    int max = dp[0] = 1; // 只有一个元素则长度为1
    for (int i = 1; i < dp.length; i++) {
        dp[i] = 1; // 默认只有一个元素时长度为1
        for (int j = 0; j < i; j++) {
            // 无法成为一个上升子序列
            if (nums[i] <= nums[j]) {
                continue;
            }
            dp[i] = Math.max(dp[i], dp[j] + 1);
        }
        max = Math.max(dp[i], max);
    }
    return max;
}

最长上升子序列 – 二分搜索实现

Untitled

思路

Untitled

练习4 – 最长公共子序列(LCS)

最长公共子序列(Longest Common Subsequence,LCS)

leetcode_1143_最长公共子序列:https://leetcode-cn.com/problems/longest-common-subsequence/

  • 题目:求两个序列的最长公共子序列长度
    • [1, 3, 5, 9, 10] 和 [1, 4, 9, 10] 的最长公共子序列是 [1, 9, 10],长度为 3
    • ABCBDAB 和 BDCABA 的最长公共子序列长度是 4,可能是
      • ABCBDAB 和 BDCABA > BDAB
      • ABCBDAB 和 BDCABA > BDAB
      • ABCBDAB 和 BDCABA > BCAB
      • ABCBDAB 和 BDCABA > BCBA

思路

Untitled

最长公共子序列 – 递归实现

/**
 * 递归实现
 *
 * @param nums1
 * @param nums2
 * @return
 */
static int longestCommonSubsequence1(int[] nums1, int[] nums2) {
    if (nums1 == null || nums1.length == 0) {
        return 0;
    }
    if (nums2 == null || nums2.length == 0) {
        return 0;
    }
    return lcs(nums1, nums1.length, nums2, nums2.length);
}

/**
 * 求nums1前i个元素和nums2前j个元素的最长公共子序列长度
 *
 * @param nums1
 * @param i
 * @param nums2
 * @param j
 * @return
 */
static int lcs1(int[] nums1, int i, int[] nums2, int j) {
    if (i == 0 || j == 0) {
        return 0;
    }
    if (nums1[i - 1] == nums2[j - 1]) {
        return lcs1(nums1, i - 1, nums2, j - 1) + 1;
    }
    return Math.max(lcs1(nums1, i - 1, nums2, j),
            lcs1(nums1, i, nums2, j - 1));
}

Untitled

最长公共子序列 – 非递归实现

Untitled

/**
 * 非递归实现
 *
 * @param nums1
 * @param nums2
 * @return
 */
static int longestCommonSubsequence2(int[] nums1, int[] nums2) {
    if (nums1 == null || nums1.length == 0) {
        return 0;
    }
    if (nums2 == null || nums2.length == 0) {
        return 0;
    }
    int[][] dp = new int[nums1.length + 1][nums2.length + 1];
    for (int i = 1; i <= nums1.length; i++) {
        for (int j = 1; j <= nums2.length; j++) {
            if (nums1[i - 1] == nums2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[nums1.length][nums2.length];
}

最长公共子序列 – 滚动数组优化

  • 可以使用滚动数组化空间复杂度
/**
 * 非递归实现 - 滚动数组
 *
 * @param nums1
 * @param nums2
 * @return
 */
static int longestCommonSubsequence3(int[] nums1, int[] nums2) {
    if (nums1 == null || nums1.length == 0) {
        return 0;
    }
    if (nums2 == null || nums2.length == 0) {
        return 0;
    }
    int[][] dp = new int[2][nums2.length + 1];
    for (int i = 1; i <= nums1.length; i++) {
        int row = i & 1; // 相当于 i % 2
        int preRow = (i - 1) & 1; // 相当于 (i - 1) % 2
        for (int j = 1; j <= nums2.length; j++) {
            if (nums1[i - 1] == nums2[j - 1]) {
                dp[row][j] = dp[preRow][j - 1] + 1;
            } else {
                dp[row][j] = Math.max(dp[preRow][j], dp[row][j - 1]);
            }
        }
    }
    return dp[nums1.length & 1][nums2.length]; // 相当于 nums1.length % 2
}

最长公共子序列 – 一维数组优化

/**
 * 非递归实现 - 一维数组
 *
 * @param nums1
 * @param nums2
 * @return
 */
static int longestCommonSubsequence4(int[] nums1, int[] nums2) {
    if (nums1 == null || nums1.length == 0) {
        return 0;
    }
    if (nums2 == null || nums2.length == 0) {
        return 0;
    }
    int[] dp = new int[nums2.length + 1];
    for (int i = 1; i <= nums1.length; i++) {
        int cur = 0;
        for (int j = 1; j <= nums2.length; j++) {
            int leftTop = cur;
            cur = dp[j];
            if (nums1[i - 1] == nums2[j - 1]) {
                dp[j] = leftTop + 1;
            } else {
                dp[j] = Math.max(dp[j], dp[j - 1]);
            }
        }
    }
    return dp[nums2.length];
}

练习5 – 最长公共子串

  • 最长公共子串(Longest Common Substring)
    • 子串是连续的子序列
  • 题目:求两个字符串的最长公共子串长度
    • ABCBA 和 BABCA 的最长公共子串是 ABC,长度为 3
  • 思路:

Untitled

最长公共子串 – 实现

Untitled

static int lcs(String str1, String str2) {
    if (str1 == null || str2 == null) {
        return 0;
    }
    char[] nums1 = str1.toCharArray();
    char[] nums2 = str2.toCharArray();
    if (nums1.length == 0) {
        return 0;
    }
    if (nums2.length == 0) {
        return 0;
    }
    int[][] dp = new int[nums1.length + 1][nums2.length + 1];
    int max = 0;
    for (int i = 1; i <= nums1.length; i++) {
        for (int j = 1; j <= nums2.length; j++) {
            if (nums1[i - 1] != nums2[j - 1]) {
                continue;
            }
            dp[i][j] = dp[i - 1][j - 1] + 1;
            max = Math.max(dp[i][j], max);
        }
    }
    return max;
}

最长公共子串 – 一维数组优化

/**
 * row:行
 * col:列
 *
 * @param str1
 * @param str2
 * @return
 */
static int lcs2(String str1, String str2) {
    if (str1 == null || str2 == null) {
        return 0;
    }
    char[] nums1 = str1.toCharArray();
    char[] nums2 = str2.toCharArray();
    if (nums1.length == 0) {
        return 0;
    }
    if (nums2.length == 0) {
        return 0;
    }
    char[] rowsChars = nums1, colsChars = nums2;
    //以最长的那个字符串作为列
    if (nums1.length < nums2.length) {
        colsChars = nums1;
        rowsChars = nums2;
    }
    int[] dp = new int[rowsChars.length + 1];
    int max = 0;
    for (int row = 1; row <= rowsChars.length; row++) {
        int cur = 0;
        for (int col = 1; col <= colsChars.length; col++) {
            int leftTop = cur;
            cur = dp[col];
            if (nums1[row - 1] != nums2[col - 1]) {
                dp[col] = 0;
            } else {
                dp[col] = leftTop + 1;
                max = Math.max(dp[col], max);
            }
        }
    }
    return max;
}

练习6 – 0-1背包

Untitled

0-1背包 – 实现

static int maxValue(int[] values, int[] weights, int capacity) {
    if (values == null || values.length == 0) {
        return 0;
    }
    if (weights == null || weights.length == 0) {
        return 0;
    }
    if (values.length != weights.length || capacity <= 0) {
        return 0;
    }
    // 特征方程: dp(i, j) 是最大承重为 j、有前 i 件物品可选时的最大总价值;
    int[][] dp = new int[values.length + 1][capacity + 1];
    for (int i = 1; i <= values.length; i++) {
        for (int j = 1; j <= capacity; j++) {
            if (j < weights[i - 1]) { //如果背包的最大承重小于最后一件物品的重量
                dp[i][j] = dp[i - 1][j]; //没法选择最后一件物品
            } else {
                //有两种选择:
                //①不选择最后一件物品:dp[i-1][j]
                //②选择最后一件物品:values[i - 1] + dp[i - 1][j - weights[i - 1]
                dp[i][j] = Math.max(dp[i - 1][j], values[i - 1] + dp[i - 1][j - weights[i - 1]]);
            }
        }
    }
    return dp[values.length][capacity];
}
public static void main(String[] args) {
    int[] values = {6, 3, 5, 4, 6};
    int[] weights = {2, 2, 6, 5, 4};
    /**
     * dp(3,7) 是最大承重为7,有前3件物品可选时的最大价值
     * dp(4,8) 是最大承重为8,有前4件物品可选时的最大价值
     * dp(n,capacity) 是最大承重为capacity,有前n件物品可选时的最大价值
     *
     * i == 5
     * j == 10
     * ①如果 j < weights[i],dp(i, j) = dp(i - 1, j)
     * 如果 j >= weights[i],那么有下面两种情况
     * ②如果不选择第i个物品,dp(i, j) = dp(i - 1, j)
     * ③如果选择第i个物品,dp(i, j) = values[i - 1] + dp(i - 1,j - weights[i - 1])
     * dp(i ,j) = Maths.max( dp(i - 1, j), values[i - 1] + dp(i - 1,j - weights[i - 1]) );
     */
    int capacity = 10;
    System.out.println(maxValue(values, weights, capacity));
}

0-1背包 – 一维数组

Untitled

/**
 * 一维数组
 *
 * @param values
 * @param weights
 * @param capacity
 * @return
 */
static int maxValue2(int[] values, int[] weights, int capacity) {
    if (values == null || values.length == 0) {
        return 0;
    }
    if (weights == null || weights.length == 0) {
        return 0;
    }
    if (values.length != weights.length || capacity <= 0) {
        return 0;
    }
    int[] dp = new int[capacity + 1];
    for (int i = 1; i <= values.length; i++) {
        for (int j = capacity; j >= 1; j--) {
            if (j < weights[i - 1]) {
                continue;
            }
            dp[j] = Math.max(dp[j], values[i - 1] + dp[j - weights[i - 1]]);
        }
    }
    return dp[capacity];
}

0-1背包 – 一维数组优化

/**
 * 一维数组 - 优化
 *
 * @param values
 * @param weights
 * @param capacity
 * @return
 */
static int maxValue3(int[] values, int[] weights, int capacity) {
    if (values == null || values.length == 0) {
        return 0;
    }
    if (weights == null || weights.length == 0) {
        return 0;
    }
    if (values.length != weights.length || capacity <= 0) {
        return 0;
    }
    int[] dp = new int[capacity + 1];
    for (int i = 1; i <= values.length; i++) {
        for (int j = capacity; j >= weights[i - 1]; j--) {
            dp[j] = Math.max(dp[j], values[i - 1] + dp[j - weights[i - 1]]);
        }
    }
    return dp[capacity];
}

0-1背包 – 恰好装满 – 实现

Untitled

Untitled

/**
 * 01背包问题 - 恰好装满
 *
 * @param values
 * @param weights
 * @param capacity
 * @return 如果返回-1,代表没法刚好凑到capacity这个容量
 */
static int maxValueExactly(int[] values, int[] weights, int capacity) {
    if (values == null || values.length == 0) {
        return 0;
    }
    if (weights == null || weights.length == 0) {
        return 0;
    }
    if (values.length != weights.length || capacity <= 0) {
        return 0;
    }
    int[] dp = new int[capacity + 1];
    for (int j = 1; j <= capacity; j++) {
        dp[j] = Integer.MIN_VALUE;
    }
    for (int i = 1; i <= values.length; i++) {
        for (int j = capacity; j >= weights[i - 1]; j--) {
            dp[j] = Math.max(dp[j], values[i - 1] + dp[j - weights[i - 1]]);
        }
    }
    return dp[capacity] < 0 ? -1 : dp[capacity];
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿小羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值