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且非空,所以有以下狀態轉移方程:
如果使用蠻力法的話,時間複雜度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