可被3整除的最大和 || 贪心,动态规划

题目链接: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。这时使用贪心进行分类讨论就会变得非常麻烦。

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值