5. 最长回文子串
class Solution(object):
def longestPalindrome(self, s):
res = ""
if len(s) <= 1:
return s
for i in range(len(s)):
s1 = self.palindrome(s, i, i)
s2 = self.palindrome(s, i, i+1)
res = res if len(res) >= len(s1) else s1
res = res if len(res) >= len(s2) else s2
return res
def palindrome(self, s, i, j):
if j >= len(s):
return s[i]
while (i >= 0 and j < len(s) and s[i] == s[j]):
i = i-1
j = j+1
return s[i+1:j]
22. 括号生成
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
res = []
def backtrace(ans, l, r):
if l == n and r == n:
res.append(''.join(ans))
if l < n:
ans.append('(')
backtrace(ans, l+1, r)
ans.pop()
if r < l:
ans.append(')')
backtrace(ans, l, r+1)
ans.pop()
backtrace([], 0, 0)
return res
32. 最长有效括号
class Solution:
def longestValidParentheses(self, s: str) -> int:
# dp
# dp[i]表示以 i 结尾的字符串最大有效长度
dp = [0 for i in range(len(s))]
res = 0
if len(s) <= 1:
return 0
for i in range(len(s)):
if s[i] == ')':
# 可合并到第二个if
if i-1 >= 0 and s[i-1] == '(':
dp[i] = 2 + (dp[i-2] if i>=2 else 0)
res = max(res, dp[i])
continue
if i-1 >= 0 and s[i-1] == ')' and i-1-dp[i-1] >= 0 and s[i-1-dp[i-1]] == '(':
dp[i] = dp[i-1] + 2 + dp[i-2-dp[i-1]]
res = max(res, dp[i])
return res
# 栈
# res = 0
# stack = [-1]
# if len(s) <= 1:
# return 0
# for i in range(len(s)):
# if s[i] == '(':
# stack.append(i)
# continue
# if s[i] == ')':
# stack.pop()
# if len(stack) == 0:
# stack.append(i)
# else:
# res = max(res, i - stack[-1])
# return res
42. 接雨水
class Solution:
def trap(self, height: List[int]) -> int:
# 双指针
res = 0
left = 0
right = len(height) - 1
l_max = r_max = 0
while left <= right:
l_max = max(l_max, height[left])
r_max = max(r_max, height[right])
if l_max <= r_max:
res += max(0, l_max - height[left])
left += 1
else:
res += max(0, r_max - height[right])
right -= 1
return res
# # 备忘录优化
# res = 0
# l = [0 for i in range(len(height))]
# r = [0 for i in range(len(height))]
# l[0] = height[0]
# r[-1] = height[-1]
# for i in range(1, len(height)):
# l[i] = max(l[i-1], height[i])
# for j in range(len(height)-2, -1, -1):
# r[j] = max(r[j+1], height[j])
# for i in range(1, len(height)):
# res += max(min(l[i], r[i]) - height[i], 0) # 和0取max
# return res
# # 暴力解法
# res = 0
# for i in range(len(height)):
# l = r = 0
# for j in range(i):
# l = max(l, height[j])
# for k in range(i+1, len(height)):
# r = max(r, height[k])
# res += max(min(l, r) - height[i], 0) # 和0取max
# return res
45. 跳跃游戏 II
class Solution:
def jump(self, nums: List[int]) -> int:
# 正向查找,维护当前能够到达的最大下标位置,记为边界。我们从左到右遍历数组,到达边界时,更新边界并将跳跃次数增加 1
# 时间复杂度O(N),空间复杂度O(1)
res = 0
end = 0
max_pos = 0
for i in range(len(nums)-1):
max_pos = max(max_pos, i + nums[i])
if i == end:
end = max_pos
res += 1
return res
# # 贪心算法,通过局部最优求解全局最优,反向查找每次最远的可到达位置
# # 时间复杂度 O(N),空间复杂度 O(1)
# res = 0
# pos = len(nums) - 1
# while pos > 0:
# for i in range(len(nums)):
# if i + nums[i] >= pos:
# pos = i
# res += 1
# break
# return res
53. 最大子数组和
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
# dp
# dp[i]表示以 nums[i]结尾的连续子数组最大和,只依赖 dp[i-1],可优化空间复杂度到 O(1)
# 时间复杂度 O(N),空间复杂度 O(1)
l = len(nums)
if l < 1:
return 0
# dp_1 = [0 for i in range(l)]
dp_1 = nums[0]
res = dp_1
for i in range(1, l):
dp_1 = max(dp_1+nums[i], nums[i])
res = max(res, dp_1)
return res
55. 跳跃游戏
class Solution:
def canJump(self, nums: List[int]) -> bool:
# 贪心,维护最远能到达位置max_pos,其中max_pos只在索引小于max_pos时更新
l = len(nums)
if l <= 1:
return True
max_pos = nums[0]
for i in range(1, l):
if i <= max_pos:
max_pos = max(max_pos, i + nums[i])
if max_pos >= l-1:
return True
return False
62. 不同路径
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
# dp dp[i, j] = dp[i-1, j] + dp[i, j-1]
# 滚动数组降低空间复杂度
dp = [[1] * n] + [[1] + [0] * (n - 1) for _ in range(m - 1)]
for i in range(m):
for j in range(n):
if i >= 1 and j >= 1:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m-1][n-1]
63. 不同路径 II
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
# dp 滚动数组,一维数组,分行计算,每次计算时覆盖旧的
m = len(obstacleGrid)
n = len(obstacleGrid[0])
if m == 0 or obstacleGrid[0][0] == 1:
return 0
dp = [0 for _ in range(n)]
dp[0] = 1
for i in range(m):
for j in range(n):
if obstacleGrid[i][j] == 1:
dp[j] = 0
elif j >= 1:
dp[j] = dp[j] + dp[j-1]
return dp[n-1]
# # dp,注意初始化 dp[0][0],首位有障碍情况
# m = len(obstacleGrid)
# n = len(obstacleGrid[0])
# if m == 0 or obstacleGrid[0][0] == 1:
# return 0
# dp = [[0]*n for _ in range(m)]
# dp[0][0] = 1
# for i in range(m):
# for j in range(n):
# if obstacleGrid[i][j] == 1:
# dp[i][j] = 0
# elif i>0 or j>0:
# dp[i][j] = (dp[i-1][j] if i>=1 else 0) + (dp[i][j-1] if j>=1 else 0)
# return dp[m-1][n-1]
64. 最小路径和
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
# dp 滚动数组
# dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
m = len(grid)
n = len(grid[0])
if m == 0:
return 0
dp = [0 for _ in range(n)]
dp[0] = grid[0][0]
# 初始化
for j in range(1, n):
dp[j] = dp[j-1] + grid[0][j]
# 第二行开始遍历,首列单独处理
for i in range(1, m):
for j in range(n):
if j >= 1:
dp[j] = min(dp[j], dp[j-1]) + grid[i][j]
else:
dp[j] = dp[j] + grid[i][j]
return dp[n-1]
70. 爬楼梯
class Solution:
def climbStairs(self, n: int) -> int:
# dp 滚动数组
if n <= 1:
return 1
dp_1 = 2
dp_2 = 1
dp = dp_1
for i in range(2, n):
dp = dp_1 + dp_2
dp_2 = dp_1 # 先赋值dp_2
dp_1 = dp
return dp
# # dp
# if n <= 1:
# return 1
# dp = [0 for _ in range(n)]
# dp[0] = 1
# dp[1] = 2
# for i in range(2, n):
# dp[i] = dp[i-1] + dp[i-2]
# return dp[n-1]
91. 解码方法
class Solution:
def numDecodings(self, s: str) -> int:
# dp
n = len(s)
dp = [0 for _ in range(n)]
for i in range(n):
if s[i] != '0':
dp[i] = dp[i-1] if i>=1 else 1
if i >= 1 and s[i-1] != '0' and int(s[i-1]) * 10 + int(s[i]) <= 26:
dp[i] += dp[i-2] if i>=2 else 1
return dp[n-1]
95. 不同的二叉搜索树 II
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def generateTrees(self, n: int) -> List[TreeNode]:
# 回溯,外层循环遍历 1~n 所有结点,作为根结点,内层双层递归分别求出左子树和右子树
def generate(start, end):
if start > end:
return [None]
res = []
for i in range(start, end+1):
leftTrees = generate(start, i-1)
rightTrees = generate(i+1, end)
for l in leftTrees:
for r in rightTrees:
curTree = TreeNode(i)
curTree.left = l
curTree.right = r
res.append(curTree)
return res
if n < 1:
return []
return generate(1, n)
96. 不同的二叉搜索树
class Solution:
def numTrees(self, n: int) -> int:
# dp
# dp[n] 代表 1-n 个数能组成多少个不同的二叉排序树
# F(i,n) 代表以 i 为根节点,1-n 个数组成的二叉排序树的不同的个数。
# dp[n] = F(1,n) + F(2,n) + F(3,n) + …… + F(n,n) 。初始值 dp[0] = 1,dp[1] = 1
# F(i,n) = dp[i-1] * dp[n-i]
dp = [0 for _ in range(n+1)]
dp[0] = 1
dp[1] = 1
for i in range(2, n+1):
for j in range(1, i+1):
dp[i] += dp[j-1] * dp[i-j]
return dp[n]
97. 交错字符串
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
# dp dp[i][j]表示s1的前i个字符和s2的前j个字符是否能组成s3的前i+j个字符
m = len(s1)
n = len(s2)
if (n + m != len(s3)):
return False
dp = [[0]*(n+1) for _ in range(m+1)] # 注意行列
dp[0][0] = 1
for i in range(1, m+1):
if s1[i-1] == s3[i-1]:
dp[i][0] = max(dp[i][0], dp[i-1][0])
for j in range(1, n+1):
if s2[j-1] == s3[j-1]:
dp[0][j] = max(dp[0][j], dp[0][j-1])
for i in range(1, m+1):
for j in range(1, n+1):
if s1[i-1] == s3[i+j-1]:
dp[i][j] = max(dp[i][j], dp[i-1][j])
if s2[j-1] == s3[i+j-1]:
dp[i][j] = max(dp[i][j], dp[i][j-1])
print(dp)
return dp[m][n] == 1
# m = len(s1)
# n = len(s2)
# if (n + m != len(s3)):
# return False
# dp = [[0]*(n+1) for _ in range(m+1)] # 注意行列
# dp[0][0] = 1
# for i in range(m+1):
# for j in range(n+1):
# if i > 0 and s1[i-1] == s3[i+j-1]:
# dp[i][j] = max(dp[i][j], dp[i-1][j])
# if j > 0 and s2[j-1] == s3[i+j-1]:
# dp[i][j] = max(dp[i][j], dp[i][j-1])
# return dp[m][n] == 1
115. 不同的子序列
class Solution:
def numDistinct(self, s: str, t: str) -> int:
# dp dp[i][j]表示s[:j]的子序列中出现t[:i]的个数
# 初始化,当i=0时,第一行为1;当j=0时,第一列为0
# 转移方程:如果s[j] == t[i], dp[i][j] = dp[i-1][j-1] + dp[i][j-1]
# 如果s[j] != t[i], dp[i][j] = dp[i][j-1]
m = len(t)
n = len(s)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(m+1):
dp[i][0] = 0
for j in range(n+1):
dp[0][j] = 1
for i in range(1, m+1):
for j in range(1, n+1):
if s[j-1] == t[i-1]:
dp[i][j] = dp[i-1][j-1] + dp[i][j-1]
else:
dp[i][j] = dp[i][j-1]
return dp[-1][-1]
# 交替滚动一维 dp
# 原地滚动一维 dp,借助变量
118. 杨辉三角
class Solution:
def generate(self, numRows: int) -> List[List[int]]:
res = list()
for i in range(numRows):
row = list()
for j in range(i+1):
if j == 0 or j == i:
row.append(1)
else:
row.append(res[i-1][j-1] + res[i-1][j])
res.append(row)
return res
120. 三角形最小路径和
class Solution:
def minimumTotal(self, triangle: List[List[int]]) -> int:
# # dp dp[i][j]表示到达triangle[i][j]的最短路径和
# m = len(triangle)
# n = len(triangle[-1])
# dp = [[0]*n for _ in range(m)]
# dp[0][0] = triangle[0][0]
# for i in range(1, m):
# for j in range(i+1):
# if j == 0:
# dp[i][j] = dp[i-1][j] + triangle[i][j]
# elif j == i:
# dp[i][j] = dp[i-1][j-1] + triangle[i][j]
# else:
# dp[i][j] = min(dp[i-1][j], dp[i-1][j-1]) + triangle[i][j]
# return min(dp[m-1])
# # 空间优化 滚动数组
# m = len(triangle)
# n = len(triangle[-1])
# pre, cur = [0] * n, [0] * n
# pre[0] = triangle[0][0]
# cur[0] = triangle[0][0] # 处理只有一行情况
# for i in range(1, m):
# for j in range(i+1):
# if j == 0:
# cur[j] = pre[j] + triangle[i][j]
# elif j == i:
# cur[j] = pre[j-1] + triangle[i][j]
# else:
# cur[j] = min(pre[j], pre[j-1]) + triangle[i][j]
# pre = cur.copy()
# return min(cur)
# 空间优化 一维数组
m = len(triangle)
n = len(triangle[-1])
cur = [0] * n
cur[0] = triangle[0][0]
for i in range(1, m):
for j in range(i, -1, -1): # 倒序,保证 cur[j-1] 未覆盖
if j == 0:
cur[j] += triangle[i][j]
elif j == i:
cur[j] = cur[j-1] + triangle[i][j]
else:
cur[j] = min(cur[j], cur[j-1]) + triangle[i][j]
return min(cur)
121. 买卖股票的最佳时机
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# # 一次遍历,记录到当天为止的最小股票价格和最大利润
# n = len(prices)
# inf = int(1e9)
# minprice = inf
# maxProfit = 0
# for i in range(n):
# minprice = min(minprice, prices[i])
# maxProfit = max(maxProfit, prices[i] - minprice)
# return maxProfit
# dp dp[i]表示截止到第 i 天能获取的最大利润
# 判断第i天是否卖出股票
# dp[i] = max(dp[i-1], prices[i] - minPrice)
# 边界条件,dp[0] = 0
n = len(prices)
minPrice = prices[0]
dp = [0] * n
for i in range(1, n):
minPrice = min(minPrice, prices[i])
dp[i] = max(dp[i-1], prices[i] - minPrice)
return max(dp)
122. 买卖股票的最佳时机 II
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# # dp[i][0] 表示第 i 天交易完后手里没有股票的最大利润
# # dp[i][1] 表示第 i 天交易完后手里持有一支股票的最大利润(i 从 0 开始)
# # 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])
# n = len(prices)
# dp = [[0]*2 for _ in range(n)]
# 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 max([x[0] for x in dp])
# 空间优化
n = len(prices)
dp_0 = 0
dp_1 = -prices[0]
for i in range(1, n):
dp_temp = dp_0
dp_0 = max(dp_0, dp_1 + prices[i])
dp_1 = max(dp_1, dp_temp - prices[i])
return dp_0
# 贪心算法,把所有的上坡收集到就是最大利润
n = len(prices)
res = 0
for i in range(1, n):
if prices[i] - prices[i-1] > 0:
res += prices[i] - prices[i-1]
return res
124. 二叉树中的最大路径和
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def __init__(self):
self.res = float("-inf")
def maxPathSum(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
# 深度优先搜索, 返回当前树能贡献的最大路径值, 路径一定包含 root
# 注意:在对每个子树进行计算时更新全局最大值
def dfs(root: Optional[TreeNode]) -> int:
if not root:
return 0
left_max = max(0, dfs(root.left)) # 左子树最大值
right_max = max(0, dfs(root.right)) # 右子树最大值
# 更新全局最大值, max(root, left+root, root+right, left+root+right)
self.res = max(self.res, root.val + left_max + right_max) # 以当前根节点为树的路径最大值
# 返回当前根节点可向父节点贡献的路径最大值,可能是 左+根 右+根 根
# (不能是 左+根+右,与父节点无法形成有效路径)
return max(root.val + left_max, root.val + right_max)
dfs(root)
return self.res
131. 分割回文串
class Solution:
def partition(self, s: str) -> List[List[str]]:
# 回溯 + dp
# 时间复杂度:O(n*2^n), 空间复杂度 O(n^2)
res = []
ans = []
n = len(s)
# 构建 dp 数组, dp[i][j]表示 s[i:j+1] 是否回文,(也可采用 memo 存储)
# i >= j, dp[i][j] = True
# i < j, dp[i][j] = dp[i+1][j-1] and s[i] == s[j]
dp = [[True] * n for _ in range(n)]
for i in range(n-1, -1, -1):
for j in range(i+1, n):
dp[i][j] = dp[i+1][j-1] and s[i] == s[j]
def isPalindrome(i: int, j: int) -> bool:
return dp[i][j]
# 递归截止条件,做选择,递归,退出选择
def dfs(i: int):
# 截止条件,索引超限
if i >= n:
res.append(ans[:])
return
# 做选择
for j in range(i, n):
if isPalindrome(i, j):
ans.append(s[i:j+1])
# 进入递归
dfs(j+1)
# 退出选择
ans.pop()
dfs(0)
return res
152. 乘积最大子数组
class Solution:
def maxProduct(self, nums: List[int]) -> int:
# # dp,与【最大子数组和】不一样,不满足最优子结构
# # 需要维护两个值,以 nums[i] 结尾的最大值 & 最小值
# n = len(nums)
# if n == 0:
# return 0
# dp_max = [0 for _ in range(n)]
# dp_min = [0 for _ in range(n)]
# dp_max[0] = nums[0]
# dp_min[0] = nums[0]
# for i in range(1, n):
# dp_max[i] = max(dp_max[i-1] * nums[i], dp_min[i-1] * nums[i], nums[i])
# dp_min[i] = min(dp_min[i-1] * nums[i], dp_max[i-1] * nums[i], nums[i])
# return max(dp_max)
# 空间优化
n = len(nums)
if n == 0:
return 0
dp_max = nums[0]
dp_min = nums[0]
res = nums[0]
for i in range(1, n):
tmp = dp_max
dp_max = max(dp_max * nums[i], dp_min * nums[i], nums[i])
dp_min = min(dp_min * nums[i], tmp * nums[i], nums[i])
res = max(res, dp_max)
return res
174. 地下城游戏
class Solution:
def calculateMinimumHP(self, dungeon: List[List[int]]) -> int:
# dp dp[i][j]表示要到达右下角所需要的最低初始值
# 遍历方向需要从右下角开始,向左向上进行
# 遇到 dp[i][j] <= 0 时,需要置为 1,保证活着
m = len(dungeon)
n = len(dungeon[0])
dp = [[0]*n for _ in range(m)]
dp[m-1][n-1] = (-dungeon[m-1][n-1] + 1) if dungeon[m-1][n-1] <= 0 else 1
for j in range(n-2, -1, -1):
dp[m-1][j] = max(dp[m-1][j+1] - dungeon[m-1][j], 1)
for i in range(m-2, -1, -1):
dp[i][n-1] = max(dp[i+1][n-1] - dungeon[i][n-1], 1)
for i in range(m-2, -1, -1):
for j in range(n-2, -1, -1):
dp[i][j] = max(min(dp[i+1][j], dp[i][j+1]) - dungeon[i][j], 1)
return dp[0][0]
# # dfs 暴力求解 方案超时
# m = len(dungeon)
# n = len(dungeon[0])
# # dfs 返回的是 在 (i, j) 位置所需要的最小初始值
# def dfs(i: int, j: int) -> int:
# if (i == m-1 and j == n-1):
# return max(-dungeon[i][j] + 1, 1)
# r_min = dfs(i, j+1) if j <= n-2 else float('inf')
# d_min = dfs(i+1, j) if i <= m-2 else float('inf')
# res = max(min(r_min, d_min) - dungeon[i][j], 1)
# # print(i, j, res) # 打印,判断重复递归
# return res
# return dfs(0, 0)
# # dfs 暴力求解 方案超时
# m = len(dungeon)
# n = len(dungeon[0])
# # 增加 memo
# memo = [[float('inf')]*n for _ in range(m)]
# # dfs 返回的是 在 (i, j) 位置所需要的最小初始值
# def dfs(i: int, j: int) -> int:
# res = 0
# if memo[i][j] < float('inf'):
# return memo[i][j]
# if (i == m-1 and j == n-1):
# res = max(-dungeon[i][j] + 1, 1)
# else:
# r_min = dfs(i, j+1) if j <= n-2 else float('inf')
# d_min = dfs(i+1, j) if i <= m-2 else float('inf')
# res = max(min(r_min, d_min) - dungeon[i][j], 1)
# return res
# return dfs(0, 0)