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

动态规划(Dynamic Programming,DP)

根据百度百科上的定义:

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

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

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

动态规划是一种以空间换时间的技术。我看了几个例子,感觉动态规划其实是递归的反推:
递归: 从要求的的问题(顶层)开始,一层一层递归到出口条件,然后再逐层返回
动态规划:出口条件开始 ,逐层上升到要求解的问题。

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

70 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

爬到第n阶有两种可能

  1. 现在站在第n-2阶,再一次性爬两阶;
  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 1
        dn = [0]*(n+1)
        dn[0] = 1
        dn[1] = 1
        for 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_out
        if prices == []:
            return 0
        min_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 最长回文子串

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

  1. aba
  2. abba

所以要用宽度为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 ] ∧ ( S i ​ = = S j ) P[i,j] = P[i+1,j-1]∧(S i​==S j) P[i,j]=P[i+1,j1](Si==Sj)
这就是状态转移方程。
至于边界,如我之前描述的两种回文串,有:
P [ i , i ] = T r u e , P [ i , i + 1 ] = ( S i = = S i + 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+1
        for l in range(n):
            # 枚举子串的起始位置 i,这样可以通过 j=i+l 得到子串的结束位置
            for i in range(n):
                j = i + l
                if j >= len(s):
                    break
                if l == 0:
                    dp[i][j] = True
                elif 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]
D P [ i ] [ j ] = D P [ i − 1 ] [ j − 1 ] + 1 DP[i][j] = DP[i-1][j-1] +1 DP[i][j]=DP[i1][j1]+1
如果s1[i]!=s2[j]
D P [ i ] [ j ] = m a x ( D P [ i ] [ j − 1 ] , D P [ i − 1 ] [ j ] ) DP[i][j] = max(DP[i][j-1], DP[i-1][j]) DP[i][j]=max(DP[i][j1],DP[i1][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] + 1
                else:
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1])
        return max(max(dp))

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值