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

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)

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

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

LC-1262. 可被三整除的最大和(状态机DP)_第1张图片

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];
    }
}

你可能感兴趣的:(算法刷题记录,leetcode,算法,职场和发展)