Leetcode:两道不错的动态规划题目

觉得这两题好是因为这两题可以通过转化为其他比较容易分析的问题来求解。

 

416. 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.

Note:

  1. Each of the array element will not exceed 100.
  2. The array size will not exceed 200.

Example 1:

Input: [1, 5, 11, 5]

Output: true

Explanation: The array can be partitioned as [1, 5, 5] and [11].

Example 2:

Input: [1, 2, 3, 5]

Output: false

Explanation: The array cannot be partitioned into equal sum subsets.

题意:给你一个数组,问你这个数组能否分成两个元素之和相等的子集合。

思路:

1、先对整个数组求和sum。如果sum为奇数就不可能满足。

2、sum为偶数,现在的目标是判断原数组有没有一些元素的和为sum/2。

3、问题转化:在一堆元素中找到几个元素使这些元素的总和为sum/2 -> 在一堆物品中找到几个物品使这些物品刚好能够填满体积为sum/2的背包。可以将物品的重量和体积看成相同的(即元素的值),如果最后可以得到体积为sum/2的背包的重量也为sum/2,那就说明可以找到。

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum=accumulate(nums.begin(),nums.end(),0);
        if(sum&1)
            return false;
        sum/=2;
        vector<int> bag(sum+1,0);
        
        for(int w:nums)
            for(int v=sum;v>=w;--v)
                if(bag[v]<bag[v-w]+w)
                    bag[v]=bag[v-w]+w;
        return bag[sum]==sum;
        
    }
};

1049. Last Stone Weight II

这是5.19contest的最难一题,我不会做,看了别人的解释才恍然大悟。

题目描述:

We have a collection of rocks, each rock has a positive integer weight.

Each turn, we choose any two rocks and smash them together.  Suppose the stones have weights x and y with x <= y.  The result of this smash is:

  • If x == y, both stones are totally destroyed;
  • If x != y, the stone of weight x is totally destroyed, and the stone of weight y has new weight y-x.

At the end, there is at most 1 stone left.  Return the smallest possible weight of this stone (the weight is 0 if there are no stones left.)

Note:

  1. 1 <= stones.length <= 30
  2. 1 <= stones[i] <= 100

Example 1:

Input: [2,7,4,1,8,1]
Output: 1
Explanation: 
We can combine 2 and 4 to get 2 so the array converts to [2,7,1,8,1] then,
we can combine 7 and 8 to get 1 so the array converts to [2,1,1,1] then,
we can combine 2 and 1 to get 1 so the array converts to [1,1,1] then,
we can combine 1 and 1 to get 0 so the array converts to [1] then that's the optimal value.

题意:有一堆元素,每次任取两个,如果两个元素的差的绝对值为0,则把这两个元素才原集合中删除,然后继续取,如果不为0则把绝对值加进原集合同时删除两个取出的元素,然后继续取。问最后可能剩下的最小元素是多少(如果没有剩下的元素则输出0).

思路:

没思路,感觉好像是数学题,用暴力求解超时了。

别人的思路:

这问题就相当于给你一个集合(可以有重复数字的集合),问你如何将这个集合分成两个子集使得两个子集的元素之和的差的绝对值最小,要求输出这个差值。

为什么相当于?

按照原题意去做,比如 4-2=2,8-7=1,然后再2-1,是不是相对于4+7-(2+8),元素多了也是如此。所以可以做问题的转换。

做法:

由于

  1. 1 <= stones.length <= 30
  2. 1 <= stones[i] <= 100

所以可以用DP,不会有占用空间过多的ERROR。

设一个DP数组dp[n][sum],只有两个值:1->有;2->没有,dp[n][sum]=1表示前n个元素的子集中有一个子集的所有元素的和为sum。

对每个元素,只有在子集和不在子集两种状态,容易DP,可得:

dp[n][sum]= dp[n-1][sum]  || dp[n-1][sum-第n个元素的值];

||左边表示:子集中不包含第n个元素且元素之和为sum;

||右边表示:子集中包含第n个元素且元素之和为sum。

最后在dp[n][i]中找我们的答案,这里的n指的是所有元素的总个数。

class Solution
{
public:
    int lastStoneWeightII(vector<int> &stones)
    {
        int n=stones.size();
        int sum=accumulate(stones.begin(),stones.end(),0);
        vector<vector<int>> dp(n+1,vector<int>(sum+1,0));
        
        //这里初始化了所有元素都不选的情况
        for(int i=0;i<=n;++i)
            dp[i][0]=1;
        //利用推导的DP公式
        for(int i=1;i<=n;++i)
        {
            int v=stones[i-1];
            for(int j=1;j<=sum;++j)
            {
                dp[i][j]=dp[i-1][j];
                if(v<=j)
                    dp[i][j]|=dp[i-1][j-v];
            }
        }
        //分出来的两个子集本质上是可以互相转换的,
        //即分出两个子集A={a,b,c}和B={d,e,f}跟B={a,b,c}和A={d,e,f}是一样的
        //因此遍历范围为0-sum/2
        //从sum/2开始递减遍历,只要dp[n][I]存在就一定是差值最小,看我们的计算公式sum-2i就知道了
        int ans=3000;
        for(int i=sum/2;i>=0;--i)
            if(dp[n][i])
            {
                ans=sum-2*i;
                break;
            }
        return ans;
    }
};

 

学了第二题后最大的感触就是问题的转换真的巧妙。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值