牛客面试必刷TOP101——动态规划

部分题目还没写,等待更新-----------

斐波那契数列 BM62

原题

思路:题目直接给出了公式f[n] = f[n-1] + f[n-2],如果使用递归,有些值其实是重复使用的,比如f[4] = f[3] + f[2],而f[3] = f[2] + f[1],f[2]就会计算两次,所以可以动态规划求解。a0表示f[n-2],a1表示f[n-1]。

class Solution:
    def Fibonacci(self , n: int) -> int:
        # write code here
        if n <= 2:
            return 1
        a0 = 1
        a1 = 1
        for i in range(3,n+1):
            t = a0 + a1
            a0 = a1
            a1 = t
        return a1
跳台阶 BM63

原题

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

思路:和BM62一样

class Solution:
    def jumpFloor(self , number: int) -> int:
        # write code here
        if number <= 2:
            return number
        a0 = 1
        a1 = 2
        for i in range(3,number+1):
            a0,a1 = a1,a0+a1
        return a1
最小花费爬楼梯 BM64

原题

给定一个整数数组 cost ,其中 cost[i]是从楼梯第i上爬需要支付的费用,下标从0开始。一旦你支付此费用,即可选择向上爬一个或者两个台阶。你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。请你计算并返回达到楼梯顶部的最低花费。

思路

  • 使用数组f作为辅助数组,f[i]表示爬到第i个台阶的最小花费
  • 状态转移方程f[i] = min(f[i-1],f[i-1])+cost[i]
  • 计算到达楼梯顶部的最低花费,也就是说返回的应该是f[n],而不是f[n-1]
class Solution:
    def minCostClimbingStairs(self , cost: List[int]) -> int:
        # write code here
        if len(cost) == 1:
            return cost[0]
        elif len(cost) == 2:
            return min(cost[0],cost[1])
        else:
            f = [0 for i in range(len(cost)+1)]
            f[0] = cost[0]
            f[1] = cost[1]
            for i in range(2,len(cost)+1):
                if i < len(cost):
                    f[i] = min(f[i-1],f[i-2])+cost[i]
                else:
                    f[i] = min(f[i-1],f[i-2])
            return f[len(cost)]
最长公共子序列(二) BM65

原题

给定两个字符串str1和str2,输出两个字符串的最长公共子序列。如果最长公共子序列为空,则返回"-1"。目前给出的数据,仅仅会存在一个最长的公共子序列

数据范围: 0 ≤ ∣ s t r 1 ∣ , ∣ s t r 2 ∣ ≤ 20000 0 \le |str1|,|str2| \le 20000 0str1,str220000

要求:空间复杂度 O ( n 2 ) O(n^2) O(n2 ,时间复杂度 O ( n 2 ) O(n^2) O(n2)

思路:动态规划

可以和最长公共子串一起看,最长公共子串要求最终求得的公共子串是连续的,而本题的不同点在于非连续,所以递推公式的区别在于,如果 s t r 1 [ i ] str1[i] str1[i]不等于 s t r 2 [ j ] str2[j] str2[j]时,如何求解 d p [ i ] [ j ] dp[i][j] dp[i][j]

假设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示str1的前i个字符和str2前j个字符的最长公共子序列的长度,则有以下递推公式:

  • 如果 s t r 1 [ i ] = s t r 2 [ j ] str1[i]=str2[j] str1[i]=str2[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
  • 如果不相等,则 d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) dp[i][j]=max(dp[i-1][j],dp[i][j-1]) dp[i][j]=max(dp[i1][j],dp[i][j1])

求出最长长度后,需要再返回找到子序列,从dp二维数组的右下角开始遍历

  • 如果 s t r 1 [ i ] = s t r 2 [ j ] str1[i]=str2[j] str1[i]=str2[j],就把当前字符加到数组中,那下一步一定是往左上角走;
  • 如果不相等,根据前面 d p [ i ] [ j ] dp[i][j] dp[i][j]的递推公式,可能是通过 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]计算得到,也可能是通过 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1]计算得到,如何确定下一步是往左走还是向上走。
    • 如果过 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1]大,显然向左走;如果过 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]大,则向上走;如果相等该如何选择呢?
    • 这个地方可以在求最长长度之前做一个预处理,令s1存储输入较长的字符串,而s2存储较短的字符串
    • d p [ i − 1 ] [ j ] = d p [ i ] [ j − 1 ] dp[i-1][j]=dp[i][j-1] dp[i1][j]=dp[i][j1]时,向上走
