第三周 动态规划
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)