题目链接:1262. 可被三整除的最大和 - 力扣(LeetCode)
解法一:贪心
正难则反+分类讨论
题目中求数组中的最大和,别且能被3整除。
正着做比较难,求选取哪些数的和最大,我们可以反着来,求丢弃哪些数可以使和最大,先求出数组元素的总和sum,再进行如下分类讨论。
前提:在这里用 x表示满足nums[i]%3==1数,用 y表示满足nums[i]%3==2的数。
如果sum%3==0,直接返回sum,sum肯定是满足要求的最大和,因为数组中的元素都是正数。
如果sum%3==1,那么该数组的组成有两种情况。
- 第一种情况是该数组有一个数x%3==1,其余数之和s%3==0,而我们想要得到最大和,所以只要删除最小的x即可,这就是贪心的地方。
- 第二种情况是该数组有两个数y1,y2,并且y1%3==2,y2%3==2,其余数之和s%3==0,同理,我们想要最大和,那么就需要删除最小的和次小的y。
如果sum%3==2,同样也可以分成两种情况。
- 第一种情况是该数组有一个数y%3==2,其余数之和s%3==0,这时我们只需删除最小的y1即可。
- 第二种情况是数组种有两个数x1,x2,并且x1%3==1,x2%3==1,这时需要删除最小的和次小的x即可。
还有一个问题,我们需要直到数组中最小的和次小的x,y这4个数。如何求一个数组中的最小值和次小值,就以求x的最小值和次小值为例,我们可以定义两个变量x1,x2,分别表示最小值和次小值,遍历数组一边即可,具体过程如下图:
class Solution {
public:
int maxSumDivThree(vector<int>& nums) {
const int INF=0x3f3f3f3f;
int sum=0,x1=INF,x2=INF,y1=INF,y2=INF;
for(int x:nums)
{
sum+=x;
//求最小值及次小值
if(x%3==1)
{
if(x<x1) x2=x1,x1=x;
else if(x<x2) x2=x;
}
else if(x%3==2)
{
if(x<y1) y2=y1,y1=x;
else if(x<y2) y2=x;
}
}
//分类讨论
if(sum%3==0) return sum;
else if(sum%3==1) return max(sum-x1,sum-y1-y2);
else return max(sum-x1-x2,sum-y1);
}
};
解法二:动态规划
首先定义状态表示:
dp[i][j]表示在前i个数中,选取若干个数,并且他们的和模3等于j,这些数的最大和。
接下来推到状态转移方程:
对于第i个数,我们面临选或者不选两种情况。
不选nums[i],那么状态可以转移为dp[i-1][j],也就是在前i-1个数中选数。
选nums[i],问题变成在0-i-1之间选数,所选数字之和s满足(s+nums[i])%3=j,即s%3=(j-nums[i])%3的前提下,s的最大值。所以状态可以转移为dp[i-1][(j-nums[i])%3]+nums[i]。
这两种情况取最大值:
dp[i][j]=max(dp[i-1][j],dp[i-1][(j-nums[i])%3]+nums[i])
由于(j-nums[i])%3可能会出现负数,根据状态表示,我们知道0<=j<3,我们可以将这部分修改为
(j+3-nums[i]%3)%3,可以理解为从0开始加3直到大于0,但为了保证<3,最后再模3。
初始化时f[0][0]=0,表示没选任何数字,初始化为0,f[0][1]=f[0][2]=INT_MIN,无意义,为了不影响计算。初始化为无穷小。
class Solution {
public:
int maxSumDivThree(vector<int>& nums) {
int n=nums.size();
int f[n+1][3];
f[0][0]=0,f[0][1]=f[0][2]=INT_MIN;
for(int i=1;i<=n;i++)
for(int j=0;j<3;j++)
f[i][j]=max(f[i-1][j],f[i-1][(j-nums[i-1]%3+3)%3]+nums[i-1]);
return f[n][0];
}
};
小结:
动态规划解法是该题的标准解法,因为如果题目中的模3该为模5,模10,模100。这时使用贪心进行分类讨论就会变得非常麻烦。