class Solution:
    def LCS(self , s1: str, s2: str) -> str:
        # write code here
        if not s1 or not s2:
            return "-1"
        if len(s1) < len(s2):
            s1,s2 = s2,s1
        dp = [[0]*(len(s2)+1) for _ in range(len(s1)+1)]
        for i in range(1,len(s1)+1):
            for j in range(1,len(s2)+1):
                if s1[i-1] == s2[j-1]:
                    dp[i][j] = dp[i-1][j-1] + 1
                else:
                    dp[i][j] = max(dp[i][j-1] , dp[i-1][j])
        s = []
        i,j = len(s1),len(s2)
        while i >0 and j > 0:
            if s1[i-1] == s2[j-1]:
                s.append(s1[i-1])
                i -= 1
                j -= 1
            else:
                if dp[i-1][j] >= dp[i][j-1]:
                    i -= 1
                else:
                    j -= 1
        if not s:
            return "-1"
        else:
            return "".join(s[::-1])
最长公共子串 BM66

原题

给定两个字符串str1和str2,输出两个字符串的最长公共子串。题目保证str1和str2的最长公共子串存在且唯一。

思路

  1. 动态规划

    • 使用一个二维数组dp,dp[i][j]表示str1中以第i个字符结尾,str2以第j个字符结尾的最长公共子串的长度

    • str1[i] = str2[j]时,有如下状态转移方程

      d p [ i , j ] = d p [ i − 1 ] [ j − 1 ] + 1 , s t r 1 [ i ] = = s t r 2 [ j ] dp[i,j] = dp[i-1][j-1] + 1 , str1[i] == str2[j] dp[i,j]=dp[i1][j1]+1,str1[i]==str2[j]

    • str1[i] 和str2[j]不相等时,dp[i,j] = 0

    • 时间复杂度和空间复杂度均为O(m*n),python只能过7组用例

    class Solution:
        def LCS(self , str1: str, str2: str) -> str:
            # write code here
            m = len(str1)
            n = len(str2)
            dp = [[0]*n for i in range(m)]
            for i in range(m):
                if str2[0] == str1[i]:
                    dp[i][0] = 1
            for j in range(n):
                if str1[0] == str2[j]:
                    dp[0][j] = 1
            index = 0
            max_len = 0
            for i in range(1,m):
                for j in range(1,n):
                    if str1[i] == str2[j]:
                        dp[i][j] = dp[i-1][j-1] + 1
                        if dp[i][j] > max_len:
                            max_len = dp[i][j]
                            index = i
                    else:
                        dp[i][j] = 0     
            return str1[index-max_len+1:index+1]       
    
  2. 滑动窗口

    • 定义双指针i和j
    • 比较str1区间[i,j]内元素在str2中是否存在
      • 如果存在,则使用res记录最长公共子串
      • 不存在,左指针i右移
    • 窗口是一直在增大的,当i指针移动时,j指针也在后移,所以只要[i,j]区间内元素在str2中,那该区间就存储的最长公共子串
class Solution:
    def LCS(self , str1: str, str2: str) -> str:
        # write code here
        i = 0
        res = ""
        for j in range(len(str1)):
            if str1[i:j+1] in str2:
                res = str1[i:j+1]
            else:
                i += 1
        return res
不同路径的数目(一)BM67

原题

一个机器人在m×n大小的地图的左上角(起点)。机器人每次可以向下或向右移动。机器人要到达地图的右下角(终点)。可以有多少种不同的路径从起点走到终点?

思路

  • 定义二维数组,dp[i][j]表示机器人到达第i行第j列格子的路径数目

  • 机器人只能向右、向下移动,所以状态转移方程为

    d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] dp[i][j] = dp[i-1][j]+dp[i][j-1] dp[i][j]=dp[i1][j]+dp[i][j1]

class Solution:
    def uniquePaths(self , m: int, n: int) -> int:
        # write code here
        dp = [[0]*n for _ in range(m)]
        for i in range(m):
            dp[i][0] = 1
        for j in range(n):
            dp[0][j] = 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[m-1][n-1]

