什么? 状态机还能够用来刷 LeetCode? 若是你还不知道,那么就快进来看看吧!前端
题目地址: https://leetcode-cn.com/probl...python
给你一个整数数组 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
一种方式是找出全部的可以被 3 整除的子集,而后挑选出和最大的。因为咱们选出了全部的子集,那么时间复杂度就是 $O(2^N)$ , 毫无疑问会超时。这里咱们使用回溯法找子集,若是不清楚回溯法,能够参考我以前的题解,不少题目都用到了,好比78.subsets。git
更多回溯题目,能够访问上方连接查看(可使用一套模板搞定):github
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
减法的核心思想是,咱们求出总和。若是总和不知足题意,咱们尝试减去最小的数,使之知足题意。正则表达式
这种算法的思想,具体来讲就是:算法
因为咱们须要取 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 的数组来表示,第一项是最小值,第二项是次小值。app
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
我在数据结构与算法在前端领域的应用 - 第二篇 中讲到了有限状态机。
状态机表示若干个状态以及在这些状态之间的转移和动做等行为的数学模型。通俗的描述状态机就是定义了一套状态変更的流程:状态机包含一个状态集合,定义当状态机处于某一个状态的时候它所能接收的事件以及可执行的行为,执行完成后,状态机所处的状态。
状态机使用很是普遍,好比正则表达式的引擎,编译器的词法和语法分析,网络协议,企业应用等不少领域都会用到。
拿本题中来讲,咱们从左到右扫描数组的过程,将会不断改变状态机的状态。
咱们使用 state 数组来表示本题的状态:
咱们的状态转移方程就会很容易。说到状态转移方程,你可能会想到动态规划。没错!这种思路能够直接翻译成动态规划,算法彻底同样。若是你看过我上面提到的文章,那么状态转移方程对你来讲就会很容易。若是你不清楚,那么请往下看:
max(state[2] + num, state[0])
。同理 state[1] 和 state[2] 的转移逻辑相似。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]
实际上,咱们能够采起加法(贪婪策略),感兴趣的能够试一下。