leetcode笔记|第三周 动态规划

第三周 动态规划

198 打家劫舍

如果是小偷,选择如何偷东西可以今晚获利最大,注意的是相邻两家不能连着偷会被发现。动态规划就是写递归方程,要注意边界条件。这道题目首先考虑没有可偷的,那就是0,如果只有一家,那就是他家,如果很多家,就考虑当前i家时能偷的最大金额是 要么前一家的最大金额 要么前面的前面那家最大金额加上当前这家。最后返回最后一家的最大金额即可。注意是从最简单的情况开始考虑,像小时候做的总结规律题目一样。

class Solution(object):
    def rob(self, nums):
        if not nums:
            return 0

        length = len(nums)
        if length == 1:
            return nums[0]

        dp = [0]*length
        dp[0]=nums[0]
        dp[1]=max(nums[0], nums[1])
        for i in range(2, length):
            dp[i] = max(dp[i-2]+nums[i], dp[i-1])
        return dp[length-1] 

或者也可以考虑,只需要保存当前和前一个,在下一个的时候,时间复杂度o(n),因为只需要遍历一遍;空间复杂度o(1),只需要存储两个。

first, second = num[0], max(nums[0], nums[1])
        for i in range(2, length):
          	first, second = second, max(first + num[i], second)

今天和dk讨论的时候,说我总感觉隔着点什么是因为我没上过算法课。嗯,补一下动态规划的知识:动态规划题目需要定义子问题,并且写出子问题的递推关系。首先,子问题是和原问题相似并且能够表示原问题的带参数的问题,他的解是能够通过其他子问题的解求出的,然后要写出递推公式,并且注意k=0和k=1这样的简单的边界条件,才能构成完整的递推关系。一般都是自底向上的计算顺序,偶尔也有自顶向下的。

53 最大子序和

[-2,1,-3,4,-1,2,1,-5,4],寻找最短子序列,和最大。动态规划,子问题是第i个位置的子序列和,原问题是所有子序列和的最大值。注意,如果第i个元素的前面的序列和小于0,那么就对后面没有贡献了就丢掉,子序列从当前位置开始;如果大于0,那么可以加上前面的和,是有正贡献的。注意,返回的不是最后一个子序列和,而是所有子序列和的最大值。

class Solution(object):
    def maxSubArray(self, nums):
        dp = [0] * len(nums)
        dp[0]=nums[0]
        for i in range(1,len(nums)):
            if dp[i-1]<0:
                dp[i] = nums[i]
            else:
                dp[i] = dp[i-1] + nums[i]
        return max(dp)

62 不同路径

给一个m*n的矩阵,从左上角走到右下角,问有多少路径,注意只能往下和往右。

首先考虑一共走(m+n-2)步,其中选择m-1步为下,剩下的指定为右,或者选n-1步为右,剩下的指定为下。那么就是数学问题排列组合, C(m+n-2)(n-1)=(m+n-2)!/[(n-1)!(m+n-2-n+1)!]

其实首先应该考虑的是动态规划,子问题是第[i,j]格子的最多路径,为上格子的值+左格子的值。边界条件横着竖着都只有一条路,所以第一行和第一列都是0。注意这里构造dp初始矩阵第一行+(m-1)个第二行。