进阶版:空间复杂O(1),时间复杂度O(min(m,n))

从矩阵左上角走到矩阵右下角,总共需要往下走m−1步,往右走n−1步,不同的走法路径在于往下和往右的组合情况,即在一堆往下和往右的序列中,每种排列的情况,序列一共有m+n−2个位置,选择其中n*−1个位置作为往下,即不同的走法有 C m + n − 2 n − 1 = ( m + n − 2 ) ! ( n − 1 ) ! ( m − 1 ) ! C^{n-1}_{m+n-2}=\frac{(m+n-2)!}{(n-1)!(m-1)!} Cm+n2n1=(n1)!(m1)!(m+n2)!种情况。

class Solution:
    #计算阶乘
    def fun(self, n: int) -> int: 
        sum = 1
        #连乘
        for i in range(1, n + 1):
            sum *= i
        return sum
    def uniquePaths(self , m: int, n: int) -> int:
        #公式计算
        return self.fun(m + n - 2) // (self.fun(m - 1) * self.fun(n - 1))
矩阵的最小路径和 BM68

原题

给定一个 n * m 的矩阵 a,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,输出所有的路径中最小的路径和。

思路:和BM67相同,状态转移方程略微差异

求解的是最小的路径和,先定义二维数组dp

  • d p [ i ] [ j ] dp[i][j] dp[i][j]表示到达[i,j]的最小路径和,其要么从 [ i − 1 , j ] [i-1,j] [i1,j]右移得到,要么通过 [ i , j − 1 ] [i,j-1] [i,j1]下移得到.
  • 状态转移方程 d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) + m a t r i x [ i ] [ j ] dp[i][j] = min(dp[i-1][j],dp[i][j-1])+matrix[i][j] dp[i][j]=min(dp[i1][j],dp[i][j1])+matrix[i][j]
class Solution:
    def minPathSum(self , matrix: List[List[int]]) -> int:
        # write code here
        m = len(matrix)
        n = len(matrix[0])
        dp = [[0]*n for i in range(m)]
        dp[0][0] = matrix[0][0]
        for i in range(1,m):
            dp[i][0] = dp[i-1][0] + matrix[i][0]
        for j in range(1,n):
            dp[0][j] = dp[0][j-1] + matrix[0][j]
        for i in range(1,m):
            for j in range(1,n):
                dp[i][j] = min(dp[i-1][j],dp[i][j-1])+matrix[i][j]
        return dp[m-1][n-1]
把数字翻译成字符串 BM69

原题

有一种将字母编码成数字的方式:‘a’->1, ‘b->2’, … , ‘z->26’。我们把一个字符串编码成一串数字,再考虑逆向编译成字符串。由于没有分隔符,数字编码成字母可能有多种编译结果,例如 11 既可以看做是两个 ‘a’ 也可以看做是一个 ‘k’ 。但 10 只可能是 ‘j’ ,因为 0 不能编译成任何结果。

现在给一串数字,返回有多少种可能的译码结果

数据范围:字符串长度满足 0 < n ≤ 90 0 < n \le 90 0<n90

进阶:空间复杂度 O ( n ) O(n) O(n),时间复杂度 O ( n ) O(n) O(n)

思路:动态规划

dp[i]表示前i个数字译码结果个数;

  • 如果当前字符为‘0’
    • 判断前一个字符,如果前一个字符大于2,比如30,说明无法翻译,直接返回0
    • 如果前面字符小于2,加入是10,说明nums[i]和nums[i-1]组合进行翻译,因此$dp[i]=dp[i-1] $
  • 当前字符不为’0’
    • 判断 n u m s [ i − 1 ] + n u m s [ i ] nums[i-1]+nums[i] nums[i1]+nums[i]是否可以翻译为字母,即是否小于26(还要判断大于10,用于确定前一位不为1),如果可以,那有两种翻译方式,要么nums[i]和nums[i-1]组合翻译,要么分开翻译;$dp[i]=dp[i-1]+dp[i-2] $
    • 如果不小于26,说明只能单个数字翻译,所以$dp[i]=dp[i-1]
