【力扣周賽】186場| 5392、5393、5394、5395 |動態規劃|滑動窗口

5392. 分割字符串的最大得分

給你一個由若干 0 和 1 組成的字符串 s ,請你計算並返回將該字符串分割成兩個 非空 子字符串(即 左 子字符串和 右 子字符串)所能獲得的最大得分。

「分割字符串的得分」爲 左 子字符串中 0 的數量加上 右 子字符串中 1 的數量。

算法思路

嘗試着用可以規律化的思維來解題。

對這道題,讀題後很容易想到分割的可能性,就是從字符串第一個元素開始,假設s=‘12345’,就會分割爲’1’,‘2345’與’12’,‘345’與’123’,‘45’與’1234’,‘5’。
!如果這樣的話我就不會像做題時一樣忘掉了兩個 非空 子字符串這一點了。

所以很容易進一步:通過for循環,每一步都對左右字串進行for循環求分,但這顯然不是一個聰明的做法,基於以前刷題的經驗,很容易想到,對初始狀態左右子串l,r標識,進行求分,得到結果,之後的for循環對每個元素進行判斷,是'0'l+1,是"1"則r-1.每次都對MAX進行一次判斷更新。

class Solution:
    def maxScore(self, s: str) -> int:
        l,r=s[0].count('0'),s[1:].count('1')
        MAX=l+r
        for i in s[1:-1]:
            if i=='0':l+=1
            else:r-=1
            MAX=max(MAX,l+r)
        return MAX

5393. 可獲得的最大點數

幾張卡牌 排成一行,每張卡牌都有一個對應的點數。點數由整數數組 cardPoints 給出。

每次行動,你可以從行的開頭或者末尾拿一張卡牌,最終你必須正好拿 k 張卡牌。

你的點數就是你拿到手中的所有卡牌的點數之和。

給你一個整數數組 cardPoints 和整數 k,請你返回可以獲得的最大點數。

算法思路

本來毫無思路,差點就要放棄了,在最後才透過這花裏胡哨的描述看到本質:

每次都只能選擇開頭或末尾的卡,最多拿K張牌,求可能的最大點數。
實際上就是不論選擇的次序如何,結果就是數組前面的i個元素和數組後面的k-i個元素的最大值。求i,求最大值。

想到這個就很簡單了,可以參考5392.

class Solution:
    def maxScore(self, cardPoints: List[int], k: int) -> int:
        tp=sum(cardPoints[-k:])
        MAX=tp
        for i in range(k):
            tp=tp-cardPoints[-k+i]+cardPoints[i]
            MAX=max(MAX,tp)
        return MAX

執行用時 :112 ms, 在所有 Python3 提交中擊敗了100.00%的用戶
內存消耗 :24 MB, 在所有 Python3 提交中擊敗了100.00%的用戶

提交早的一個好處就是不管你執行結果是什麼鳥樣子基本都是雙百。

5394. 對角線遍歷 II

給你一個列表 nums ,裏面每一個元素都是一個整數列表。請你依照下面各圖的規則,按順序返回 nums 中對角線上的整數。
在這裏插入圖片描述

算法思路

第一想法是蠢兮兮的去把不規則數組填滿,變成矩形。然後參考24號那天的【力扣】1329:將矩陣按對角線排序 | 數組 | 對角線遍歷
裏的睿智操作,進行遍歷。

class Solution:
    def findDiagonalOrder(self, nums):
        MAX=0

        for i in nums:
            MAX=max(MAX,len(i))
        for i in range(len(nums)):
            nums[i]=nums[i]+[0]*(MAX-len(nums[i]))
        res=[]
        self.change=(-1,1)
        def pack(i, j,MAX):
            while 0 <= i < len(nums) and 0 <= j < MAX:
                if nums[i][j]:res.append(nums[i][j])
                i += self.change[0]
                j += self.change[1]

        for i in range(len(nums)):
            pack(i,0,MAX)
        for i in range(1,MAX):
            pack(len(nums)-1,i,MAX)
            
        return res

結果就是百分百被長數據打臉。

優化

但是好在那天看到了優秀的思路,對於從左上角到右下角的遍歷,因爲座標x=y,所以相同線上的元素x-y相同。

很顯然,對於這道題,有x+y是一個定值。(我的數學都還給老師了我對不起老師啊)

