leetcode 416. 分割等和子集 中等 动态规划

题目:
leetcode 416. 分割等和子集 中等 动态规划_第1张图片
这里我直接引用别人的题解,讲动态规划讲的很好
动态规划的一般思考方向
1、状态定义;
2、状态转移方程;
3、初始化;
4、输出;
5、思考状态压缩。
这 5 个部分是本题解的结构。其它类似的动态规划问题也可以按照这样的方向去思考、解释和理解。

这是一个典型的“动态规划”问题,并且它的“原形”是“0-1 背包问题”。使用“动态规划”解决问题的思路是“以空间换时间”,“规划”这个词在英文中就是“填表格”的意思,代码执行的过程,也可以称之为“填表格”。

“动态规划”的方法可以认为是为我们提供了一个思考问题的方向,我们不是直接面对问题求解,而是去找原始问题(或者说和原始问题相关的问题)的最开始的样子,通过“状态转移方程”(这里没法再解释了,可以结合下文理解)记录下每一步求解的结果,直到最终问题解决。

而直接面对问题求解,就是我们熟悉的“递归”方法,由于有大量重复子问题,我们就需要加缓存,这叫“记忆化递归”,这里就不给参考代码了,感兴趣的朋友可以自己写一下,比较一下它们两种思考方式的不同之处和优缺点。

做这道题需要做这样一个等价转换:是否可以从这个数组中挑选出一些正整数,使得这些数的和等于整个数组元素的和的一半。前提条件是:数组的和一定得是偶数,即数组的和一定得被 22 整除,这一点是特判。

本题与 0-1 背包问题有一个很大的不同,即:

  • 0-1 背包问题选取的物品的容积总量不能超过规定的总量;
  • 本题选取的数字之和需要恰恰好等于规定的和的一半。
    这一点区别,决定了在初始化的时候,所有的值应该初始化为 false。 (《背包九讲》的作者在介绍 0-1 背包问题的时候,有强调过这点区别,我在这里也只是再重复一下。)

作为“0-1 背包问题”,它的特点是:“每个数只能用一次”。思路是:物品一个一个选,容量也一点一点放大考虑(这一点是“动态规划”的思想,特别重要)。

如果在实际生活中,其实我们也是这样做的,一个一个尝试把候选物品放入“背包”,看什么时候能容纳的价值最大。

具体做法是:画一个 len 行,target + 1 列的表格。这里 len 是物品的个数,target 是背包的容量。len 行表示一个一个物品考虑,target + 1多出来的那 1 列,表示背包容量从 0 开始,很多时候,我们需要考虑这个容量为 0 的数值。

状态定义:dp[i][j]表示从数组的 [0, i] 这个子区间内挑选一些正整数,每个数只能用一次,使得这些数的和恰好等于 j。
状态转移方程:很多时候,状态转移方程思考的角度是“分类讨论”,对于“0-1 背包问题”而言就是“当前考虑到的数字选与不选”。
1、不选择 nums[i],如果在 [0, i - 1] 这个子区间内已经有一部分元素,使得它们的和为 j ,那么 dp[i][j] = true;

2、选择 nums[i],如果在 [0, i - 1] 这个子区间内就得找到一部分元素,使得它们的和为 j - nums[i]。

状态转移方程是:

dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i]]

一般写出状态转移方程以后,就需要考虑边界条件(一般而言也是初始化条件)。

1、j - nums[i] 作为数组的下标,一定得保证大于等于 0 ,因此 nums[i] <= j;
2、注意到一种非常特殊的情况:j 恰好等于 nums[i],即单独 nums[j] 这个数恰好等于此时“背包的容积” j,这也是符合题意的。

因此完整的状态转移方程是:
在这里插入图片描述
初始化:dp[0][0] = false,因为是正整数,当然凑不出和为 0。
输出:dp[len - 1][target],这里 len 表示数组的长度,target 是数组的元素之和(必须是偶数)的一半。

代码:

class Solution {
     
    public boolean canPartition(int[] nums) {
     
        if(nums == null || nums.length == 0){
     
            return false;
        }
        int sum = 0;
        for (int num : nums) {
     
            sum += num;
        }
        //如果是奇数,直接返回false即可
        if (sum%2 != 0) {
     
            return false;
        }
        int target = sum / 2;
        //行:nums范围,列:求解目标,目标也包括0
        boolean[][] dp = new boolean[nums.length][target + 1];

        // 第 1 个数只能恰是目标值才有解
        if (nums[0] <= target) {
     
            dp[0][nums[0]] = true;
        }

        // 再求解之后的情况
        for (int i = 1; i < len; i++) {
     
            for (int j = 0; j <= target; j++) {
     
                if (nums[i] == j) {
     
                    dp[i][j] = true;
                    continue;
                }
                if (nums[i] < j) {
     
                    //如果nums[i] < j,那么要么前i-1范围内能凑成target j,要么前i-1范围能凑成target j - nums[i]
                    dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
                }
            }
        }
        return dp[len - 1][target];

    }
}

在这里插入图片描述
在这里插入图片描述
本文分析部分转自链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/

你可能感兴趣的:(算法,动态规划,算法,Java)