class Solution:
    def solve(self , nums: str) -> int:
        # write code here
        dp = [0]*  len(nums)
        dp[0] = 1
        for i in range(1,len(nums)):  
            if nums[i] == '0':
                if nums[i-1] > '2':
                    return 0
                else:
                    dp[i] = dp[i-1]
            elif '11' <= nums[i-1]+nums[i] <= '26':
                if i >= 2:
                    dp[i] = dp[i-1] + dp[i-2]
                else:
                    dp[i] = dp[i-1] + 1 
            else:
                    dp[i] = dp[i-1]
        return dp[len(nums)-1]
兑换零钱(一)BM70

原题
给定数组arr,arr中所有的值都为正整数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个aim,代表要找的钱数,求组成aim的最少货币数。

如果无解,请返回-1.

数据范围:数组大小满足$ 0 \le n \le 10000 , 数 组 中 每 个 数 字 都 满 足 , 数组中每个数字都满足 0 < val \le 10000,0 \le aim \le 5000$

要求:时间复杂度 $O(n \times aim) , 空 间 复 杂 度 ,空间复杂度 O(aim)$。

思路:动态规划

0-1背包问题,使用dp[i]表示凑出i元需要的最小货币数

  • 初始化 d p [ 0 ] = 0 dp[0]=0 dp[0]=0,dp其他元素为aim+1,dp长度为aim+1
  • 依次遍历dp求得1-aim元所需要的最小的货币数
  • 求解dp[i]
    • 遍历array数组,如果i>array[j],则可以考虑用array[j]找零
    • 递推公式 d p [ i ] = m i n ( d p [ i ] , d p [ i − a r r a y [ j ] ] + 1 ) dp[i]=min(dp[i],dp[i-array[j]]+1) dp[i]=min(dp[i],dp[iarray[j]]+1)
  • 最后判断 d p [ a i m ] dp[aim] dp[aim]值是否大于 a i m aim aim,如果大于说明在求解过程中没有更新,即无解,返回-1;否则返回 d p [ a i m ] dp[aim] dp[aim]
class Solution:
    def minMoney(self , arr: List[int], aim: int) -> int:
        # write code here
        if aim == 0:
            return 0
        if not arr or min(arr) > aim :
            return -1
        dp = [aim+1 for i in range(aim+1)]
        dp[0] = 0
        for i in range(1,aim+1):
            for j in range(len(arr)):
                if i >= arr[j]:
                    dp[i] = min(dp[i],dp[i-arr[j]] + 1)
        if dp[aim] > aim:
            return -1
        else:
            return dp[aim]
最长上升子序列(一) BM71

原题

思路:dp为一维数组,dp[i]表示数组前i个元素中以i结尾的最长上升子序列的长度

  • dp初始化为1,表示以i结尾的最长上升子序列的长度,初始化以i结尾的最长上升子序列只有自己,所以是1
  • 第一层循环:想知道整个数组最长子序列,先将其分为一个个子问题,即先求解前i个元素的最长上升子序列长度。i从0开始,当i=n-1时 ,表示已经求解出整个数组的最长子序列长度;所以遍历所有i的位置,即 0 < = i < = n − 1 0<=i<=n-1 0<=i<=n1
  • 第二层循环:
    • 通过dp[i]的定义,首先arr[i]一定是选中的,因此想要知道dp[i]的值,需要先知道dp[0…i-1]的值。
    • 0 < = j < = i − 1 0<=j<=i-1 0<=j<=i1,如果 a r r [ j ] < a r r [ i ] arr[j] < arr[i] arr[j]<arr[i],说明以arr[j]结尾的上升子序列加上arr[i]一定也是一个上升子序列,所以i位置的最长上升子序列长度 d p [ i ] = m a x ( d p [ i ] , d p [ j ] + 1 ) dp[i] = max(dp[i] , dp[j] + 1) dp[i]=max(dp[i],dp[j]+1)
  • 最后返回max(dp)即可得到最长上升子序列
class Solution:
    def LIS(self , arr: List[int]) -> int:
        # write code here
        if not arr:
            return 0
        dp = [1 for i in range(len(arr))]
        for i in range(len(arr)):
            for j in range(i):
                if arr[j] < arr[i]:
                    dp[i] = max(dp[i],dp[j]+1)
        return max(dp)
连续子数组的最大和 BM72

