【力扣周赛】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 中对角线上的整数。
【力扣周赛】186场| 5392、5393、5394、5395 |动态规划|滑动窗口_第1张图片

算法思路

第一想法是蠢兮兮的去把不规则数组填满,变成矩形。然后参考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个结果中的最大值

【力扣周赛】186场| 5392、5393、5394、5395 |动态规划|滑动窗口_第2张图片
就这一句话,我竟然没想到(容我再去吐会儿血)

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且非空,所以有以下状态转移方程:
       d p [ i + 1 ] = m a x ( n u m s [ i + 1 ] , d p [ i + 1 − j ] + d p [ i + 1 ] ) dp[i+1]=max(nums[i+1],dp[i+1-j]+dp[i+1]) dp[i+1]=max(nums[i+1],dp[i+1j]+dp[i+1])   1 < = j < = k 1 <=j<=k 1<=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

你可能感兴趣的:(力扣日记)