LeetCode 1262. 可被三整除的最大和

题目描述

1262. 可被三整除的最大和

思路分析

解法一:动态规划(推荐)

核心思路

  • 定义 dp[r] 表示当前遍历到的元素中,子集和对 3 取余为 r(r = 0/1/2)时的最大子集和。
  • 初始化 dp[0] = 0dp[1] = dp[2] = -∞(表示尚未达到该余数的合法状态)。
  • 遍历每个数 num,用临时数组保存更新前的状态,避免同一轮使用同一元素多次。
  • 对于当前元素 num,余数为 r = num % 3,则更新:dp[(j + r) % 3] = max(dp[(j + r) % 3], dp[j] + num),其中 j 为旧状态下合法的余数。
  • 遍历结束后,dp[0] 即为所求的最大可被 3 整除的子集和。


复杂度分析

  • 时间复杂度:O(n),其中 n 表示数组 nums 的长度,每个元素只遍历一次,状态数固定为 3。
  • 空间复杂度:O(1),只使用了长度为 3 的 dp 数组。
class Solution {
    public int maxSumDivThree(int[] nums) {
        // dp[r] 表示子集和对 3 取余为 r 时的最大子集和,初始化为极小值
        int[] dp = new int[]{0, Integer.MIN_VALUE, Integer.MIN_VALUE};

        for (int num : nums) {
            // 保存旧状态,防止同一轮更新被重复使用
            int[] prev = dp.clone();
            int r = num % 3;
            for (int j = 0; j < 3; j++) {
                // 仅从合法状态转移(跳过未达到的状态)
                if (prev[j] == Integer.MIN_VALUE) {
                    continue;
                }
                // 加入当前元素后,新余数为 (j + r) % 3
                int newR = (j + r) % 3;
                dp[newR] = Math.max(dp[newR], prev[j] + num);
            }
        }

        // dp[0] 即为可被 3 整除的最大子集和
        return dp[0];
    }
}
func maxSumDivThree(nums []int) int {
	// dp[r] 表示子集和对 3 取余为 r 时的最大子集和
	const minInf = -1 << 30
	dp := [3]int{0, minInf, minInf}

	for _, num := range nums {
		// 保存旧状态,防止同一轮更新被重复使用
		prev := dp
		r := num % 3
		for j := 0; j < 3; j++ {
			// 仅从合法状态转移
			if prev[j] == minInf {
				continue
			}
			// 加入当前元素后,新余数为 (j + r) % 3
			newR := (j + r) % 3
			if prev[j]+num > dp[newR] {
				dp[newR] = prev[j] + num
			}
		}
	}

	// dp[0] 即为可被 3 整除的最大子集和
	return dp[0]
}

解法二:贪心

核心思路

  • 先求数组所有元素的总和 sum
  • sum % 3 == 0,直接返回 sum
  • sum % 3 == 1,需要减少余数 1:
    • 方案 A:移除一个余数为 1 的最小元素;
    • 方案 B:移除两个余数为 2 的最小元素;
    • 取两方案中结果更大的一个。
  • sum % 3 == 2,需要减少余数 2:
    • 方案 A:移除一个余数为 2 的最小元素;
    • 方案 B:移除两个余数为 1 的最小元素;
    • 取两方案中结果更大的一个。
  • 对每种余数只需记录最小的两个元素即可完成判断。


复杂度分析

  • 时间复杂度:O(n),其中 n 表示数组 nums 的长度,遍历一次记录各余数的最小元素。
  • 空间复杂度:O(1),只需存储余数为 1 和 2 的最小两个元素。
class Solution {
    public int maxSumDivThree(int[] nums) {
        int sum = 0;
        // rm1 存余数为 1 的最小两个元素,rm2 存余数为 2 的最小两个元素
        int[] rm1 = new int[]{Integer.MAX_VALUE, Integer.MAX_VALUE};
        int[] rm2 = new int[]{Integer.MAX_VALUE, Integer.MAX_VALUE};

        for (int num : nums) {
            sum += num;
            int r = num % 3;
            // 维护余数为 1 的最小两个值
            if (r == 1) {
                if (num < rm1[0]) {
                    rm1[1] = rm1[0];
                    rm1[0] = num;
                } else if (num < rm1[1]) {
                    rm1[1] = num;
                }
            }
            // 维护余数为 2 的最小两个值
            if (r == 2) {
                if (num < rm2[0]) {
                    rm2[1] = rm2[0];
                    rm2[0] = num;
                } else if (num < rm2[1]) {
                    rm2[1] = num;
                }
            }
        }

        if (sum % 3 == 0) {
            return sum;
        }

        int ans = 0;
        if (sum % 3 == 1) {
            // 移除最小余数 1 的元素
            if (rm1[0] != Integer.MAX_VALUE) {
                ans = Math.max(ans, sum - rm1[0]);
            }
            // 移除最小的两个余数 2 的元素
            if (rm2[1] != Integer.MAX_VALUE) {
                ans = Math.max(ans, sum - rm2[0] - rm2[1]);
            }
        } else {
            // 移除最小余数 2 的元素
            if (rm2[0] != Integer.MAX_VALUE) {
                ans = Math.max(ans, sum - rm2[0]);
            }
            // 移除最小的两个余数 1 的元素
            if (rm1[1] != Integer.MAX_VALUE) {
                ans = Math.max(ans, sum - rm1[0] - rm1[1]);
            }
        }
        return ans;
    }
}
func maxSumDivThree(nums []int) int {
	sum := 0
	// rm1 存余数为 1 的最小两个元素,rm2 存余数为 2 的最小两个元素
	const maxInt = 1<<31 - 1
	rm1 := [2]int{maxInt, maxInt}
	rm2 := [2]int{maxInt, maxInt}

	for _, num := range nums {
		sum += num
		r := num % 3
		// 维护余数为 1 的最小两个值
		if r == 1 {
			if num < rm1[0] {
				rm1[1] = rm1[0]
				rm1[0] = num
			} else if num < rm1[1] {
				rm1[1] = num
			}
		}
		// 维护余数为 2 的最小两个值
		if r == 2 {
			if num < rm2[0] {
				rm2[1] = rm2[0]
				rm2[0] = num
			} else if num < rm2[1] {
				rm2[1] = num
			}
		}
	}

	if sum%3 == 0 {
		return sum
	}

	ans := 0
	if sum%3 == 1 {
		// 移除最小余数 1 的元素
		if rm1[0] != maxInt {
			if v := sum - rm1[0]; v > ans {
				ans = v
			}
		}
		// 移除最小的两个余数 2 的元素
		if rm2[1] != maxInt {
			if v := sum - rm2[0] - rm2[1]; v > ans {
				ans = v
			}
		}
	} else {
		// 移除最小余数 2 的元素
		if rm2[0] != maxInt {
			if v := sum - rm2[0]; v > ans {
				ans = v
			}
		}
		// 移除最小的两个余数 1 的元素
		if rm1[1] != maxInt {
			if v := sum - rm1[0] - rm1[1]; v > ans {
				ans = v
			}
		}
	}
	return ans
}

相似题目

题目 难度 考察点
1014. 最佳观光组合 中等 动态规划、数组
1524. 和为奇数的子数组数目 中等 动态规划、前缀和
1031. 两个非重叠子数组的最大和 中等 动态规划、前缀和
983. 最低票价 中等 动态规划
1049. 最后一块石头的重量 II 中等 动态规划、背包
416. 分割等和子集 中等 动态规划、背包
2939. 最大异或乘积 中等 动态规划、贪心
本文作者:
本文链接: https://hgnulb.github.io/blog/2026/63943299
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!