1262. 可被三整除的最大和
给你一个整数数组 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/greatest-sum-divisible-by-three
一拿到题,脑子里浮出一些算法,暴力,贪心,动规,,,
暴力=>回溯
万物始于暴力
找出所有能被 3 整除的子集,选出和最大的。时间复杂度为 O(2^N) ,肯定超时。那就会想到回溯找子集
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
然而还是超时😅
贪心+排序
- 将所有的数字加起来
- sum 除以 3,得到余数 mod
- 建立两个数组,一个是余数为 1 的mod1,一个是余数为 2 的mod2
- 如果 mod 为 0,直接返回。
- 如果 mod 为 1,减去 mod1 中最小的一个(如果有的话),或者减去两个 mod2 中最小的(如果有的话),最后减去谁取决于谁更小。
- 如果 mod 为 2,同理
因为要取最小,所以对mod1,mod2排个序,time-complexity大致为O(nlogn)
class Solution: def maxSumDivThree(self, nums: List[int]) -> int: mod1=[] mod2=[] sum=0 for num in nums: sum+=num if num%3==1: mod1.append(num) if num%3==2: mod2.append(num) mod1.sort() mod2.sort() if sum%3==0: return sum elif sum%3==1 and mod1: if len(mod2)>=2 and mod1[0]>mod2[0]+mod2[1]: return sum-mod2[0]-mod2[1] return sum-mod1[0] elif sum%3==2 and mod2: if len(mod1)>=2 and mod2[0]>mod1[0]+mod1[1]: return sum-mod1[0]-mod1[1] return sum-mod2[0] return 0
贪心+非排序
观察发现,只用到了 mod1 和 mod2 最小的两个数。因此完全可以在线性时间和常数空间完成。只需要分别记录 mod1 和 mod2 的最小值和次小值即可,用两个长度为 2 的数组来表示,第一项是最小值,第二项是次小值。
class Solution: def maxSumDivThree(self, nums: List[int]) -> int: mod1 = [float('inf')] * 2 mod2 = [float('inf')] * 2 total = 0 for num in nums: total += num if num % 3 == 1: if num < mod1[0]: t = mod1[0] mod1[0] = num mod1[1] = t elif num < mod1[1]: mod1[1] = num if num % 3 == 2: if num < mod2[0]: t = mod2[0] mod2[0] = num mod2[1] = t elif num < mod2[1]: mod2[1] = num if total % 3 == 0: return total elif total % 3 == 1 and mod1: if len(mod2) >= 2 and mod1[0] > mod2[0] + mod2[1]: return total - mod2[0] - mod2[1] return total - mod1[0] elif total % 3 == 2 and mod2: if len(mod1) >= 2 and mod2[0] > mod1[0] + mod1[1]: return total - mod1[0] - mod1[1] return total - mod2[0] return 0
动规(有限状态机)
从左到右扫描数组的过程,将会不断改变状态。
用 state 数组来表示本题的状态:
- state[0] 表示 mod 为 0 的 最大和
- state[1] 表示 mod 为 1 的 最大和
- state[2] 表示 mod 为 2 的 最大和
- 从左往右不断扫描数字,设这个数字为 num。
- 如果 num % 3 为 0。 state[0], state[1], state[2] 直接加上 num
- 如果 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]
查一手资料,才知道这个可以叫作有限状态机
状态机表示若干个状态以及在这些状态之间的转移和动作等行为的数学模型。通俗的描述状态机就是定义了一套状态変更的流程:状态机包含一个状态集合,定义当状态机处于某一个状态的时候它所能接收的事件以及可执行的行为,执行完成后,状态机所处的状态。
状态机使用非常广泛,比如正则表达式的引擎,编译器的词法和语法分析,网络协议,企业应用等很多领域都会用到。