Medium
题目描述
给定一个整数数组 nums,返回可以从数组中选择的元素的最大和,使得这个和能被三整除。
示例 1:
输入:nums = [3,6,5,1,8]
输出:18
解释:选择数字 3, 6, 1 和 8,它们的和是 18(可被 3 整除的最大和)。
示例 2:
输入:nums = [4]
输出:0
解释:4 不能被 3 整除,所以无法选择任何数字。
示例 3:
输入:nums = [1,2,3,4,4]
输出:12
解释:选择数字 1, 3, 4 和 4,它们的和是 12(可被 3 整除的最大和)。
约束条件:
1 <= nums.length <= 4 * 10^41 <= nums[i] <= 10^4
解题思路
这是一个经典的动态规划问题,核心思想是跟踪不同余数状态下的最大和。
解题思路:
我们需要理解一个数学性质:如果数字的和除以3的余数为0,那么这个和就能被3整除。因此,我们可以维护三个状态:
dp[0]:当前和除以3余数为0时的最大和dp[1]:当前和除以3余数为1时的最大和dp[2]:当前和除以3余数为2时的最大和
对于数组中的每个数字 num,我们需要考虑加入这个数字后对各个状态的影响:
- 如果当前状态余数为
i,加入数字num后,新状态的余数为(i + num) % 3
状态转移过程: 遍历每个数字时,我们需要更新所有可能的状态。为了避免在同一轮更新中使用已经更新过的值,我们使用临时变量保存当前状态。
贪心思路(替代方案): 另一种思路是使用贪心算法:先计算所有数字的和,然后根据总和除以3的余数,移除最小的几个数字使得剩余和能被3整除。
这里我们采用动态规划解法,因为它更加直观且容易理解。
代码实现
class Solution {
public:
int maxSumDivThree(vector<int>& nums) {
vector<int> dp(3, 0);
for (int num : nums) {
vector<int> temp = dp;
for (int i = 0; i < 3; i++) {
dp[(temp[i] + num) % 3] = max(dp[(temp[i] + num) % 3], temp[i] + num);
}
}
return dp[0];
}
};
class Solution:
def maxSumDivThree(self, nums: List[int]) -> int:
dp = [0, 0, 0]
for num in nums:
temp = dp[:]
for i in range(3):
dp[(temp[i] + num) % 3] = max(dp[(temp[i] + num) % 3], temp[i] + num)
return dp[0]
public class Solution {
public int MaxSumDivThree(int[] nums) {
int[] dp = new int[3];
foreach (int num in nums) {
int[] temp = (int[])dp.Clone();
for (int i = 0; i < 3; i++) {
dp[(temp[i] + num) % 3] = Math.Max(dp[(temp[i] + num) % 3], temp[i] + num);
}
}
return dp[0];
}
}
var maxSumDivThree = function(nums) {
let dp = [0, 0, 0];
for (let num of nums) {
let temp = [...dp];
for (let i = 0; i < 3; i++) {
dp[(temp[i] + num) % 3] = Math.max(dp[(temp[i] + num) % 3], temp[i] + num);
}
}
return dp[0];
};
复杂度分析
| 复杂度类型 | 大小 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 遍历数组一次,每次处理3个状态 |
| 空间复杂度 | O(1) | 只使用固定大小的dp数组和临时数组 |