class Solution:
    def findDiagonalOrder(self, nums):
        d={}
        res=[]
        for i in range(len(nums)):
            for j in range(len(nums[i])):
                if i+j in d:
                    d[i+j].append(nums[i][j])
                else:
                    d[i+j]=[nums[i][j]]
        for i in sorted(d.keys()):
            res+=d[i][::-1]
        return res

有一個小坑就是一次遍歷結束後的值相對題目要求來說是倒序,當然這不是問題,

執行用時 :276 ms, 在所有 Python3 提交中擊敗了100.00%的用戶
內存消耗 :34.6 MB, 在所有 Python3 提交中擊敗了100.00%的用戶

5180. 帶限制的子序列和

給你一個整數數組 nums 和一個整數 k ,請你返回 非空 子序列元素和的最大值,子序列需要滿足:子序列中每兩個 相鄰 的整數 nums[i] 和 nums[j] ,它們在原數組中的下標 i 和 j 滿足 i < j 且 j - i <= k

數組的子序列定義爲:將數組中的若干個數字刪除(可以刪除 0 個數字),剩下的數字按照原本的順序排布。
原數組也可以是子序列。

算法思路

參加的第十一場周賽了,悲哀就悲哀在於我壓軸題總做不出來,爆哭。

想到應該可以用動態規劃解決,然而構造動態轉移方程時卡住了,想了想還是躺着舒服就不掙扎了。

動態規劃

點擊原文

解釋:
dp[i]:以下標i爲結尾的子序列的和值
M:[i-k,i-1]範圍內的dp最大值
轉移方程:
if M >= 0:dp[i] = M + nums[i]
else:dp[i] = nums[i]
class Solution:
    def constrainedSubsetSum(self, nums, k: int):
        dp = [0]*len(nums)
        dp[0] = nums[0]
        arr = [(nums[0],0)]
        for i in range(1,len(nums)):
            M = arr[0][0]

            if M>=0:dp[i] = M+nums[i]
            else:dp[i] = nums[i]
            
            # 這一部分用來維護arr的第一個元素是[i-k,i-1]範圍內的dp最大值
            while arr and dp[i]>=arr[-1][0]:
                arr.pop()
            arr.append((dp[i],i))
            while arr[0][1]<(i-k+1):
                arr.pop(0)
                
        return max(dp)

滑動窗口

點擊原文

解題思路
每一個新元素的結果取決於前K個結果中的最大值,維護一個滑動窗口,管理前K個結果中的最大值

在這裏插入圖片描述
就這一句話,我竟然沒想到(容我再去吐會兒血)

import collections

class Solution:
    def constrainedSubsetSum(self, nums: List[int], k: int) -> int:
        n = len(nums)
        dp = [0] * n
        deque = collections.deque(maxlen=k)   
        dp[0] = nums[0]
        deque.append(0)

        for i in range(1, n):
            dp[i] = nums[i] + max(0, dp[deque[0]])
            if i >= k:
                while deque and i - deque[0] >= k:
                    deque.popleft()
            while deque and dp[i] >= dp[deque[-1]]:
                deque.pop()
            deque.append(i)
        
        #print(dp)
        return max(dp)

DP+單調棧優化

點擊原文
解題思路
定義狀態dp[i]爲以i結尾的的最大子序和,那麼當考慮第i+1個的時候,由於向量兩個小標差距不大於k且非空,所以有以下狀態轉移方程:
      dp[i+1]=max(nums[i+1],dp[i+1j]+dp[i+1])dp[i+1]=max(nums[i+1],dp[i+1-j]+dp[i+1])  1<=j<=k1 <=j<=k
如果使用蠻力法的話,時間複雜度O(nk)O(nk),會超時。所以需要優化。

由於當前時刻只依賴於前k個時刻的狀態,所以快速找到前k個狀態中的最大的即可。這個時候聯想到滑動窗口最大的題目。


class Solution:
    def constrainedSubsetSum(self, nums: List[int], k: int) -> int:
        n = len(nums)
        dp = nums[:]
        dp[0] = nums[0]
        res = nums[0]
        s = [(nums[0], 0)]
        for i in range(1, len(nums)):
            dp[i] = max(dp[i], s[0][0] + nums[i])
            while s and s[-1][0] <= dp[i]:
                s.pop()
            s.append((dp[i], i))
            if s[0][1] <= i - k:
                s.pop(0)
            res = max(res, dp[i])
        return res

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章