原题

1、要么选取当前i元素,要么从第i个元素开始:d[i]=max(d[i-1]+array[i],array[i]);特殊情况,如果d[i-1]<0,则直接从第i个元素开始,时间复杂度O(n),空间复杂度O(n)

class Solution:
    def FindGreatestSumOfSubArray(self , array: List[int]) -> int:
        # write code here
        dp = [0]*(len(array)+1)
        res = array[0]
        for i in range(1,len(array)+1):
            if dp[i-1] < 0:
                dp[i] = array[i-1]
            else:
                dp[i] = max(dp[i-1]+array[i-1],array[i-1])
            res = max(res,dp[i])
        return res

2、进阶版,使用一个元素记录结果

  • 使用dp记录前i-1个元素的最大和
  • 每次都先加上当前i元素,更新res最大和;如果dp小于0,则直接令dp=0
class Solution:
    def FindGreatestSumOfSubArray(self , array: List[int]) -> int:
        # write code here
        dp = 0
        res = array[0]
        for i in range(len(array)):
            dp += array[i]
            res = max(res,dp)
            if dp < 0:
                dp = 0
        return res
最长回文子串 BM73

原题

对于长度为n的一个字符串A(仅包含数字,大小写英文字母),请设计一个高效算法,计算其中最长回文子串的长度。

要求:空间复杂度O*(1),时间复杂度 O*(n2)

进阶: 空间复杂度O*(n),时间复杂度O*(n)

思路1:【贪心+滑动窗口+双指针 】

  • 使用max_len变量保存最长回文子串的长度

  • 求最长的回文子串,贪心思想,从最长的字符串开始判断,即开始假定最长回文子串的长度为max_len = n

  • 使用双指针 l l l r r r表示当前窗口的左右边界值,判断当前窗口的字符串是否为回文串

    • i , j = l , r i,j = l,r i,j=l,r,如果 A [ i ] ! = A [ j ] A[i] != A[j] A[i]!=A[j],不是回文子串,直接跳出循环
    • 是回文串,则直接返回max_len
  • 如果不是回文子串,则窗口右移一个单位,判断是否为回文串;

    • 直到遍历完所有长度为max_len的子串,都没有出现回文子串‘那么max_len减1
    • 重新令滑动窗口的左边界从0位置开始,右边界 r = l + m a x l e n − 1 r = l + max_len -1 r=l+maxlen1
  • 时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂O(1)

class Solution:
    def getLongestPalindrome(self , A: str) -> int:
        # write code here
        if not str:
            return 0
        max_len = len(A)
        while max_len > 1:
            l = 0
            r = l + max_len -1
            while r < len(A):
                i = l
                j = r
                while i <= j:
                    if A[i] == A[j]:
                        i += 1
                        j -= 1
                    else:
                        break
                if i >= j:
                    return max_len
                l += 1
                r = l + max_len -1
            max_len -= 1
        return max_len

思路2:动态规划

思路3:马拉车算法 O(n)时间复杂度

编辑距离 BM75

原题

给定两个字符串 str1 和 str2 ,请你算出将 str1 转为 str2 的最少操作数。

你可以对字符串进行3种操作:

1.插入一个字符

2.删除一个字符

3.修改一个字符。

字符串长度满足 1 ≤ n ≤ 1000 1 \le n \le 1000 1n1000 ,保证字符串中只出现小写英文字母。

class Solution:
    def editDistance(self , str1: str, str2: str) -> int:
        # write code here
        dp = [[0]*(len(str2)+1) for _ in range(len(str1)+1)]
        for i in range(1,len(str2)+1):
            dp[0][i] = dp[0][i-1] + 1
        for i in range(1,len(str1)+1):
            dp[i][0] = dp[i-1][0] + 1
        for i in range(1,len(str1)+1):
            for j in range(1,len(str2)+1):
                if str1[i-1] == str2[j-1]:
                    dp[i][j] = dp[i-1][j-1]
                else:
                    dp[i][j] = min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1
        return dp[len(str1)][len(str2)]
打家劫舍(一)BM78

