什麼? 狀態機還能夠用來刷 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]
實際上,咱們能夠採起加法(貪婪策略),感興趣的能夠試一下。