部分题目还没写,等待更新-----------
列表
斐波那契数列 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 0≤∣str1∣,∣str2∣≤20000
要求:空间复杂度 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[i−1][j−1]+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[i−1][j],dp[i][j−1])
求出最长长度后,需要再返回找到子序列,从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[i−1][j]计算得到,也可能是通过 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j−1]计算得到,如何确定下一步是往左走还是向上走。
- 如果过 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j−1]大,显然向左走;如果过 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][j]大,则向上走;如果相等该如何选择呢?
- 这个地方可以在求最长长度之前做一个预处理,令s1存储输入较长的字符串,而s2存储较短的字符串
- d p [ i − 1 ] [ j ] = d p [ i ] [ j − 1 ] dp[i-1][j]=dp[i][j-1] dp[i−1][j]=dp[i][j−1]时,向上走
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的最长公共子串存在且唯一。
思路:
-
动态规划
-
使用一个二维数组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[i−1][j−1]+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]
-
-
滑动窗口
- 定义双指针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[i−1][j]+dp[i][j−1]
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+n−2n−1=(n−1)!(m−1)!(m+n−2)!种情况。
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] [i−1,j]右移得到,要么通过 [ i , j − 1 ] [i,j-1] [i,j−1]下移得到.
- 状态转移方程 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[i−1][j],dp[i][j−1])+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<n≤90
进阶:空间复杂度 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[i−1]+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[i−array[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<=n−1
- 第二层循环:
- 通过dp[i]的定义,首先arr[i]一定是选中的,因此想要知道dp[i]的值,需要先知道dp[0…i-1]的值。
- 令 0 < = j < = i − 1 0<=j<=i-1 0<=j<=i−1,如果 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+maxlen−1
-
时间复杂度 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 1≤n≤1000 ,保证字符串中只出现小写英文字母。
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 1≤n≤2×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[:i−2])+nums[i]
- 小偷最后一个偷第i家可以获得的最大金额为:偷前i-2家的最大金额+第i家可以偷得的金额
- 因此可以使用一个变量max_money存储前i-2家的最大金额
- 根据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[i−1])
- 最后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[i−2]+nums[i];如果不偷,则 d p [ i ] = d p [ i − 1 ] dp[i]=dp[i-1] dp[i]=dp[i−1]
所以状态转移方程为: 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[i−2]+nums[i],dp[i−1])
这个状态转移方程更容易理解,而且最终的结果即为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 1≤n≤2×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天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益
-
你可以多次买卖该只股票,但是再次购买前必须卖出之前的股票
-
如果不能获取收益,请返回0
-
假设买入卖出均无手续费
思路:和(一)相同,使用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中。
步骤:
- 如果第i天的股票价格比min_buy更低,则更新min_buy,否则执行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