原题
你是一个经验丰富的小偷,准备偷沿街的一排房间,每个房间都存有一定的现金,为了防止被发现,你不能偷相邻的两家,即,如果偷了第一家,就不能再偷第二家;如果偷了第二家,那么就不能偷第一家和第三家。给定一个整数数组nums,数组中的元素表示每个房间存有的现金数额,请你计算在不被发现的前提下最多的偷窃金额。

数据范围:数组长度满足 1 ≤ n ≤ 2 × 1 0 5 1≤n≤2×10^5 1n2×105 ,数组中每个值满足$1≤num[i]≤5000 $

思路1

定义一个一维数组dp,dp[i]存储【最后一家偷第i家可以偷窃的最大金额】

小偷为了防止被发现,只能间隔偷,所以如果小偷要偷第i家,那一定不能偷i-1家,所以可以建立状态转移方程 d p [ i ] = m a x ( d p [ : i − 2 ] ) + n u m s [ i ] dp[i] = max(dp[:i-2])+nums[i] dp[i]=max(dp[:i2])+nums[i]

  1. 小偷最后一个偷第i家可以获得的最大金额为:偷前i-2家的最大金额+第i家可以偷得的金额
  2. 因此可以使用一个变量max_money存储前i-2家的最大金额
  3. 根据max_money计算dp[i],计算完后,更新max_money值, m a x _ m o e n e y = m a x ( m a x _ m o e n e y , d p [ i − 1 ] ) max\_moeney = max(max\_moeney,dp[i-1]) max_moeney=max(max_moeney,dp[i1])
  4. 最后max_moeney存储的是前n-1家可以偷得的最大金额,因此返回值可以直接返回max_moeney = dp[i-1]和dp[n-1]的较大者即可
class Solution:
    def rob(self , nums: List[int]) -> int:
        # write code here
        if len(nums) == 0:
            return 0
        elif len(nums) <= 2:
            return max(nums)
        else:
            dp = [0 for i in range(len(nums))]
            dp[0] = nums[0]
            dp[1] = nums[1]
            max_moeney = dp[0]
            for i in range(2,len(nums)):
                dp[i] = max_moeney + nums[i]
                if max_moeney < dp[i-1]:
                    max_moeney = dp[i-1]
            return max(max_moeney,dp[-1])

思路2

两个思路其实都是动态规划,不过是dp[i]的含义不同,这个是看了官方题解的思路,不得不说我还是太菜了,确实上面那个状态转移方程不够标准

定义:dp[i]存储偷前i家可以获得的最大金额,对于第i家可以选择偷或者不偷,如果偷,则 d p [ i ] = d p [ i − 2 ] + n u m s [ i ] dp[i]=dp[i-2]+nums[i] dp[i]=dp[i2]+nums[i];如果不偷,则 d p [ i ] = d p [ i − 1 ] dp[i]=dp[i-1] dp[i]=dp[i1]

所以状态转移方程为: d p [ i ] = m a x ( d p [ i − 2 ] + n u m s [ i ] , d p [ i − 1 ] ) dp[i]=max(dp[i-2]+nums[i],dp[i-1]) dp[i]=max(dp[i2]+nums[i],dp[i1])

