目录
1. 斐波那契数列(1、1、2、3、5、8)
Fibonacci 前两数之和等于下一个数(前两个数都为1)
def fib(n):
if n == 1 or n== 2:
return 1
a = 1
b = 1
for i in range(3, n+1):
a, b = b, a+b # tmp = b; b = a + b; a = tmp
return b
########## 递 归 #########
def fib(n):
if n == 1 or n == 2:
return 1
return fib(n-1) + fib(n-2)
########## DP动态规划 ##########
def fib(n):
dp = [1]*(n+1)
for i in range(3, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
2. 青蛙跳台阶(1、2、3、5、8、13)
def jumpFloor(n):
a = 1
b = 2
if n == 1 or n== 2:
return n
for i in range(3, n+1):
a, b = b, a+b # tmp = b; b = a + b; a = tmp
return b
########## 递 归 ##########
def jumpFloor(n):
if n == 1 or n == 2:
return n
return fib(n-1) + fib(n-2)
########## DP动态规划 ##########
def jumpFloor(n):
dp = [1]*(n+1)
if n < 2:
return dp[n]
for i in range(2, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
3. 青蛙跳台阶(进阶版)
# 青蛙可以跳n级台阶
# 根据推导得,答案为 2的n-1次方
# return 2**(n-1)
def fib(n):
ls = [0]*(n+1)
if n==1 or n==2:
return n
ls[0], ls[1], ls[2] = 1, 1, 2
for i in range(3,n+1):
for j in range(i):
ls[i] += ls[j]
return ls[-1]
4. 最小花费爬楼梯
核心思想:目的楼层的最小花费 = min(前一台阶到此的花费, 前两台阶到此的花费)
class Solution:
def minCostClimbingStairs(self , cost: List[int]) -> int:
# dp[i] 表示爬到第i阶梯需要的最小花费
n = len(cost)
dp = [0 for i in range(n+1)] # [0]*(n+1)
for i in range(2, n+1):
dp[i] = min(dp[i-2] + cost[i-2], dp[i-1] + cost[i-1])
return dp[-1]
5. 有多少个不同的二叉搜索树
# 以j为头结点进行搜索,左子树有j-1个节点,右子树有i-j个节点
# 状态转移方程:i个节点的树个数=j个节点左子树个数*i-1-j个节点右子树个数
n = int(input())
dp = [0]*(n+1)
dp[0],dp[1] = 1,1
for i in range(2,n+1):
for j in range(i):
dp[i] += dp[j]*dp[i-j-1]
print(dp[n])
6. 打家劫舍(一)
class Solution:
def rob(self, nums: List[int]) -> int:
# dp[i]表示长度为i的数组,最多能偷取多少钱
dp = [0 for i in range(len(nums) + 1)]
# 长度为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)]
7. 打家劫舍(二)
class Solution:
def rob(self, nums: List[int]) -> int:
# dp[i]表示长度为i的数组,最多能偷取多少钱
dp1 = [0 for i in range(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 for i in range(len(nums) + 1)]
# 不偷第一家
dp2[1] = 0
# 可以偷最后一家
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)])
8. 计算所需硬币最少个数
class Solution:
def calculationCoin(self, coins: List[int], amount: int) -> int:
# 初始化dp
dp = [amount + 1] * (amount + 1)
# 目标金额为 0 时,硬币数量为 0
dp[0] = 0
for i in range(1, amount + 1):
for coin in coins:
if coin <= i:
dp[i] = min(dp[i], dp[i - coin] + 1)
return -1 if dp[amount] > amount else dp[amount]
9. 兑换零钱(一)
class Solution:
def minMoney(self, arr: List[int], aim: int) -> int:
# 小于1的都返回0
if aim < 1:
return 0
# dp[i]表示凑齐i元最少需要多少货币数
dp = [(aim + 1) for i in range(aim + 1)]
dp[0] = 0
# 遍历1-aim元
for i in range(1, aim + 1):
# 每种面值的货币都要枚举
for j in range(len(arr)):
# 如果面值不超过要凑的钱才能用
if arr[j] <= i:
# 维护最小值
dp[i] = min(dp[i], dp[i - arr[j]] + 1)
# 如果最终答案大于aim代表无解
if dp[aim] > aim:
return -1
else:
return dp[aim]
9. 买卖股票的最好时机(一)
暴力法:
class Solution:
def maxProfit(self , prices: List[int]) -> int:
res = 0
for i in range(len(prices)):
for j in range(i+1, len(prices)):
res = max(res, prices[j] - prices[i])
return res
一次遍历:minprice记录股票最低价格,maxprofit记录最大利润
class Solution:
def maxProfit(self, prices: List[int]) -> int:
minprice = int(1e9)
maxprofit = 0
for price in prices:
maxprofit = max(price - minprice, maxprofit)
minprice = min(price, minprice)
return maxprofit
dp动态规划:
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
if n == 0: return 0
# j=0表示不持股,j=1表示持股
# dp[i][0]表示某一天不持股到该天为止的最大收益
# dp[i][1]表示某天持股,到该天为止的最大收益
dp = [[0] * 2 for i in range(n)]
# 第一天不持股,总收益为0
dp[0][0] = 0
# 第一天持股,总收益为减去该天的股价
dp[0][1] = -prices[0]
# 遍历后续每天,状态转移
for i in range(1, n):
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i])
dp[i][1] = max(dp[i - 1][1], -prices[i])
# 最后一天不持股,到该天为止的最大收益
return dp[n - 1][0]
一维dp动态规划最优版:
class Solution:
def maxProfit(self , prices: List[int]) -> int:
n = len(prices)
if n == 0: return 0
dp = [0]*n
minprice = prices[0]
for i in range(1,n):
minprice = min(minprice, prices[i])
dp[i] = max(dp[i-1], prices[i] - minprice)
return dp[-1]
10. 买卖股票的最好时机(二)
暴力法:
class Solution:
def maxProfit(self , prices: List[int]) -> int:
res = 0
n = len(prices)
for i in range(n-1):
diff = prices[i+1] - prices[i]
if diff > 0:
res += diff
return res
dp动态规划:
class Solution:
def maxProfit(self , prices: List[int]) -> int:
n = len(prices)
if n <= 1: return 0
# cash为当天持有现金(不持股)的最大收益
# hold为当天持有股票的最大收益
cash = [0]*n
hold = [0]*n
# 第一天不持股,收益为0
cash[0] = 0
# 第一天持股,收益为-该天的股价
hold[0] = -prices[0]
for i in range(1, n):
cash[i] = max(cash[i-1], hold[i-1] + prices[i])
hold[i] = max(hold[i-1], cash[i-1] - prices[i])
return cash[-1]
# 另一种形式
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
# 0为不持股,1为持股
# dp[i][0]表示某一天不持股到该天为止的最大收益
# dp[i][1]表示某天持股,到该天为止的最大收益
dp = [[0, 0] for i in range(n)]
# 第一天不持股,总收益为0
dp[0][0] = 0
# 第一天持股,总收益为减去该天的股价
dp[0][1] = -prices[0]
# 遍历后续每天,状态转移
for i in range(1, n):
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i])
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i])
# 最后一天不持股,到该天为止的最大收益
return dp[n - 1][0]
变量记录(dp动态规划最优版):
class Solution:
def maxProfit(self , prices: List[int]) -> int:
n = len(prices)
if n <= 1: return 0
cash = 0
hold = -prices[0]
preCash = cash
preHold = hold
for i in range(1, n):
cash = max(preCash, preHold + prices[i])
hold = max(preHold, preCash - prices[i])
preCash = cash
preHold = hold
return cash
11. 买卖股票的最好时机(三)
class Solution:
def maxProfit(self , prices: List[int]) -> int:
n = len(prices)
if n <= 1: return 0
# dp[i][j]表示i时间下,j=0为没操作,1为第一次买入一支股票,2为第一次卖出一支股票,3为第2次买入一支股票,4为第2次卖出一支股票
dp = [[int(-1e9)]*5 for i in range(n)]
# 第一天不操作
dp[0][0] = 0
# 第一天第一次买入
dp[0][1] = -prices[0]
for i in range(1, n):
dp[i][0] = 0
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
dp[i][2] = max(dp[i-1][2], dp[i-1][1] + prices[i])
dp[i][3] = max(dp[i-1][3], dp[i-1][2] - prices[i])
dp[i][4] = max(dp[i-1][4], dp[i-1][3] + prices[i])
# max(没操作,一次卖出,两次卖出)
return max(0, max(dp[-1][2], dp[-1][4]))
变量记录(dp动态规划最优版):
class Solution:
def maxProfit(self , prices: List[int]) -> int:
n = len(prices)
if n <= 1: return 0
dp = [0 for _ in range(5)]
# 0没操作,1第一次买入,2第一次卖出,3第二次买入,4第二次卖出
dp[0] = 0
dp[1] = -prices[0]
dp[3] = int(-1e9)
for i in range(1, n):
dp[0] = 0
dp[1] = max(dp[1], -prices[i])
dp[2] = max(dp[2], dp[1] + prices[i])
dp[3] = max(dp[3], dp[2] - prices[i])
dp[4] = max(dp[4], dp[3] + prices[i])
return max(0 ,max(dp[2], dp[4]))
12. 买卖股票的最佳时机 IV
三维dp:
class Solution:
def maxProfit(self, k: int, prices: List[int]) -> int:
n = len(prices)
if n <= 1: return 0
# 若k大于数组的一半,即可以无限次交易
if k > n//2:
cash = 0
hold = -prices[0]
preCash = cash
preHold = hold
for i in range(1, n):
cash = max(preCash, preHold + prices[i])
hold = max(preHold, preCash - prices[i])
preCash = cash
preHold = hold
return cash
# 三维dp,第i天,交易了多少次,当前的买卖状态(0为卖出,1为买入)
dp = [[[0,0] for i in range(k+1)] for _ in range(n)]
# 初始化第一天
for i in range(k+1):
dp[0][i][0] = 0
dp[0][i][1] = -prices[0]
for i in range(1, n):
for j in range(1, k+1):
# 处理第k次买入
dp[i][j-1][1] = max(dp[i-1][j-1][1], dp[i-1][j-1][0] - prices[i])
# 处理第k次卖出
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j-1][1] + prices[i])
return dp[-1][k][0]
二维dp:
class Solution:
def maxProfit(self, k: int, prices: List[int]) -> int:
n = len(prices)
if n <= 1: return 0
# 若k大于数组的一半,即可以无限次交易
if k > n//2:
cash = 0
hold = -prices[0]
preCash = cash
preHold = hold
for i in range(1, n):
cash = max(preCash, preHold + prices[i])
hold = max(preHold, preCash - prices[i])
preCash = cash
preHold = hold
return cash
# 二维dp,交易了多少次,当前的买卖状态(0为卖出,1为买入)
dp = [[0,0] for _ in range(k+1)]
for i in range(k+1):
dp[i][1] = -prices[0]
for i in range(1, n):
for j in range(k, 0, -1):
# 处理第k次买入
dp[j-1][1] = max(dp[j-1][1], dp[j-1][0] - prices[i])
# 处理第k次卖出
dp[j][0] = max(dp[j][0], dp[j-1][1] + prices[i])
return dp[-1][0]
13. 背包问题
# 背包所能容纳的最大价值
n,v = map(int,input().split())
dp = [[0 for i in range(v+1)] for j in range(n+1)]
lv, lw = [], []
for i in range(n):
l = (list(map(int,input().split())))
lv.append(l[0]) # 物品重量列表
lw.append(l[1]) # 物品价值列表
# 👇核心代码👇
for i in range(1,n+1):
for j in range(1,v+1):
if j < lv[i-1]:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = max(dp[i-1][j],dp[i-1][j-lv[i-1]]+lw[i-1])
print(dp[n][v])
01背包:共有n个物品,给定一个容量为V的背包。接下来n行,每行有该物品的体积和价值。
- 求背包能装的最大价值。
- 如果背包恰好装满,最多能装多大价值的物品。
import sys
n,v = map(int,input().split())
lv, lw = [0], [0]
for i in range(n):
l = (list(map(int,input().split())))
lv.append(l[0]) # 物品重量列表
lw.append(l[1]) # 物品价值列表
# dp1为第一题,不考虑背包是否装满,最多装多大价值的物品
dp1 = [0 for i in range(v+1)]
# dp2为第二题,在背包恰好装满时,最多装多大价值的物品
dp2 = [-sys.maxsize-1 for i in range(v+1)]
dp2[0] = 0
for i in range(1,n+1):
for j in range(v, lv[i]-1, -1):
dp1[j] = max(dp1[j],dp1[j-lv[i]]+lw[i])
dp2[j] = max(dp2[j],dp2[j-lv[i]]+lw[i])
print(dp1[v])
print(0) if dp2[v]<0 else print(dp2[v])
14. 把数字翻译成字符串
class Solution:
def solve(self, nums: str) -> int:
# 排除0
if nums == "0":
return 0
# 排除只有一种可能的10 和 20
if nums == "10" or nums == "20":
return 1
# 当0的前面不是1或2时,无法译码,0种
for i in range(1, len(nums)):
if nums[i] == "0":
if nums[i - 1] != "1" and nums[i - 1] != "2":
return 0
# 辅助数组初始化为1
dp = [1 for i in range(len(nums) + 1)]
for i in range(2, len(nums) + 1):
# 在11-19,21-26之间的情况
if (nums[i - 2] == "1" and nums[i - 1] != "0") or (
nums[i - 2] == "2" and nums[i - 1] > "0" and nums[i - 1] < "7"):
dp[i] = dp[i - 1] + dp[i - 2]
else:
dp[i] = dp[i - 1]
return dp[len(nums)]
15. 数字字符串转化成IP地址
class Solution:
def __init__(self):
self.res = []
self.s = ""
self.nums = ""
# step表示第几个数字,index表示字符串下标
def dfs(self, step: int, index: int):
# 当前分割出的字符串
cur = ""
# 分割出了四个数字
if step == 4:
# 下标必须走到末尾
if index != len(self.s):
return
self.res.append(self.nums)
else:
i = index
# 最长遍历3位
while i < index + 3 and i < len(self.s):
cur += self.s[i]
# 转数字比较
num = int(cur)
temp = self.nums
# 不能超过255且不能有前导0
if num <= 255 and (len(cur) == 1 or cur[0] != "0"):
# 添加点
if step - 3 != 0:
self.nums += cur + "."
else:
self.nums += cur
# 递归查找下一个数字
self.dfs(step + 1, i + 1)
# 回溯
self.nums = temp
i += 1
def restoreIpAddresses(self, s: str) -> List[str]:
self.s = s
self.dfs(0, 0)
return self.res
16. 正则表达式匹配
class Solution:
def match(self, str: str, pattern: str) -> bool:
n1 = len(str)
n2 = len(pattern)
# dp[i][j]表示str前i个字符和pattern前j个字符是否匹配
dp = [[False] * (n2 + 1) for i in range(n1 + 1)]
# 两个都为空串自然匹配
dp[0][0] = True
# 初始化str为空的情况,字符串下标从1开始
for i in range(2, n2 + 1):
# 可以让自己前面个字符重复0次
if pattern[i - 1] == "*":
# 与再前一个能够匹配空串有关
dp[0][i] = dp[0][i - 2]
# 遍历str每个长度
for i in range(1, n1 + 1):
# 遍历pattern每个长度
for j in range(n2 + 1):
# 当前字符不为*,用.去匹配或者字符直接相同
if pattern[j - 1] != "*" and (
pattern[j - 1] == "." or pattern[j - 1] == str[i - 1]
):
dp[i][j] = dp[i - 1][j - 1]
# 当前的字符为*
elif j >= 2 and pattern[j - 1] == "*":
# 若是前一位为.或者前一位可以与这个数字匹配
if pattern[j - 2] == "." or pattern[j - 2] == str[i - 1]:
# 转移情况
dp[i][j] = dp[i - 1][j] or dp[i][j - 2]
else:
# 不匹配
dp[i][j] = dp[i][j - 2]
return dp[n1][n2]
17. 最长公共子串
class Solution:
def LCS(self , str1: str, str2: str) -> str:
# 让str1为较长的字符串
if len(str1) < len(str2):
str1 ,str2 = str2, str1
res = ""
length = 0
for i in range(len(str1)):
if str1[i-length : i+1] in str2:
res = str1[i-length: i+1]
length += 1
return res
18. 最长回文子串
暴力法:
class Solution:
# 验证是否是回文
def Valid(self, s: str, l: int, r: int) -> bool:
while l < r:
if s[l] != s[r]:
return False
l += 1
r -= 1
return True
def getLongestPalindrome(self , A: str) -> int:
# 记录最长回文子串的长度
maxlen = 1
# 记录最长回文子串的起始点
begin = 0
for i in range(len(A)-1):
for j in range(len(A)-1, i, -1):
if j-i+1 > maxlen and self.Valid(A, i, j):
maxlen = j-i+1
begin = i
break
# 最长回文子串为:A[begin:begin+maxlen]
return maxlen
中心扩散法:
class Solution:
# 中心扩散
def fun(self, s: str, begin: int, end: int) -> int:
# 每个中心点开始扩散
while begin >= 0 and end <= len(s)-1 and s[begin] == s[end]:
begin -= 1
end += 1
# 返回长度
return end - begin - 1
def getLongestPalindrome(self , A: str) -> int:
maxlen = 1
# 以每个点为中心
for i in range(len(A) - 1):
# 分奇数长度 和 偶数长度向两边扩展
maxlen = max(maxlen, max(self.fun(A, i, i), self.fun(A, i, i+1)))
return maxlen
———————— E N D ———————— E N D ———————— E N D ————————
# 若获取最长子串的方法
def getLongestPalindrome(self , A: str) -> int:
maxlen = 1
begin = 0
# 以每个点为中心
for i in range(len(A) - 1):
max_now = max(self.fun(A, i, i), self.fun(A, i, i+1))
if maxlen < max_now:
maxlen = max_now
begin = i - (maxlen-1)//2
return A[begin:begin+maxlen]
19. 最长的括号子串
class Solution:
def longestValidParentheses(self, s: str) -> int:
res = 0
# 记录上一次连续括号结束的位置
start = -1
st = []
for i in range(len(s)):
# 左括号入栈
if s[i] == "(":
st.append(i)
# 右括号
else:
# 如果右括号时栈为空,不合法,设置为结束位置
if len(st) == 0:
start = i
else:
# 弹出左括号
st.pop()
# 栈中还有左括号,说明右括号不够,减去栈顶位置就是长度
if len(st) != 0:
res = max(res, i - st[-1])
# 栈中没有括号,说明左右括号行号,减去上一次结束的位置就是长度
else:
res = max(res, i - start)
return res
20. 最长公共子序列(二)
class Solution:
def __init__(self):
self.x = ""
self.y = ""
#获取最长公共子序列
def ans(self, i: int, j: int, b: List[List[int]]):
res = ""
#递归终止条件
if i == 0 or j == 0:
return res
#根据方向,往前递归,然后添加本级字符
if b[i][j] == 1:
res = res + self.ans(i - 1, j - 1, b)
res = res + self.x[i - 1]
elif b[i][j] == 2:
res = res + self.ans(i - 1, j, b)
elif b[i][j] == 3:
res = res + self.ans(i, j - 1, b)
return res
def LCS(self , s1: str, s2: str) -> str:
#特殊情况
if s1 is None or s2 is None:
return "-1"
len1 = len(s1)
len2 = len(s2)
self.x = s1
self.y = s2
#dp[i][j]表示第一个字符串到第i位,第二个字符串到第j位为止的最长公共子序列长度
dp = [[0] * (len2 + 1) for i in range(len1 + 1)]
#动态规划数组相加的方向
b = [[0] * (len2 + 1) for i in range(len1 + 1)]
#遍历两个字符串每个位置求的最长长度
for i in range(1, len1 + 1):
for j in range(1, len2 + 1):
#遇到两个字符相等
if s1[i - 1] == s2[j - 1]:
#考虑由二者都向前一位
dp[i][j] = dp[i - 1][j - 1] + 1
#来自于左上方
b[i][j] = 1
#遇到的两个字符不同
#左边的选择更大,即第一个字符串后退一位
elif dp[i - 1][j] > dp[i][j - 1]:
dp[i][j] = dp[i - 1][j]
#来自于左方
b[i][j] = 2
#右边的选择更大,即第二个字符串后退一位
else:
dp[i][j] = dp[i][j - 1]
#来自于上方
b[i][j] = 3
#获取答案字符串
res = self.ans(len1, len2, b)
#检查答案是否位空
if res is None or res == "":
return "-1"
else:
return res
21. 最长上升子序列(一)
class Solution:
def LIS(self, arr: List[int]) -> int:
# 设置数组长度大小的动态规划辅助数组
dp = [1 for i in range(len(arr))]
res = 0
for i in range(1, len(arr)):
for j in range(i):
# 可能j不是所需要的最大的,因此需要dp[i] < dp[j] + 1
if arr[i] > arr[j] and dp[i] < dp[j] + 1:
# i点比j点大,理论上dp要加1
dp[i] = dp[j] + 1
# 找到最大长度
res = max(res, dp[i])
return res
22. 连续子数组的最大和
class Solution:
def FindGreatestSumOfSubArray(self, array: List[int]) -> int:
# 记录到下标i为止的最大连续子数组和
dp = [0 for i in range(len(array))]
dp[0] = array[0]
maxsum = dp[0]
for i in range(1, len(array)):
# 状态转移:连续子数组和最大值
dp[i] = max(dp[i - 1] + array[i], array[i])
# 维护最大值
maxsum = max(maxsum, dp[i])
return maxsum