class Solution:
def maxSumDivThree(self, nums: List[int]) -> int:
self.res = 0
def backtrack(temp, start):
total = sum(temp)
if total % 3 == 0:
self.res = max(self.res, total)
for i in range(start, len(nums)):
temp.append(nums[i])
backtrack(temp, i + 1)
temp.pop(-1)
backtrack([], 0)
return self.res
减法 + 排序
减法的核心思想是,我们求出总和。如果总和不满足题意,我们尝试减去最小的数,使之满足题意。
思路
这种算法的思想,具体来说就是:
我们将所有的数字加起来,我们不妨设为 total
total 除以 3,得到一个余数 mod, mod 可能值有 0,1,2.
同时我们建立两个数组,一个是余数为 1 的数组 one,一个是余数为 2 的数组 two
如果 mod 为 0,我们直接返回即可。
如果 mod 为 1,我们可以减去 one 数组中最小的一个(如果有的话),或者减去两个 two 数组中最小的(如果有的话),究竟减去谁取决谁更小。
如果 mod 为 2,我们可以减去 two 数组中最小的一个(如果有的话),或者减去两个 one 数组中最小的(如果有的话),究竟减去谁取决谁更小。
由于我们需要取 one 和 two 中最小的一个或者两个,因此对数组 one 和 two 进行排序是可行的,如果基于排序的话,时间复杂度大致为 $O(NlogN)$,这种算法可以通过。
以题目中的例 1 为例:
以题目中的例 2 为例:
代码
class Solution:
def maxSumDivThree(self, nums: List[int]) -> int:
one = []
two = []
total = 0
for num in nums:
total += num
if num % 3 == 1:
one.append(num)
if num % 3 == 2:
two.append(num)
one.sort()
two.sort()
if total % 3 == 0:
return total
elif total % 3 == 1 and one:
if len(two) >= 2 and one[0] > two[0] + two[1]:
return total - two[0] - two[1]
return total - one[0]
elif total % 3 == 2 and two:
if len(one) >= 2 and two[0] > one[0] + one[1]:
return total - one[0] - one[1]
return total - two[0]
return 0
减法 + 非排序
思路
上面的解法使用到了排序。 我们其实观察发现,我们只是用到了 one 和 two 的最小的两个数。因此我们完全可以在线形的时间和常数的空间完成这个算法。我们只需要分别记录 one 和 two 的最小值和次小值即可,在这里,我使用了两个长度为 2 的数组来表示,第一项是最小值,第二项是次小值。
代码
class Solution:
def maxSumDivThree(self, nums: List[int]) -> int:
one = [float('inf')] * 2
two = [float('inf')] * 2
total = 0
for num in nums:
total += num
if num % 3 == 1:
if num < one[0]:
t = one[0]
one[0] = num
one[1] = t
elif num < one[1]:
one[1] = num
if num % 3 == 2:
if num < two[0]:
t = two[0]
two[0] = num
two[1] = t
elif num < two[1]:
two[1] = num
if total % 3 == 0:
return total
elif total % 3 == 1 and one:
if len(two) >= 2 and one[0] > two[0] + two[1]:
return total - two[0] - two[1]
return total - one[0]
elif total % 3 == 2 and two:
if len(one) >= 2 and two[0] > one[0] + one[1]:
return total - one[0] - one[1]
return total - two[0]
return 0
如果 num % 3 为 0。 那么我们的 state[0], state[1], state[2] 可以直接加上 num(题目限定了 num 为非负), 因为任何数字加上 3 的倍数之后,mod 3 的值是不变的。
如果 num % 3 为 1。 我们知道 state[2] + num 会变成一个能被三整除的数,但是这个数字不一定比当前的 state[0]大。 代码表示就是max(state[2] + num, state[0])。同理 state[1] 和 state[2] 的转移逻辑类似。
同理 num % 3 为 2 也是类似的逻辑。
最后我们返回 state[0]即可。
代码
class Solution:
def maxSumDivThree(self, nums: List[int]) -> int:
state = [0, float('-inf'), float('-inf')]
for num in nums:
if num % 3 == 0:
state = [state[0] + num, state[1] + num, state[2] + num]
if num % 3 == 1:
a = max(state[2] + num, state[0])
b = max(state[0] + num, state[1])
c = max(state[1] + num, state[2])
state = [a, b, c]
if num % 3 == 2:
a = max(state[1] + num, state[0])
b = max(state[2] + num, state[1])
c = max(state[0] + num, state[2])
state = [a, b, c]
return state[0]
当然这个代码还可以简化:
class Solution:
def maxSumDivThree(self, nums: List[int]) -> int:
state = [0, float('-inf'), float('-inf')]
for num in nums:
temp = [0] * 3
for i in range(3):
temp[(i + num) % 3] = max(state[(i + num) % 3], state[i] + num)
state = temp
return state[0]
复杂度分析
时间复杂度:$O(N)$
空间复杂度:$O(1)$
关键点解析
贪婪法
状态机
数学分析
扩展
实际上,我们可以采取加法(贪婪策略),感兴趣的可以试一下。
另外如果题目改成了请你找出并返回能被x整除的元素最大和,你只需要将我的解法中的 3 改成 x 即可。