这个状态转移方程更容易理解,而且最终的结果即为dp数组的最后一个元素:dp[len(nums)-1

class Solution:
    def rob(self , nums: List[int]) -> int:
        # write code here
        dp = [0]*(len(nums)+1)
        dp[1] = nums[0]
        for i in range(2,len(nums)+1):
            dp[i] = max(dp[i-1],nums[i-1]+dp[i-2])
        return dp[len(nums)]
打家劫舍(二) BM79

原题

你是一个经验丰富的小偷,准备偷沿湖的一排房间,每个房间都存有一定的现金,为了防止被发现,你不能偷相邻的两家,即,如果偷了第一家,就不能再偷第二家,如果偷了第二家,那么就不能偷第一家和第三家。沿湖的房间组成一个闭合的圆形,即第一个房间和最后一个房间视为相邻。给定一个长度为n的整数数组nums,数组中的元素表示每个房间存有的现金数额,请你计算在不被发现的前提下最多的偷窃金额。

数据范围:数组长度满足 1 ≤ n ≤ 2 × 1 0 5 1≤n≤2×10^5 1n2×105 ,数组中每个值满足$

思路

由于是环形,所以需要分两种情况讨论

  • 所以如果偷了第一家,最后一家不能偷,所以是1-(n-1),即dp[1] = nums[0]
  • 如果不偷第一家,则最后一家可以偷,所以是2-n,dp[1] = 0
  • 最后返回两种情况的较大者即可
class Solution:
    def rob(self , nums: List[int]) -> int:
        dp1 = [0]*(len(nums)+1)
        dp1[1] = nums[0]
        for i in range(2,len(nums)):
            dp1[i] = max(dp1[i-1],nums[i-1]+dp1[i-2])
        res = dp1[len(nums)-1]
        
        dp2 = [0]*(len(nums)+1)
        for i in range(2,len(nums)+1):
            dp2[i] = max(dp2[i-1],nums[i-1]+dp2[i-2])
        return max(res,dp2[len(nums)]
买卖股票的最好时机(一) BM80

原题

假设你有一个数组prices,长度为n,其中prices[i]是股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益

1.你可以买入一次股票和卖出一次股票,并非每天都可以买入或卖出一次,总共只能买入和卖出一次,且买入必须在卖出的前面的某一天

2.如果不能获取到任何利润,请返回0

3.假设买入卖出均无手续费

思路:对于第j天,我们可以选择卖出股票(前提在前j-1天买入了股票);或是买入股票。

  • res表示获取的最大利润;min_buy表示最低买入的股票价格;初始值res=0,min_buy为第一天买入的股票价格;
  • 从第1天开始遍历,对于第i天而言,要么卖出股票,计算获利,并更新相应最大利润;
  • 要么买入股票,和min_buy最低买入价格比较,如果当然更低,更新min_buy
class Solution:
    def maxProfit(self , prices: List[int]) -> int:
        # write code here
        min_buy = prices[0]
        res = 0
        for i in range(1,len(prices)):
            t = prices[i] - min_buy
            if res < t:
                res = t
            min_buy = min(min_buy,prices[i])
        return res
买卖股票的最好时机(二) BM81

原题

假设你有一个数组prices,长度为n,其中prices[i]是某只股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益

  1. 你可以多次买卖该只股票,但是再次购买前必须卖出之前的股票

  2. 如果不能获取收益,请返回0

  3. 假设买入卖出均无手续费

思路:和(一)相同,使用min_buy记录买入的最小价格;res记录当然获得的最大利润

区别在于:由于(一)中,只能买卖一次,当遇到更大的res时,更新res值;在本题中,可以买卖多次,因此需要进行累加。需要考虑:什么时候进行卖出,进行下一次的买入。我们期望以最低价格买入,最高价格卖出,因此对于第i天是否卖出,还需要参考第i+1天的股票价格。

举个例子,【1,4,8,6】,在第一天买入,res初始化为0,min_buy=1,从第二天开始

  • prices[1] = 4,大于最低买入价格,可以考虑卖出,这时参考第i+1天,即第三天价格,发现第三天价格比第二天更高,所以可以在第三天再考虑是否卖出
  • prices[2] = 8,第三天大于最低买入价格,同时第4天的价格比第三天低 ,基于贪心思想,显然第三天应该卖出股票;同时,min_buy初始化为第i+1天的股票价格。

从上面的例子可以总结出,当后一天的股票价格比当天低的时候,应该卖出股票,将获取利润累加到res中。

步骤:

  1. 如果第i天的股票价格比min_buy更低,则更新min_buy,否则执行2
  2. 比较第i+1天的价格,如果 p r i c e s [ i + 1 ] < p r i c e s [ i ] prices[i+1] < prices[i] prices[i+1]<prices[i],则第i天应该卖出股票,计算利润累加到res中;更新min_buy,进行下一次买入, m i n b u y = p r i c e s [ i + 1 ] min_buy = prices[i+1] minbuy=prices[i+1]
class Solution:
    def maxProfit(self , prices: List[int]) -> int:
        # write code here
        min_buy = prices[0]
        res = 0
        for i in range(len(prices)-1):
            if prices[i] < min_buy:
                min_buy = prices[i]
            else:
                if prices[i+1] < prices[i]:
                    res += prices[i] - min_buy
                    min_buy = prices[i+1]
        if prices[i+1] > min_buy:
            res += prices[i+1] - min_buy
        return res
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秃头嘤嘤魔

感谢厚爱

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值