这是动态规划的最后一个章节了,主要讲讲0-1 背包,数组区间,字符串编辑和其他一些动态规划的经典问题,那我们开始吧!
0-1 背包
问题描述:有n 个物品,它们有各自的重量和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?
总体思路:根据动态规划解题步骤(问题抽象化、建立模型、寻找约束条件、判断是否满足最优性原理、找大问题与小问题的递推关系式、填表、寻找解组成)找出01背包问题的最优解以及解组成,然后编写代码实现;
区间动态规划
区间 DP是指在一段区间上进行的一系列动态规划。 对于区间 DP 这一类问题,我们需要计算区间 [1,n] 的答案,通常用一个二维数组 dp 表示,其中 dp[x][y] 表示区间 [x,y]。
有些题目,dp[l][r] 由 dp[l][r−1] 与 dp[l+1][r] 推得;也有些题目,我们需要枚举区间 [l,r] 内的中间点,由两个子问题合并得到,也可以说 dp[l][r] 由 dp[l][k] 与 dp[k+1][r] 推得,其中 l≤k<r。 对于长度为 n 的区间 DP,我们可以先计算 [1,1],[2,2]…[n,n] 的答案,再计算 [1,2],[2,3]…[n−1,n],以此类推,直到得到原问题的答案。
下面是真题时间,这次真题有点多,可以慢慢看看。
416. 分割等和子集
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
每个数组中的元素不会超过 100
数组的大小不会超过 200
示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.
class Solution(object):
def canPartition(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
sum_nums = sum(nums)
if sum_nums%2 != 0:
return False
avg = sum_nums//2
n = len(nums)
dp = [[1] + [0] * avg for _ in range(n+1)]
for i in range(1,n+1):
for j in range(1,avg + 1):
dp[i][j] = dp[i-1][j]
if j-nums[i-1] >= 0:
if dp[i][j] == 0 and dp[i-1][j-nums[i-1]] == 1:
dp[i][j] = 1
return True if dp[-1][-1] == 1 else False
139. 单词拆分
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
注意你可以重复使用字典中的单词。
示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false
class Solution(object):
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: bool
"""
if not s:
return True
temp = [0]
for i in range(len(s) + 1):
for j in temp:
if s[j:i] in wordDict:
temp.append(i)
break
return temp[-1] == len(s)
494. 目标和
给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
示例 1:
输入: nums: [1, 1, 1, 1, 1], S: 3
输出: 5
解释:
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
一共有5种方法让最终目标和为3。
class Solution(object):
def findTargetSumWays(self, nums, S):
"""
:type nums: List[int]
:type S: int
:rtype: int
"""
sum_n = sum(nums)
if (sum_n+S) % 2 != 0 or sum_n<S:
return 0
return self.get_nums(nums,(S+sum_n)//2)
def get_nums(self,nums,S):
dp = [0]*(S+1)
dp[0] = 1
for num in nums:
for i in range(S,num-1,-1):
dp[i] += dp[i-num]
return dp[S]
322. 零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
说明:
你可以认为每种硬币的数量是无限的。
dp[i]表示金额为i需要最少的金额多少,
对于任意金额j,dp[j] = min(dp[j],dp[j-coin]+1),如果j-coin存在的话.
class Solution(object):
def coinChange(self, coins, amount):
"""
:type coins: List[int]
:type amount: int
:rtype: int
"""
dp = [float("inf")] * (amount+1)
dp[0] = 0
for i in range(1,amount+1):
for coin in coins:
if i >= coin and dp[int(i - coin)] != float('inf'):
dp[i] = min(dp[i],dp[i-coin]+1)
# print(dp)
return dp[-1] if dp[-1]!= float("inf") else -1
121. 买卖股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
class Solution(object):
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
n = len(prices)
min_p,max_p = 99999,0
for i in range(n):
min_p = min(min_p,prices[i])
max_p = max(max_p,prices[i] - min_p)
return max_p
303. 区域和检索 - 数组不可变
给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。
示例:
给定 nums = [-2, 0, 3, -5, 2, -1],求和函数为 sumRange()
sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3
说明:
你可以假设数组不可变。
会多次调用 sumRange 方法。
class NumArray(object):
def __init__(self, nums):
"""
:type nums: List[int]
"""
self.nums = nums
def sumRange(self, i, j):
"""
:type i: int
:type j: int
:rtype: int
"""
return sum(self.nums[i:j+1])
413. 等差数列划分
如果一个数列至少有三个元素,并且任意两个相邻元素之差相同,则称该数列为等差数列。
例如,以下数列为等差数列:
1, 3, 5, 7, 9
7, 7, 7, 7
3, -1, -5, -9
以下数列不是等差数列。
1, 1, 2, 5, 7
数组 A 包含 N 个数,且索引从0开始。数组 A 的一个子数组划分为数组 (P, Q),P 与 Q 是整数且满足 0<=P<Q<N 。
如果满足以下条件,则称子数组(P, Q)为等差数组:
元素 A[P], A[p + 1], ..., A[Q - 1], A[Q] 是等差的。并且 P + 1 < Q 。
函数要返回数组 A 中所有为等差数组的子数组个数。
示例:
A = [1, 2, 3, 4]
返回: 3, A 中有三个子等差数组: [1, 2, 3], [2, 3, 4] 以及自身 [1, 2, 3, 4]。
说明:dp[i] 表示比A[:i]到A[:i+1]新增加的等差数列子数组个数
拿[1,2,3,4]来说 1,2,3是等差数列,所有dp[2]=1,2,3,4相比于1,2,3来说增加了[2,3,4],[1,2,3,4]两个等差数列
对于[1,2,3,4,5],dp[4]新增加了[2,3,4,5],[3,4,5],[1,2,3,4,5],所以dp[5]=dp[4]+1=5
对于[1,2,3,4,5,7] 由于在7这里中断了前面等差数列,没有增加子等差数组,后面又就需要重新计数了
对于整个序列A,最后结果的就是sum(dp)
class Solution(object):
def numberOfArithmeticSlices(self, A):
"""
:type A: List[int]
:rtype: int
"""
n = len(A)
dp = [0] * n
for i in range(2,n):
if A[i] - A[i-1] == A[i-1] -A[i-2]:
dp[i] = dp[i-1] + 1
return sum(dp)
583. 两个字符串的删除操作
给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。
示例 1:
输入: "sea", "eat"
输出: 2
解释: 第一步将"sea"变为"ea",第二步将"eat"变为"ea"
说明:
给定单词的长度不超过500。
给定单词中的字符只含有小写字母。
思路:这道题给了我们两个单词,问我们最少需要多少步可以让两个单词相等,每一步我们可以在任意一个单词中删掉一个字符。可以用dp来做,就定义一个二维的dp数组,其中dp[i][j]表示word1的前i个字符和word2的前j个字符组成的两个单词,能使其变相同的最小的步数。
下面来看递推式dp[i][j]怎么求,首先来考虑dp[i][j]和dp[i-1][j-1]之间的关系,我们可以发现,如果当前的两个字符相等,那么dp[i][j] = dp[i-1][j-1] + 1,因为最长相同子序列又多了一个相同的字符,所以长度加1。由于我们dp数组的大小定义的是(n1+1) x (n2+1),所以我们比较的是word1[i-1]和word2[j-1]。那么我们想如果这两个字符不相等呢,难道我们直接将dp[i-1][j-1]赋值给dp[i][j]吗,当然不是,我们还要错位相比嘛,比如就拿题目中的例子来说,”sea”和”eat”,当我们比较第一个字符,发现’s’和’e’不相等,下一步就要错位比较啊,比较sea中第一个’s’和eat中的’a’,sea中的’e’跟eat中的第一个’e’相比,这样我们的dp[i][j]就要取dp[i-1][j]跟dp[i][j-1]中的较大值了,最后我们求出了最大共同子序列的长度,就能直接算出最小步数了,参见代码如下:
class Solution(object):
def minDistance(self, word1, word2):
"""
:type word1: str
:type word2: str
:rtype: int
"""
n = len(word1)
m = len(word2)
dp = [[0]*(m+1) for _ in range(n+1)]
for i in range(1,n+1):
for j in range(1,m+1):
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j],dp[i][j-1])
return m+n-2*dp[-1][-1]
646. 最长数对链
给出 n 个数对。 在每一个数对中,第一个数字总是比第二个数字小。
现在,我们定义一种跟随关系,当且仅当 b < c 时,数对(c, d) 才可以跟在 (a, b) 后面。我们用这种形式来构造一个数对链。
给定一个对数集合,找出能够形成的最长数对链的长度。你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造。
示例 :
输入: [[1,2], [2,3], [3,4]]
输出: 2
解释: 最长的数对链是 [1,2] -> [3,4]
注意:
给出数对的个数在 [1, 1000] 范围内。
class Solution(object):
def findLongestChain(self, pairs):
"""
:type pairs: List[List[int]]
:rtype: int
"""
pairs.sort(key = lambda ele:ele[1])
cur,res = -sys.maxsize,0
for pair in pairs:
if pair[0] > cur:
res += 1
cur = pair[1]
else:
continue
return res
希望本文能对你有所帮助!
最后打个小广告,我的公众号,会写点学习心得,喜欢可以关注下!~!