算法-Partition Equal Subset Sum(动态规划)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法-Partition Equal Subset Sum(动态规划)相关的知识,希望对你有一定的参考价值。

一直以来,动态规划是我的问题,今天看到了一道动态规划的题,做了好久,也思考了好久,借鉴别人的代码才把这个问题理解到。

  题意:

Given a non-empty array containing only positive integers, find if 
the array can be partitioned into two subsets such that the sum 
of elements in both subsets is equal.

样例:

Given nums = [1, 5, 11, 5], return true
two subsets: [1, 5, 5], [11]

Given nums = [1, 2, 3, 9], return false

基本的意思就是说,给定一个数组,能不能将这个数组分为两个部分,同时这两个部分的和是相同的。

我刚刚看到这个题时,想到的是用回溯法来做,因为这个是回溯法的子集树。但是,超时了!

1.回溯法(超时)

 1     private  boolean flag = false;
 2     public  boolean canPartition(int[] nums) {
 3         Integer target = Arrays.stream(nums).boxed().reduce(0, (a, b) -> a + b);
 4         if(target % 2 != 0){
 5             return false;
 6         }
 7         return backTrack(nums, 0, 0, target / 2);
 8     }
 9 
10     private static boolean backTrack(int nums[], int start, int sum , int target) {
11         if(sum == target){
12             flag  = true;
13         }
14         else
15         {
16             for(int i = start; i < nums.length; i++){
17                 if(sum + nums[i] <= target){
18                     backTrack(nums, i + 1, sum + nums[i], target);
19                 }
20             }
21         }
22         return flag;
23     }

后来一想,这个跟0-1背包很像,0-1背包也是选择一些物品来转,而这里是选择一些数字,保证两个部分的和是相同的。于是照着0-1背包的方法来做。

2.类似于0-1背包的做法

 1      public static boolean canPartition(int[] nums) {
 2          int sum = Arrays.stream(nums).reduce(0, Integer::sum);
 3          if (sum % 2 == 1)
 4              return false;
 5          else {
 6              sum /= 2;
 7              int n = nums.length;
 8              // dp[i][j] 表示 如果我们取前i个数字,且背包容量为j的情况下,最多能放入多少东西
 9              int dp[][] = new int[n][sum + 1];
10              // dp[0][0] 为初始状态,表示,没有任何没有东西没有体积,其余部分初始化
11              for (int i = nums[0]; i <= sum; i++) {
12                  dp[0][i] = nums[0];
13              }
14              // 遍历n个数字,即视为n个产品
15              for (int i = 1; i < n; i++) {
16                  // 加入了这种物品后更新状态
17                  for (int j = nums[i]; j <= sum; j++) {
18                      dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]);
19                  }
20              }
21              // 放满了才能表示正好1/2
22              if (dp[n - 1][sum] == sum)
23                  return true;
24              else
25                  return false;
26          }
27      }

后来看了网上其他的代码,感觉另一种方法更好。

3.其他做法(动态规划)

 1     public  boolean canPartition(int[] nums) {
 2          int sum = Arrays.stream(nums).reduce(0, Integer::sum);
 3          int target = sum / 2;
 4          if(sum % 2 != 0){
 5              return false;
 6          }
 7          boolean [] dp = new boolean[target + 1];
 8          dp[0] = true;
 9          for(int i = 0; i < nums.length; i++){
10                 for (int j = target; j >= nums[i]; j--) {
11                     dp[j] = dp[j] || dp[j - nums[i]];
12                 }
13          }
14          return dp[target];
15     }

 上面的代码用的是也是动态规划,但是只用了一维数组。

其中意思是:

  我们定义dp数组来表示某一个数字是否是数组nums的任意一个集合的和,比如,dp[2]表示的是2是否是nums的任意一个集合的和,如果是的话,那为true,否则为false。所以我们最终看dp[sum / 2](sum为数组nums的和)是否为true。但是我们怎么得到这个dp数组呢?

  首先,我们得到dp[sum / 2]就行,所以数组的长度最好是sum + 1,因为dp[0]不在题的范围内,所以dp求出1 ~ sum/2直接的所有值,于是我们可以得到一个二维表(实际上是一维表,但是这里为了演示清楚,才选择二维表)。dp数组默认全部为false,然后后面更新这个数组。如图所示

技术分享

  我们来看一个表:

技术分享

 

以上是关于算法-Partition Equal Subset Sum(动态规划)的主要内容,如果未能解决你的问题,请参考以下文章

算法: 零一背包问题416. Partition Equal Subset Sum

算法: 零一背包问题416. Partition Equal Subset Sum

算法: 数组是否可以分成和相等的两个子集416. Partition Equal Subset Sum

刷题416. Partition Equal Subset Sum

LeetCode Partition Equal Subset Sum

416 Partition Equal Subset Sum