【Leetcode】DP系列
【Leetcode-5】
一、题目:最长回文字串
给你一个字符串 s
,找到 s
中最长的回文子串。
二、代码:
def longestPalindrome(self, s: str) -> str: """ dp[i][j]表示从i到j是否为回文子串,则dp[i][j]=(s[i]==s[j])&dp[i+1][j-1] 边界条件:dp[i][i]=True 长度l从1到n,起始点i从0到n-1 """ n = len(s) if n == 0: return '' dp = [[False]*n for _ in range(n)] for i in range(n): dp[i][i] = True # 边界条件 max_str = [1, 0] # len, start for l in range(2, n + 1): for i in range(n): j = l + i - 1 # j-i+1=l if j > n - 1: break if (s[i] == s[j]) and (l <= 3 or dp[i+1][j-1]): dp[i][j] = True if max_str[0] < l: max_str = [l, i] max_str = s[max_str[1]: max_str[0] + max_str[1]] return max_str
【Leetcode-53】
一、题目:最大子序和
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
二、代码:
def maxSubArray(self, nums: List[int]) -> int: """ dp[i]表示以i为结尾的最大子序列和,presum[i]表示在i之前最大和 若presum[i]<0则dp[i]=nums[i],越加负数越小。否则presum += num 由于求最大值,不必保存每一项的值,因此不用数组、直接覆盖更新即可 """ presum = maxsum = nums[0] for num in nums[1:]: if presum < 0: presum = num else: presum += num maxsum = max(maxsum, presum) return maxsum
【Leetcode-55】
一、题目:跳跃游戏
给定一个非负整数数组 nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标。
二、代码:
def canJump(self, nums: List[int]) -> bool: """ 如果i位置可达,则i之前的所有位置都可达。 计算从每个位置i能到达的位置max_pos:如果当前位置可达,则最大可达=max(i+num, max_pos) 如果最后一个位置len(nums)-1可达的位置>=len(nums)-1则最终可达 """ max_pos = 0 # 开始只能走到0 for i, num in enumerate(nums): # 计算从i点开始能到达的位置 if max_pos >= i: # 当前位置可达 max_pos = max(max_pos, num+i) if max_pos >= len(nums) - 1: return True else: return False
【Leetcode-62】
一、题目:不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
二、代码:
def uniquePaths(self, m: int, n: int) -> int: dp = [[1]*n for _ in range(m)] 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]
【Leetcode-64】
一、题目:最小路径和
给定一个包含非负整数的 m x n
网格 grid
,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
二、代码:
def minPathSum(self, grid: List[List[int]]) -> int: """ dp[ij]表示从起点到ij最短距离,某位置只能来自左或上,求二者最小即可。 ij初始值为grid[ij],因此每次算dp[ij]时只要把左或上的dp值加过来即可 """ m, n = len(grid), len(grid[0]) dp = grid for j in range(1, n): dp[0][j] += dp[0][j-1] # 第一行 for i in range(1, m): dp[i][0] += dp[i-1][0] # 第一列 for i in range(1, m): for j in range(1, n): dp[i][j] += min(dp[i-1][j], dp[i][j-1]) return dp[m-1][n-1]
【Leetcode-70】
一、题目:爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
二、代码:
def climbStairs(self, n: int) -> int: """ p = p1 + p2 从3开始要取到n """ p1, p2 = 1, 2 if n == 1: return p1 if n == 2: return p2 p = 0 for i in range(3, n+1): p = p1 + p2 p1 = p2 p2 = p return p
【Leetcode-72】
一、题目:编辑距离
给你两个单词 word1
和 word2
,请你计算出将 word1
转换成 word2
所使用的最少操作数 。
二、代码:
def minDistance(self, word1: str, word2: str) -> int: """ dp[i][j]表示word1的前i个字母变成word2的前j个字母需要的最小操作数。dp[i][j]有3种来源: 增: 正向:word1->word2,先获得dp[i-1][j]再增加一个字符,dp[i][j]=dp[i-1][j]+1 反向:word2->word1,先获得dp[i][j-1]再增加一个字符,dp[i][j]=dp[i][j-1]+1 改: 当word1第i个字符word1[i-1]=word2第j个字符word2[j-1]时,不用改,dp[i][j]=dp[i-1][j-1] 否则,dp[i][j]=dp[i-1][j-1]+1 """ m, n = len(word1), len(word2) if m == 0 or n == 0: return max(m, n) dp = [[0] * (n+1) for _ in range(m+1)] for i in range(m+1): dp[i][0] = i for j in range(n+1): dp[0][j] = j for i in range(1, m+1): for j in range(1, n+1): if word1[i-1] == word2[j-1]: dp[i][j] = dp[i-1][j-1] else: dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1 return dp[m][n]
【Leetcode-96】
一、题目:不同的二叉搜索树
给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?
二、代码:
def numTrees(self, n: int) -> int: """ dp[i]表示i个结点组成的搜索二叉树数,要求dp[n] dp[i]可以拆分为j为根节点、j-1个点为左结点、i-j个点为右结点 dp[i] = sum(dp[j-1]*dp[i-j]),j从1到ii """ dp = [0] * (n+1) dp[0] = dp[1] = 1 for i in range(2, n+1): for j in range(1, n+1): dp[i] += dp[j-1] * dp[i-j] return dp[-1]
【Leetcode-120】
一、题目:
给定一个三角形 triangle ,找出自顶向下的最小路径和。
每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。
二、代码:
def minimumTotal(self, triangle: List[List[int]]) -> int: """ 2 3 4 6 5 7 4 1 8 3 0 0 0 0 0 1.自底而上,min(dp[j] + triangle[i][j], dp[j + 1] + triangle[i][j]) 0<=j<=i 2.执行n次,返回dp[0] """ n = len(triangle) dp = [0] * (n+1) for i in reversed(range(n)): for j in range(i+1): dp[j] = min(dp[j] + triangle[i][j], dp[j + 1] + triangle[i][j]) return dp[0]
【Leetcode-121】
一、题目:买卖股票的最佳时期
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
二、代码:
def maxProfit(self, prices: List[int]) -> int: """ dp[i]表示第i天卖出能获得的利润,显然在历史最低点买入、i天卖出可以获得i天最大收益 """ min_val = prices[0] # 截止到第1天历史最低价格 profit = 0 # 第一天买没卖出 for price in prices[1:]: profit = max(price - min_val, profit) # 当天收益和历史比 min_val = min(min_val, price) # 截止到当天(包括)的最低价 return profit
【Leetcode-122】
一、题目:买卖股票的最佳时期2
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
二、代码:
class Solution: def maxProfit(self, prices: List[int]) -> int: """
dp[i]表示表示该天结束的最大收益 0表示该天结束无股票,1表示该天结束有股票 dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]) dp[i][1] = max(dp[i-1][0]-prices[i], dp[i-1][1]) """ f = [0, -prices[0]] max_pro = 0 for item in prices[1:]: f0 = max(f[0], f[1]+item) f1 = max(f[0]-item, f[1]) f = [f0, f1] return max(f)
【Leetcode-309】
一、题目:最佳买卖股票时机含冷冻期
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
二、代码:
def maxProfit(self, prices: List[int]) -> int: f = [-prices[0], 0, 0] # 第0天,有股票/无股票下一天非冷冻/无股票下一天冷冻期 for i in range(1, len(prices)): f[0] = max(f[1]-prices[i], f[0]) f[1] = max(f[1], f[2]) f[2] = f[0] + prices[i] return max(f[1], f[2]
【Leetcode-152】
一、题目:乘积最大子数组
给你一个整数数组 nums
,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。(数组中有负数)
二、代码:
def maxProduct(self, nums: List[int]) -> int: """ dp[i]表示第i个元素结尾的乘积最大值 若无负数,dp[i]=max(dp[i-1]*nums[i], nums[i]) 有负数,则要计算最小值(绝对值最大)和最大值 dp[i][0]=min(dp[i-1][0]*nums[i], dp[i-1][1]*nums[i], nums[i]) # 乘积最小值,即当前乘积为负数 dp[i][1]=max(dp[i-1][0]*nums[i], dp[i-1][1]*nums[i], nums[i]) # 乘积最大值 """ min_val = max_val = res = nums[0] for item in nums[1:]: min1, max1 = min_val, max_val max_val = max(min1*item, item, max1*item) min_val = min(min1*item, item, max1*item) res = max(res, max_val) return res
【Leetcode-198】
一、题目:打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
二、代码:
def rob(self, nums: List[int]) -> int: """ dp[i]表示i处结束后获得的最大收益 dp[i][0]:i天拿的收益=i-1天不能拿 dp[i][1]:i天不拿的收益=i-1天可拿可不拿 """ val1, val2 = nums[0], 0 # 第一天拿/不拿 for num in nums[1:]: val_1, val_2 = val1, val2 # 当天的拿/不拿同时更新,避免更新过程中的覆盖 val1 = val_2 + num val2 = max(val_1, val_2) max_val = max(val1, val2) return max_val
【Leetcode-221】
一、题目:最大正方形
在一个由 '0'
和 '1'
组成的二维矩阵内,找到只包含 '1'
的最大正方形,并返回其面积。
二、代码:
def maximalSquare(self, matrix: List[List[str]]) -> int: """ dp[i][j]表示以ij为右下角的正方形的边长,则找规律为 dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1 """ m, n = len(matrix), len(matrix[0]) dp = [[0]*n for _ in range(m)] max_len = 0 for i in range(0, m): for j in range(0, n): if matrix[i][j] == "1": if i == 0 or j == 0: dp[i][j] = 1 else: dp[i][j] = 1 + min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) max_len = max(max_len, dp[i][j]) return max_len*max_len
【Leetcode-279】
一、题目:完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。
二、代码:
def numSquares(self, n: int) -> int: """ dp[i]表示和为i的完全平方数的最小数量 dp[i] = min(dp[i-k*k]) + 1, k从1到sqrt(i) """ import math dp = [float('inf')] * (n + 1) # dp[0]=1,dp[1]=1,dp[2]=1 dp[0] = 0 dp[1] = 1 for i in range(2, n+1): for j in range(1, int(math.sqrt(i)) + 1): item = dp[i - j * j] + 1 dp[i] = min(dp[i], item) return dp[-1]
【Leetcode-322】
一、题目:零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。
二、代码:
def coinChange(self, coins: List[int], amount: int) -> int: """ dp[i]表示金额i需要的最少硬币个数, dp[i] = min(dp[i-c]) + 1,c是coins[j] 完全背包问题(无限量),硬币在外层,金额内层,正序 本题硬币和金额循环顺序不影响结果,可能是由于求的是最小硬币个数 """ dp = [-1] * (amount + 1) # 都组成不了 dp[0] = 0 for c in coins: for i in range(amount + 1): if c <= i and dp[i-c] != -1: # 能组成 if dp[i] == -1: dp[i] = dp[i-c] + 1 else: dp[i] = min(dp[i-c] + 1, dp[i]) return dp[amount]
【Leetcode-518】
一、题目:零钱兑换2
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
二、代码:
def change(self, amount: int, coins: List[int]) -> int: """ dp[i]表示组成金额i的硬币组合数, dp[i] = sum(dp[i-c]),c是coins[j] 完全背包问题(不限量表示完全背包),一般外层为硬币,且金额正序。 需要注意:循环外层为硬币,否则会出现重复,如果求组合数说明重复是算多次,则循环外层为金额 外层循环的含义为:加入当前硬币,可以组成的面值 """ dp = [0] * (amount + 1) # 都组成不了 dp[0] = 1 for c in coins: for i in range(c, amount + 1): dp[i] += dp[i-c] return dp[amount]
【Leetcode-416】
一、题目:分割等和子集
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
二、代码:
def canPartition(self, nums: List[int]) -> bool: """ 问题转换为是否可选取部分元素使得和为1/2*sum dp[i]表示是否可和为i 0-1背包问题(硬币只能取1次),硬币外层,金额内层,内层逆序。 """ total = sum(nums) if total % 2 != 0: return False target = total // 2 dp = [False] * (target + 1) dp[0] = True for c in nums: for i in reversed(range(c, target + 1)): if dp[i-c]: dp[i] = True return dp[-1]
【Leetcode-494】
一、题目:目标和
给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
二、代码:
def findTargetSumWays(self, nums: List[int], target: int) -> int: """ 设要加+的数(正数)和为P,要加-的数(负数)和为N,总和为S,则 P-N=target,P-N+P+N=target+P+N,P=(target+S)/2 要求转为:找到一些数,和为(target+S)/2,这样的组合数有多少种。注意是组合,不是排列,因为数值出现的顺序是固定的。 0/1背包问题,要逆序 设dp[i]为和为i的组合数,则dp[i]=sum(dp[i-c]) 注意:target1得是正数,否则无法创建数组 """ n = len(nums) if (target + sum(nums)) % 2 != 0: return 0 target1 = (target + sum(nums)) // 2 if target1 < 0: return 0 dp = [0] * (target1 + 1) dp[0] = 1 for c in nums: for i in reversed(range(c, target1+1)): dp[i] += dp[i-c] return dp[-1]
【Leetcode-560】
一、题目:和为K的子数组
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
数组的长度为 [1, 20,000]。
数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。
二、代码:
def subarraySum(self, nums: List[int], k: int) -> int: """ 前缀和,不能用滑窗因为有负数,不能用dp因为无递推公式 [0, i]前缀和为pre_sum,从[0,i)找和为pre_sum-k的位置j,i到j的差值为k。 记录和的数量,可能有个点的前缀和一样。 注意:初始时,有一个和为0,不然当0头开始的连续序列会找不到差值 """ sum_dict = {0: 1} # 和:数量 pre_sum = 0 res = 0 for num in nums: pre_sum += num res += sum_dict.get(pre_sum - k, 0) sum_dict[pre_sum] = sum_dict.get(pre_sum, 0) + 1 return res
【Leetcode-647】
一、题目:回文字串
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
二、代码:
def countSubstrings(self, s: str) -> int: """ dp[i][j]表示从i到j是否为回文字串 dp[i][j]=1当dp[i+1][j-1]且s[i]=s[j] """ n = len(s) dp = [[0] * n for _ in range(n)] for i in range(n): dp[i][i] = 1 # 初始长度为1 cnt = n for L in range(2, n+1): for i in range(n): j = L + i - 1 if j >= n: break if s[i] == s[j] and (j - i < 3 or dp[i+1][j-1] == 1): dp[i][j] = 1 cnt += 1 return cnt
【Leetcode-813】
一、题目:最大平均值的分组
我们将给定的数组 A 分成 K 个相邻的非空子数组 ,我们的分数由每个子数组内的平均值的总和构成。计算我们所能得到的最大分数是多少。
注意我们必须使用 A 数组中的每一个数进行分组,并且分数不一定需要是整数
二、代码:
def largestSumOfAverages(self, A: List[int], K: int) -> float:""" dp[i][k]表示0~i(包括i)元素分成k份能获得的均值总和,则 dp[i][k] = max(dp[j][k-1]+avg(j+1, i)), 0<=j<i dp[0][k] = A[0] ,表示0号元素拆分为K份的均值,为A[0]/1+0=A[0] dp[i][1] = avg(0, i),表示把0-i元素分成1份 返回值为dp[n-1][K] """ n = len(A) dp = [[0] * (K+1) for _ in range(n)] for k in range(1, K+1): dp[0][k] = A[0] for i in range(n): dp[i][1] = sum(A[:i+1])/(i+1) for i in range(1, n): for k in range(2, K+1): for j in range(i): dp[i][k] = max(dp[i][k], dp[j][k - 1] + sum(A[j + 1:i+1]) / (i - j)) return dp[n-1][K]
【Leetcode-1024】
一、题目:视频拼接
你将会获得一系列视频片段,这些片段来自于一项持续时长为 T 秒的体育赛事。这些片段可能有所重叠,也可能长度不一。
视频片段 clips[i] 都用区间进行表示:开始于 clips[i][0] 并于 clips[i][1] 结束。我们甚至可以对这些片段自由地再剪辑,例如片段 [0, 7] 可以剪切成 [0, 1] + [1, 3] + [3, 7] 三部分。
我们需要将这些片段进行再剪辑,并将剪辑后的内容拼接成覆盖整个运动过程的片段([0, T])。返回所需片段的最小数目,如果无法完成该任务,则返回 -1 。
1 <= clips.length <= 100
0 <= clips[i][0] <= clips[i][1] <= 100
0 <= T <= 100
二、代码:
def videoStitching(self, clips: List[List[int]], T: int) -> int: """ 分为判断是否能到达T和计算需要最少个数 判断是否能到达T:计算每个时间点能到达的点,若未到T走不动了(i最远只能到达i)说明不能到达T 计算最少个数:贪心选择,每次选个能到达时间最远的,用完了就换一个到达时间最远的,个数最少,需要记录前面的时间片可到达的地方 注意:T可能<clips所给范围 """ # 计算每个时间点能到达的时间点 arrive = [0] * (T) for s, e in clips: if s < T: arrive[s] = max(arrive[s], e) # 贪心选择 res = 0 now_arrive = 0 # 现在用的时间片可到达的地方,用于判断时间片是否用完 max_arrive = 0 # 已有的时间片可到达的地方,用于计算换时间片可以到达的地方 for time in range(T): max_arrive = max(max_arrive, arrive[time]) if time == max_arrive: return -1 if now_arrive == time: # 现在时间片用完,需要加时间片 res += 1 now_arrive = max_arrive return res """ dp[i]表示覆盖[0, i]需要的最小片段数 s, e = clips[j],dp[i]=min(dp[s])+1, s<=i<=e """ dp = [-1] * (time + 1) dp[0] = 0 for i in range(1, time + 1): for s, e in clips: if s < i <= e: if dp[i] != -1: dp[i] = min(dp[i], dp[s] + 1) else: dp[i] = dp[s] + 1 return dp[time]