LC-1262. 可被三整除的最大和(状态机DP)

文章介绍了如何使用动态规划解决寻找整数数组中能被三整除元素的最大和问题。通过状态机DP,定义了模三余零、余一、余二的状态,并根据当前数字对三的余数进行不同选择,更新状态转移方程。最后给出代码实现,包括空间优化的版本。
摘要由CSDN通过智能技术生成

1262. 可被三整除的最大和

难度中等229

给你一个整数数组 nums,请你找出并返回能被三整除的元素最大和。

示例 1:

输入:nums = [3,6,5,1,8]
输出:18
解释:选出数字 3, 6, 1 和 8,它们的和是 18(可被 3 整除的最大和)。

示例 2:

输入:nums = [4]
输出:0
解释:4 不能被 3 整除,所以无法选出数字,返回 0。

示例 3:

输入:nums = [1,2,3,4,4]
输出:12
解释:选出数字 1, 3, 4 以及 4,它们的和是 12(可被 3 整除的最大和)。

提示:

  • 1 <= nums.length <= 4 * 10^4
  • 1 <= nums[i] <= 10^4

状态机DP

https://leetcode.cn/problems/greatest-sum-divisible-by-three/solution/dong-tai-gui-hua-yu-zhuang-tai-zhuan-yi-by-christm/

1、状态定义

  • dp[i][0]表示nums[0...i]模三余零的最大和
  • dp[i][1]表示nums[0...i]模三余一的最大和
  • dp[i][2]表示nums[0...i]模三余二的最大和
  • 零状态:当前数字最大和模三余零
  • 一状态:当前数字最大和模三余一
  • 二状态:当前数字最大和模三余二

2、状态转移

对于任意一种状态,下一步我们都有两种选择,一是选择当前元素二是不选择当前元素

dp[i][*] = max{dp[i-1][*],dp[i-1][*] + nums[i]}  (* 取值为 0,1,2)

以上是常见的动态规划的递推结构

本题的状态转移显而易见,以当前状态是零状态为例。我们可以想到,前一个状态无非是零状态、一状态、二状态,三种情况,针对这三种情况我们分类讨论即可

class Solution {
    public int maxSumDivThree(int[] nums) {
        int n = nums.length;
        int[][] dp = new int[n+1][3];
        // 初值,不存在余1和余2的情况
        dp[0][0] = 0; dp[0][1] = Integer.MIN_VALUE; dp[0][2] = Integer.MIN_VALUE;
        for(int i = 1; i <= n; i++){
            if(nums[i-1] % 3 == 0){
                dp[i][0] = Math.max(dp[i-1][0], dp[i-1][0] + nums[i-1]);
                dp[i][1] = Math.max(dp[i-1][1], dp[i-1][1] + nums[i-1]);
                dp[i][2] = Math.max(dp[i-1][2], dp[i-1][2] + nums[i-1]);
            }else if(nums[i-1] % 3 == 1){
                dp[i][0] = Math.max(dp[i-1][0], dp[i-1][2] + nums[i-1]);
                dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + nums[i-1]);
                dp[i][2] = Math.max(dp[i-1][2], dp[i-1][1] + nums[i-1]);
            }else{ // nums[i-1] % 3 == 2
                dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + nums[i-1]);
                dp[i][1] = Math.max(dp[i-1][1], dp[i-1][2] + nums[i-1]);
                dp[i][2] = Math.max(dp[i-1][2], dp[i-1][0] + nums[i-1]);
            }
        }
        return dp[n][0]; // 返回模三余0 的值
    }
}

21.4.5 网易互联网C++笔试改版成 可被6整除的最大和

22.3.26 美团改成7了

动态规划(更一般性的状态机DP)

https://leetcode.cn/problems/greatest-sum-divisible-by-three/solution/liang-chong-suan-fa-tan-xin-dong-tai-gui-tsll/

讨论:

用【选或不选】的思路,考虑最后一个数 x = nums[n-1]
如果 x mod 3 = 0,那么 x 一定要选,问题变成从 nums[0] 到 nums[n-2] 中寻找能被 3 整除的元素最大和。
如果 x mod 3 = 1:
    如果不选 x,和上面一样,问题变成从 nums[0] 到 nums[n-2] 中寻找能被 3 整除的元素最大和 s0。
    如果选 x,问题变成从 nums[0] 到 nums[n-2] 中寻找最大元素和s2 满足 s2 mod 3 = 2,答案为 max(so,s2 +x)。
如果 x mod 3 = 2:
    如果不选 x,和上面一样,问题变成从 nums[0] 到 nums[n-2] 中寻找能被 3 整除的元素最大和 s0。
    如果选 x,问题变成从 nums[0] 到 nums[n-2] 中寻找最大元素和s1 满足 s1 mod 3 = 1,答案为 max(so,s1 +x)。
---------------------------------------------------
上述讨论,刻画了这道题的两个重要参数
	i: 表示从 nums[0]到 nums[i] 中选数
	j: 表示所选数字之和 s 需要满足 s mod 3 = j。
那么原问题就是 (i = n-1,j = 0),上述讨论得到的子问题有 (i= n-2,j=0),(i = n - 2,j = 1),(i = n - 2,j = 2)。
注: 为什么要从最后一个数开始讨论? 主要是为了方便后面把记忆化搜索改成递推。当然,你从第一个数开始讨论也是可以的。

记忆化搜索

class Solution:
    def maxSumDivThree(self, nums: List[int]) -> int:
        # 定义 dfs(i,j) 表示从 nums[0] 到 nums[i] 中选数, 所选数字之和 s 满足 s mod 3 =j的前提下, s的最大值
        @cache
        def dfs(i, j: int) -> int:
            if i < 0: return -inf if j else 0
            return max(dfs(i-1, j), dfs(i-1, (j + nums[i]) % 3) + nums[i])
        return dfs(len(nums)-1, 0)

转成递推

class Solution {
    public int maxSumDivThree(int[] nums) {
        int n = nums.length;
        int[][] f = new int[n+1][3];
        f[0][0] = 0;
        f[0][1] = Integer.MIN_VALUE;
        f[0][2] = Integer.MIN_VALUE;
        for(int i = 0; i < n; i++){
            for(int j = 0; j < 3; j++)
                f[i+1][j] = Math.max(f[i][j], f[i][(j+nums[i]) % 3] + nums[i]);
        }
        return f[n][0];
    }
}

空间优化:(用滚动数组优化空间)

由于 f[i + 1] 只依赖 f,那么 f[i - 1] 及其之前的数据就没用了

class Solution {
    public int maxSumDivThree(int[] nums) {
        int n = nums.length;
        int[] f = new int[3];
        f[0] = 0;
        f[1] = Integer.MIN_VALUE;
        f[2] = Integer.MIN_VALUE;
        for(int i = 0; i < n; i++){
            int[] tmp = Arrays.copyOf(f, 3);
            for(int j = 0; j < 3; j++)
                f[j] = Math.max(tmp[j], tmp[(j+nums[i]) % 3] + nums[i]);
            tmp = f;
        }
        return f[0];
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值