一、最简单的求两个子集和差值最小
题目链接:https://www.lintcode.com/problem/724/
变相的0/1背包问题
这个题考一点思维能力,两部分的差值绝对值最小,那就要让两部分的和最接近,最理想的状态肯定是两部分和刚好相等,差值为0。
但是如果满足不了,则尽可能往那儿靠,就是在背包容量为sum/2的时候让S1最大化地装东西,这就转化成了0/1背包问题
AC代码:
#include<bits/stdc++.h>
using namespace std;
int num[1000000];
int n;
int main()
{
cin>>n;
int sum=0;
for(int i=0;i<n;i++)
{
cin>>num[i];sum+=num[i];
}
int c=sum/2;
//cout<<c<<endl;
int dp[n+1][c+1];
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=c;j++)
{
if(num[i-1]<=j)
{
dp[i][j]=max(dp[i-1][j],dp[i-1][j-num[i-1]]+num[i-1]);
}
else dp[i][j]=dp[i-1][j];
}
}
int res=dp[n][c];
cout<<abs(res-(sum-res));
return 0;
}
/*
dp[i][j] -->将前i个物品放入容量为j的背包能得到的最大和
*/
二、求两个子集和相等
题目链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/
- nums如果要想有两个子序列相等,那么sum一定是偶数,奇数肯定不能分成两个相等序列。
- 跟上面的思路类似,sum/2 可以看作背包最大容量,nums[i] 可以看作物品质量和价值(相等),那么这道题就可以转换为每件物品只能用一次,哪些物品装入背包里物品价值总和最大,由于物品质量和价值相等,所以其实尽量装满背包,价值总和就是最大。(0-1 背包问题)
- 判断最大价值是否等于 sum/2, 成立则返回 true, 反之返回 false。
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum=0;
int n=nums.size();
for(int i=0;i<n;i++)
{
sum+=nums[i];
}
if(sum%2) return false; //如果和为奇数,肯定不能分割成两个相等和的子集
int dp[n+1][sum/2+1];
memset(dp,0,sizeof(dp));
for(int j=1;j<=sum/2;j++)
{
for(int i=1;i<=n;i++)
{
if(nums[i-1]<=j)
{
dp[i][j]=max(dp[i-1][j],dp[i-1][j-nums[i-1]]+nums[i-1]);
}
else dp[i][j]=dp[i-1][j];
}
}
return dp[n][sum/2]==sum/2;
}
};
三、是否存在两个子集的和相等并且和最大
题目链接:http://lx.lanqiao.cn/problem.page?gpid=T2992
- 这个题既用到了动态规划,也用到的贪心思想
- 我首要想到的就是简单的回溯,结果运行超时了,转变一下,将木棍的长度看成一个集合,其实就是在一个集合中找两个子集,使得这两个子集的和相等且最大,和相等就是上一个题的dp思路,和最大采用贪心思想,将集合元素排序后,每次判断当前集合元素和是否能找到两个子集的和相等,如果不能则删去集合中的最小的元素,再次判断剩下元素是否满足
- 找到的第一个满足条件的集合
注意:sum为偶数不一定代表就满足条件,还需要进行判断,
比如[2,3,4,5],sum=14,sum/2=7,dp[4][7]=5!=7,但是他的答案应该是[2,3] 和子集[5]
AC代码:
#include<iostream>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;
#define MAXSIZE 16
vector<int>num;
bool Deal(int n,int sum)
{
if(sum%2) return false;
int dp[n+1][sum/2+1];
memset(dp,0,sizeof(dp));
for(int j=1;j<=sum/2;j++)
{
for(int i=1;i<=n;i++)
{
if(num[i-1]<=j)
{
dp[i][j]=max(dp[i-1][j],dp[i-1][j-num[i-1]]+num[i-1]);
}
else dp[i][j]=dp[i-1][j];
}
}
return dp[n][sum/2]==sum/2;
}
int main()
{
int n=0;
cin>>n;
for(int i=0;i<n;i++)
{
int t;cin>>t;
num.push_back(t);
}
sort(num.begin(),num.end());
int sum=0;
for(int i=0;i<n;i++) sum+=num[i];
while(n>0)
{
if(Deal(n,sum))
{
cout<<sum/2;
break;
}
for(int j=0;j<n;j++)
{
if((sum-num[j])%2==0) //把当前这个减去能成为偶数
{
sum-=num[j];
n--;
num.erase(num.begin()+j);
break;
}
}
}
if(n==0) cout<<0;
return 0;
}
三个题目类似的方法,类似的思想,比较学习