classSolution:defmaxSumDivThree(self,nums: List[int]) ->int: self.res =0defbacktrack(temp,start): total =sum(temp)if total %3==0: self.res =max(self.res, total)for i inrange(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 为例:
代码
classSolution:defmaxSumDivThree(self,nums: List[int]) ->int: one = [] two = [] total =0for num in nums: total += numif num %3==1: one.append(num)if num %3==2: two.append(num) one.sort() two.sort()if total %3==0:return totalelif total %3==1and one:iflen(two)>=2and one[0]> two[0]+ two[1]:return total - two[0]- two[1]return total - one[0]elif total %3==2and two:iflen(one)>=2and two[0]> one[0]+ one[1]:return total - one[0]- one[1]return total - two[0]return0
减法 + 非排序
思路
上面的解法使用到了排序。 我们其实观察发现,我们只是用到了 one 和 two 的最小的两个数。因此我们完全可以在线形的时间和常数的空间完成这个算法。我们只需要分别记录 one 和 two 的最小值和次小值即可,在这里,我使用了两个长度为 2 的数组来表示,第一项是最小值,第二项是次小值。
代码
classSolution:defmaxSumDivThree(self,nums: List[int]) ->int: one = [float('inf')] *2 two = [float('inf')] *2 total =0for num in nums: total += numif num %3==1:if num < one[0]: t = one[0] one[0]= num one[1]= telif num < one[1]: one[1]= numif num %3==2:if num < two[0]: t = two[0] two[0]= num two[1]= telif num < two[1]: two[1]= numif total %3==0:return totalelif total %3==1and one:iflen(two)>=2and one[0]> two[0]+ two[1]:return total - two[0]- two[1]return total - one[0]elif total %3==2and two:iflen(one)>=2and two[0]> one[0]+ one[1]:return total - one[0]- one[1]return total - two[0]return0
如果 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]即可。
代码
classSolution:defmaxSumDivThree(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]
当然这个代码还可以简化:
classSolution:defmaxSumDivThree(self,nums: List[int]) ->int: state = [0,float('-inf'),float('-inf')]for num in nums: temp = [0] *3for i inrange(3): temp[(i + num) %3]=max(state[(i + num) %3], state[i] + num) state = tempreturn state[0]
复杂度分析
时间复杂度:$O(N)$
空间复杂度:$O(1)$
关键点解析
贪婪法
状态机
数学分析
扩展
实际上,我们可以采取加法(贪婪策略),感兴趣的可以试一下。
另外如果题目改成了请你找出并返回能被x整除的元素最大和,你只需要将我的解法中的 3 改成 x 即可。