class Solution(object):
    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """
        # Method 1
        a, b, c = 1, 1, 1
        for i in range(1,m):
            a *= i
        for j in range(1,n):
            b *= j
        for k in range(1, m+n-1):
            c *= k
        res = c / b / a
        return res

        # Method 2
        dp = [[1]*n]+[[1]+[0]* (n-1) for _ in range(m-1)]
        for i in range(1, m):
            for j in range(1, n):
                dp[i][j] = dp[i-1][j] + dp[i][j-1]
        return dp[-1][-1] 

139 单词拆分

s = “leetcode”, wordDict = [“leet”, “code”]

动态规划s的子串判断是否在wordDict里,就不如建一个表。双指针思想遍历可以避免单指针强行阶段aaaa中aaa和a的情况。(就是包含的情况 )

class Solution(object):
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
        # 不能避免lee和leet都在的时候,认为t不在而错误的情况
        # n = len(s)+1
        # i = 0
        # sum = 0
        # for j in range(n):
        #     if s[i:j] in wordDict:
        #         sum += len(s[i:j])
        #         i = j
        # return True if sum==len(s) else False

        # 双指针,两次遍历,可以识别所有的情况,就解决了上面的问题
        n = len(s)
        dp = [False]*(n+1)
        dp[0] = True
        for i in range(n):
            for j in range(i+1, n+1):
                if dp[i] and s[i:j] in wordDict:
                    dp[j] = True
        return dp[-1]

300 最长上升子序列

[10,9,2,5,3,7,101,18],最长的上升子序列是 [2,3,7,101],它的长度是 4。

动归,子问题是当前最长上升子序列长度,如果当前数字比前面的某个数字大,那么就是他的最长子序列加一覆盖当前数值,但是为了防止小的覆盖大的,所以要判断一下。debug了很久,错误出在最后返回的不是dp[-1]而是max(dp),原问题是子问题的最大值。

class Solution(object):
    def lengthOfLIS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if not nums:
            return 0
        n = len(nums)
        dp = [1]* n
        for j in range(1,n):
            for i in range(j):
                if nums[j]>nums[i]:
                    if dp[i]+1 > dp[j]:
                        dp[j] = dp[i]+1
        return max(dp)

72 编辑距离

书豪师兄说考到好几次。。。就是考虑建立一个二维表格,表格内容表示word1[i]和word2[j]两个字符串之间的编辑距离,如果是第一行和第一列,那么就是最长字符串的尺度。如果是表格内,那就是上面、左面和左上角的+1(也不一定+1,如果最后两个字母一样,那就左上角不用+1)。所以其实多种操作就简单归结为了:A比B长那么B+1,B比A长那么A+1,或者当前替换。所以不需要考虑删除,只需要考虑在短的上加就可以了。ps:考虑A和B其中一个为空,可以不用or,而是长度相乘为0。

class Solution(object):
    def minDistance(self, word1, word2):
        """
        :type word1: str
        :type word2: str
        :rtype: int
        """
        m = len(word1)
        n = len(word2)
        if m*n == 0:
            return m+n

        dp = [[0]* (n+1) for _ in range(m+1)]
        for i in range(m+1):
            dp[i][0] = i
        for j in range(n+1):
            dp[0][j] = j
        for i in range(1,m+1):
            for j in range(1,n+1):
                if word1[i-1] == word2[j-1]:
                    dp[i][j] = min(dp[i][j-1]+1,dp[i-1][j]+1,dp[i-1][j-1])
                else:
                    dp[i][j] = min(dp[i][j-1]+1,dp[i-1][j]+1,dp[i-1][j-1]+1)
        return dp[-1][-1]

174 地下城游戏

难度是困难,但最后还是做出来了,贼开心。题目是地下城游戏,骑士从左上角往右或者往下移动,去救右下角的公主,路上可以掉血、加血,求最小初始化血量。刚开始考虑就是所有路径之和最小,就是消耗最小了,那就是子问题是dp[i] [j]表示到达此点的路径之和。但忽略了一个问题,就是需要每个节点都血量大于0。二维矩阵的问题一般都考察从右下角到左上角遍历,所以考虑二维矩阵的每点数值表示最小初始化血量,那么这个X+当前消耗=min(右侧/下侧)。递推公式变为dp[i] [j] = max( min(dp[i] [j+1], dp[i+1] [j]) - dungeon[i] [j] , 1)。时刻保证dp的值是最小为1的!debug了半天也是没发现这一点… 临界条件就是最后一排和最右一列的数值了。ps,range(3,-1,-1)会返回3 2 1 0,也就是左边包括,右边不包括,-1是倒序。所以边界最后一排从colomn-2一直到0,那么就是range(colomn-2,-1,-1)。虽然题解中代码dp[i] [j] = max( min(dp[i] [j+1], dp[i+1] [j]) , 1) - dungeon[i] [j] ,但是我感觉应该是全部的max(…, 1)。不过试了一下,都能ac,神奇。

class Solution(object):
    def calculateMinimumHP(self, dungeon):
        """
        :type dungeon: List[List[int]]
        :rtype: int
        """
        row, colomn = len(dungeon), len(dungeon[0])
        dp = [[0] * colomn for _ in range(row)]
        dp[-1][-1] = max(1-dungeon[-1][-1],1)
        for i in range(colomn-2,-1,-1):
            dp[-1][i] = max(dp[-1][i+1]-dungeon[-1][i],1)
        for j in range(row-2,-1,-1):
            dp[j][-1] = max(dp[j+1][-1]-dungeon[j][-1],1)
        for i in range(row-2,-1,-1):
            for j in range(colomn-2,-1,-1):
                dp[i][j] = max(min(dp[i][j+1], dp[i+1][j])-dungeon[i][j],1)
        return max(dp[0][0],1)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值