失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > leetcode刷题记录:算法(九)动态规划

leetcode刷题记录:算法(九)动态规划

时间:2021-09-10 02:37:42

相关推荐

leetcode刷题记录:算法(九)动态规划

动态规划(Dynamic Programming,DP)

根据百度百科上的定义:

动态规划是求解决策过程最优化的过程

然后各个博客里对动态规划的描述为:

将需要求解的大问题,分解为一个个的小问题,并在计算过程中保存每个小问题的结果以避免重复计算。

动态规划是一种以空间换时间的技术。我看了几个例子,感觉动态规划其实是递归的反推:

递归:从要求的的问题(顶层)开始,一层一层递归到出口条件,然后再逐层返回

动态规划:出口条件开始 ,逐层上升到要求解的问题。

动态规划感觉是迄今为止最难理解的一种算法了,我找了些资料看,感觉还是没有领会到精髓,做题熟悉吧。

70 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

爬到第n阶有两种可能

现在站在第n-2阶,再一次性爬两阶;现在站在第n-1阶,再爬一阶

假设两种情况分别包含f(n-2)和f(n-1)种可能,爬到第n层有f(n)种可能,那么有:

f(n) = f(n-1) + f(n-2)

这就是本道题的状态转移方程。同时本题的边界条件:

f(0) = 1, f(1) = 1

接下来题就很好解了

class Solution:def climbStairs(self, n: int) -> int:if n==0 or n==1:return 1dn = [0]*(n+1)dn[0] = 1dn[1] = 1for i in range(2, n+1):dn[i] = dn[i-1]+dn[i-2]return dn[n]

121 买卖股票的最佳时机

和上一道题不一样,买卖股票涉及到买和卖两种情况,不能单纯的用一个一维数组去维护历史结果。

很容易理解:

第n天卖出的收益为,当天的股票价格减去第n-1天之前的最低买入价格,即:

sell[n] = prices[n] - min_buy[n-1]

第n天卖出的最大收益为,当天卖出收益和历史最大收益中较大的那个,即:

max_sell[n] = max (max_sell[n-1], prices[n] - min_buy[n-1])

而最低买入价格很好求,为

min_buy[n] = min(min_buy[n-1], prices[n])

所以代码为:

class Solution:def maxProfit(self, prices: List[int]) -> int:# 注释掉的部分为更节省空间和时间的做法# 但是我对这是不是动态规划表示怀疑# 所以还是写了个笨笨的真正的动态规划# 两者的差别就是前一种做法只维护了两个常量# if prices == []:#return 0# min_in = prices[0]# max_out = 0# for i in range(len(prices)):#min_in = min(min_in, prices[i])#max_out = max(max_out,prices[i]- min_in)# return max_outif prices == []:return 0min_buy, max_sell = [0]*len(prices), [0]*len(prices)min_buy[0] = prices[0]for i in range(1,len(prices)):min_buy[i] = min(min_buy[i-1], prices[i])max_sell[i] = max(max_sell[i-1],prices[i]- min_buy[i-1])return max(max_sell)

5 最长回文子串

一开始看到这道题的时候,想用滑动窗口解,因为回文串存在两种情况:

abaabba

所以要用宽度为2和3的窗口分别遍历整个字符串,找到回文串的“种子”后,以这个种子为中心进行扩展,寻找最长的字符串,每次的时间复杂度为O(N),整体的时间复杂度也是O(N)

动态规划法没什么思路,看了答案,总结如下:

对于给定长度为N的字符串s,如果存在一个子串s[i:j]是回文串,那么必然有s[i+1,j-1]必然是回文串,如果以P[i, j]来记录s[i, j]是不是回文串,则有:

P[i,j]=P[i+1,j−1]∧(Si​==Sj)P[i,j] = P[i+1,j-1]∧(S i​==S j) P[i,j]=P[i+1,j−1]∧(Si​==Sj)

这就是状态转移方程。

至于边界,如我之前描述的两种回文串,有:

P[i,i]=True,P[i,i+1]=(Si==Si+1)P[i,i] = True, P[i,i+1] = (Si==Si+1) P[i,i]=True,P[i,i+1]=(Si==Si+1)

最终答案为所有值为True的P(i,j)中,j-i+1最大的那个。

还一个需要注意的是,不能用两个指针取遍历,那样会漏掉一些结果,我就吃了这个亏吃了很久,用pycharm进行debug才发现这一点,比如:

s =‘aaaaa’

如果我用双循环进行遍历:

for i in range(n):for j in range(i,n):pass

那我们会先得到i=0,j=4,此时P(1,3)还没能进行数据更新,还是False,导致P(0,4)判断错误。

所以需要以长度进行遍历。

class Solution:def longestPalindrome(self, s: str) -> str:n = len(s)dp = [[False] * n for _ in range(n)]ans = ""# 枚举子串的长度 l+1for l in range(n):# 枚举子串的起始位置 i,这样可以通过 j=i+l 得到子串的结束位置for i in range(n):j = i + lif j >= len(s):breakif l == 0:dp[i][j] = Trueelif l == 1:dp[i][j] = (s[i] == s[j])else:dp[i][j] = (dp[i + 1][j - 1] and s[i] == s[j])if dp[i][j] and l + 1 > len(ans):ans = s[i:j+1]return ans

1143 最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

示例 :

输入:text1 = "abcde", text2 = "ace" 输出:3 解释:最长公共子序列是 "ace",它的长度为 3。

最长公共子序列(Longest Common Subsequence,简称 LCS)是一道比较经典的题目。

首先考虑设计DP数组,对于长度为m和n的字符串s1和s2,有大小为m*n的二维数组DP,DP[i][j]表示:

对于s1[0:i]和s2[0:j],他们的LCS长度为DP[i][j]。

设计好数组,后面的内容就水到渠成了,状态转移方程为:

如果s1[i]==s2[j]

DP[i][j]=DP[i−1][j−1]+1DP[i][j] = DP[i-1][j-1] +1 DP[i][j]=DP[i−1][j−1]+1

如果s1[i]!=s2[j]

DP[i][j]=max(DP[i][j−1],DP[i−1][j])DP[i][j] = max(DP[i][j-1], DP[i-1][j]) DP[i][j]=max(DP[i][j−1],DP[i−1][j])

写成代码:

class Solution:def longestCommonSubsequence(self, text1: str, text2: str) -> int:m = len(text1)n = len(text2)dp = [[0] *(n+1) for _ in range(m+1)]for i in range(1, m+1): # 这里从1开始是为了让dp[i-1][j-1]不溢出,同时提供初值for j in range(1, n+1):if text1[i-1] == text2[j-1]:dp[i][j] =dp[i-1][j-1] + 1else:dp[i][j]=max(dp[i-1][j],dp[i][j-1])return max(max(dp))

还是挺耗费时间的,跑了400+ms。

如果觉得《leetcode刷题记录:算法(九)动态规划》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。