原來狀態機也能夠用來刷 LeetCode?

什麼? 狀態機還能夠用來刷 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.subsetsgit

更多回溯題目,能夠訪問上方連接查看(可使用一套模板搞定):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

減法 + 排序

減法的核心思想是,咱們求出總和。若是總和不知足題意,咱們嘗試減去最小的數,使之知足題意。正則表達式

思路

這種算法的思想,具體來講就是:算法

  • 咱們將全部的數字加起來,咱們不妨設爲 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 的數組來表示,第一項是最小值,第二項是次小值。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 數組來表示本題的狀態:

  • state[0] 表示 mod 爲 0 的 最大和
  • state[1] 表示 mod 爲 1 的 最大和
  • state[2] 表示 mod 爲 1 的 最大和

咱們的狀態轉移方程就會很容易。說到狀態轉移方程,你可能會想到動態規劃。沒錯!這種思路能夠直接翻譯成動態規劃,算法徹底同樣。若是你看過我上面提到的文章,那麼狀態轉移方程對你來講就會很容易。若是你不清楚,那麼請往下看:

  • 咱們從左往右不斷讀取數字,咱們不妨設這個數字爲 num。
  • 若是 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]

關鍵點解析

  • 貪婪法
  • 狀態機
  • 數學分析

擴展

實際上,咱們能夠採起加法(貪婪策略),感興趣的能夠試一下。

相關文章
相關標籤/搜索