解题思路:
先dp一下,闪现的距离,dp[i]表示:第i秒闪现能走多远(dp[i]=dp[i-1]+60);
然后再算可以走路的时候 第i秒可以走多远,(dp[i]=dp[i-1]+17);
这里第二次dp是直接使用第一次dp后的结果数组的,相当于使用了闪现的最佳距离,在此基础上尝试走路。
[M, S, T] = list(map(int,input().split()))
dp = [0 for i in range(T+1)]
for i in range(1, T+1):
if M >= 10:
dp[i] = dp[i-1] + 60
M -= 10
else:
dp[i] = dp[i-1]
M += 4
for i in range(1,T+1):
dp[i] = max(dp[i],dp[i-1]+17)
if dp[i] >= S:
print('Yes')
print(i)
break
if dp[T] < S and dp[T] != 0:
print('No')
print(dp[T])
不难看出,路程长度由两个部分组成:左右走的距离 ++ 上下走的距离,因为上下走的距离一定是 nn,所以我们只需要求出左右走的最小距离即可。
很明显,当我们走完一行的线段时,我们会处在左端点/右端点,这样我们就需要在线性 dpdp 的基础上加一维来表示走完这一行处在左/右端点。
所以我们定义一个 f[i][0/1]f[i][0/1],表示走完第 ii 行的线段后,我们处在左/右端点。
然后我们就可以用上一行的状态来推出下一行了。
分为 44 种情况:
上一行在左端点,下一行走到左端点; 上一行在左端点,下一行走到右端点; 上一行在右端点,下一行走到左端点; 上一行在右端点,下一行走到右端点;
我们以"上一行在右端点,下一行走到右端点"为例:
这样我们就可以推出状态转移方程了。
- f[i][1]=min(f[i-1][1]+abs(l[i]-r[i-1])+r[i]-l[i]+1,f[i-1][0]+abs(l[i]-l[i-1])+r[i]-l[i]+1);//本行走到右端点,上一行走到左/右端点,取最小值
- f[i][0]=min(f[i-1][1]+abs(r[i]-r[i-1])+r[i]-l[i]+1,f[i-1][0]+abs(r[i]-l[i-1])+r[i]-l[i]+1);//本行走到左端点,上一行走到左/右端点,取最小值
n = int(input())
l = [[0,0] for i in range(n+1)]
dp = [[0,0] for i in range(n+1)]
for i in range(1,n+1):
l[i] = list(map(int,input().split()))
for i in range(1,n+1):
if i == 1:
dp[1][1] = l[1][1] - 1
dp[1][0] = 2*l[1][1] - l[1][0] - 1
continue
else:
temp = l[i][1] - l[i][0] + 1
dp[i][0] = min(dp[i-1][0] + abs(l[i-1][0] - l[i][1]) + temp, dp[i-1][1] + abs(l[i-1][1] - l[i][1]) + temp)
dp[i][1] = min(dp[i-1][0] + abs(l[i-1][0] - l[i][0]) + temp, dp[i-1][1] + abs(l[i-1][1] - l[i][0]) + temp)
print(min(dp[n][0] + n - l[n][0], dp[n][1] + n - l[n][1]))
转自Rubyonly 的博客
问题描述:
有N件物品和⼀个最多能被重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。 每件物品只能⽤⼀次,求解将哪些物品装⼊背包⾥物品价值总和最⼤。
要点提取:
二维数组:dp[i][j] 表示从下标为[0-i]的物品⾥任意取,放进容量为j的背包,价值总和最⼤是多少。
一维数组:dp[j]表示:容量为j的背包,所背的物品价值可以最⼤为dp[j]。
二维数组:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])(逗号前面说明不选第i个物品,逗号后面表明选择第i个物品)
一维数组:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
二维数组: 从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],⽆论是选取哪些物品,背包价值总和⼀定为0。
一维数组:dp[j]表示:容量为j的背包,所背的物品价值可以最⼤为dp[j],那么dp[0]就应该是0,因为背包容量为0所背的物品的最⼤价值就是0。
# 二维遍历,先遍历物品,在遍历背包
for i in range(1,len(weight)+1): # 遍历物品
for j in range(bagWeight+1): # 二维数组正序遍历背包容量
if (j < weight[i]): dp[i][j] = dp[i - 1][j] # 这个是为了展现dp数组⾥元素的变化
else: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
# 一维滚动数组
for i in range(len(weight)): #遍历物品
for j in range(bagWeight, weight[i] - 1, -1): # 内层循环需要逆序遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
5.举例推导dp数组
一定要注意j(内层循环)的取值范围!!!开辟的dp数组的大小,含义,然后推导数组验证是否正确。
**如果题目中要求最大或最小,那么递推公式一定是min或者max(dp[j],dp[j-nums[i]]+nums[i]),如果是方法数(或组合数),呢么递推公式一定是dp[j] += dp[j-nums[i]]
题目描述:给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
----------------------------------------示例二------------------------------------------------------
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
** 要点提取:**
解题思路分析:
01背包中, dp[j] 表示: 容量为j的背包,所背的物品价值可以最⼤为dp[j]。
套到本题, dp[j]表示: 背包总容量是j,最⼤可以凑成i的⼦集总和为dp[j]。
01背包的递推公式为: dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
本题,相当于背包⾥放⼊数值,那么物品i的重量是nums[i],其价值也是nums[i]。
所以递推公式: dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
背包的大小为sum(num)/2,所以需要开辟这么大的空间,并且保证这个值可以取到;
另一方面从dp[j]的定义来看,⾸先dp[0]⼀定是0,且题⽬给的价值都是正整数那么⾮0下标都初始化为0就可以了。这样才能让dp数组在递归公式的过程中取的最⼤的价值,⽽不是被初始值覆盖了。
每个数组中的元素不会超过 100,数组的⼤⼩不会超过 200
所以总和不会⼤于20000,背包最⼤只需要其中⼀半,所以10001⼤⼩就可以了
确定遍历顺序
0-1背包,一维数组,内层循环倒序遍历
举例推导数组
j的取值范围,涉及到nums[i]是否选择的问题,所以范围为nums[i]~target(=sum(nums)/2)的闭区间。
class Solution:
def canPartition(self, nums: List[int]) -> bool:
target = sum(nums)
if target % 2 == 1:
return False
else:
target = target // 2
dp = [0 for i in range(10001)]
flag = False
for i in range(len(nums)):
for j in range(target+1,nums[i] - 1,-1):
dp[j] = max(dp[j],dp[j-nums[i]] + nums[i])
return dp[target] == target
题意解析:
尽量让石头分成两堆,让相撞之后剩下的石头最小,
本题物品的重量为stone[i],物品的价值也为stone[i]
解题思路分析:
dp[j]表示容量(这⾥说容量更形象,其实就是重量)为j的背包,最多可以背dp[j]这么重的⽯头。
dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
a. dp[j]中的j表示容量,那么最⼤容量(重量)就是所有⽯头的重量和30 * 1000,⽽本题将数组分成两部分,所以要求的target其实只是最⼤重量的⼀半,所以dp数组开到15000⼤⼩就可以了。
b. 因为重量都不会是负数,所以dp[j]都初始化为0就可以了
使⽤⼀维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒叙遍历!
关于j的取值:因为涉及到第i个物品选不选,所以j的取值为stones[i]~target的闭区间
最后返回两个数组的差((sum - target) - target)
class Solution:
def lastStoneWeightII(self, stones: List[int]) -> int:
dp = [0]*15001
temp = sum(stones)
target = temp// 2
for i in range(len(stones)):
for j in range(target,stones[i] - 1,-1):
dp[j] = max(dp[j],dp[j-stones[i]] + stones[i])
return (temp - dp[target]) - dp[target]
注意到本题是装满有几种方法,求的是组合数
本题的思想也是将给定的数组分成两个部分,一部分全是由正数,另一部分全是负数,他们之间的关系如下:
假设加法的总和为x,那么减法对应的总和就是sum - x。
所以我们要求的是 x - (sum - x) = S
x = (S + sum) / 2
此时问题就转化为,装满容量为x背包,有⼏种⽅法。
看到(S + sum) / 2 应该担⼼计算的过程中向下取整有没有影响如在本题中sum 是5, S是2的话是⽆解的。
二维数组:dp[i][j]:使⽤ 下标为[0, i]的nums[i]能够凑满j(包括j)这么⼤容量的包,有dp[i][j]种⽅法。
一维数组:dp[j] 表示:填满j(包括j)这么⼤容积的包,有dp[j]种⽅法
所有求组合类问题的公式,都是类似这种:
dp[j] += dp[j - nums[i]]
不考虑nums[i]的情况下,填满容量为j - nums[i]的背包,有dp[j - nums[i]]种⽅法。
那么只要搞到nums[i]的话,凑成dp[j]就有dp[j - nums[i]] 种⽅法。
举⼀个例⼦,nums[i] = 2: dp[3],填满背包容量为3的话,有dp[3]种⽅法。
那么只需要搞到⼀个2(nums[i]),有dp[3]⽅法可以凑⻬容量为3的背包,相应的就有多少种⽅法可以
凑⻬容量为5的背包。
那么需要把 这些⽅法累加起来就可以了, dp[] += dp[j - nums[i]]
对于01背包问题⼀维dp的遍历, nums放在外循环, target在内循环,且内循环倒序。
j的取值范围,涉及到选不选第i个物品,所以取值还是nums[i]~target的闭区间
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
temp_s = sum(nums)
if target > temp_s:
return 0
if ((target + temp_s) % 2 == 1):
return 0
temp_target = (target + temp_s) // 2
dp = [0] * 1001
dp[0] = 1
for i in range(len(nums)):
for j in range(temp_target, nums[i] - 1,-1):
dp[j] += dp[j-nums[i]]
return dp[temp_target]
题意解析:
本题本题其实是01背包问题!其中strs 数组⾥不同⻓度的字符串元素就是不同⼤⼩的待装物品,⽽m 和 n相当于是⼀个背包,两个维度的背包。
解题思路分析:
dp[i][j]:最多有i个0和j个1的strs的最⼤⼦集的⼤⼩为dp[i][j]
dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历
class Solution:
def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
dp = [[0 for i in range(n+1)] for j in range(m+1)]
for e in range(len(strs)):
temp = strs[e]
one_num = temp.count('1')
zero_num = temp.count('0')
for i in range(m, zero_num-1,-1):
for j in range(n, one_num-1,-1):
dp[i][j] = max(dp[i][j], dp[i-zero_num][j-one_num] + 1)
return dp[m][n]
问题描述:
有N件物品和⼀个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。 每件物品都有⽆限个(也就是可以放⼊背包多次) ,求解将哪些物品装⼊背包⾥物品价值总和最⼤。
与0-1背包的区别:
- 每个物品可以取无限次,所以内层for循环要正序遍历
"""0-1背包"""
for i in range(len(weight)): # 遍历物品
for j in range(bagWeight+1,weight[i]-1,-1): # 反向遍历背包容量且注意j的取值
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
"""完全背包"""
for i in range(lenweight)): # 遍历物品
for j in range(weight[i]-1,bagWeight): # 正向遍历背包容量,且j只取到背包容量减1(bagWeight-1)
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
装满背包有几种方式:
针对这类问题,两个for循环的先后顺序非常重要,分别对应两类问题,组合数(与数字的顺序无关)和排列数(与数字的顺序有关,比如{1,3}和{3,1}为两种方式的情况)
题意分析:
钱币数量不限,这是⼀个完全背包。
但本题和纯完全背包不⼀样, 纯完全背包是能否凑成总⾦额,⽽本题是要求凑成总⾦额的个数!
注意题⽬描述中是凑成总⾦额的硬币组合数,为什么强调是组合数呢?
例如示例⼀:
5 = 2 + 2 + 1
5 = 2 + 1 + 2
这是⼀种组合,都是 2 2 1。
如果问的是排列数,那么上⾯就是两种排列了。
组合不强调元素之间的顺序,排列强调元素之间的顺序。
dp[j]:凑成总⾦额j的货币组合数为dp[j]
dp[j] (考虑coins[i]的组合总和) 就是所有的dp[j - coins[i]](不考虑coins[i])相加。
dp[j] += dp[j - coins[i]];
组合数,dp[0] = 1是 递归公式的基础。
根据j的定义,背包最大为amount,所以开辟amount+1的数组空间
纯完全背包求得是能否凑成总和,和凑成总和的元素有没有顺序没关系,即:有顺序也⾏,没有顺
序也⾏!⽽本题要求凑成总和的组合数,元素之间要求没有顺序。外层for循环遍历物品(钱币),内层for遍历背包(⾦钱总额)时,这种遍历顺序中dp[j]⾥计算的是组合数!
本题还是考虑第i个数字选不选,所以j的取值范围为nums[j]~amount的闭区间
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
dp = [0] * (amount+1)
dp[0] = 1
for i in range(len(coins)):
for j in range(coins[i],amount+1):
dp[j] += dp[j - coins[i]]
return dp[amount]
题意分析:
本题其实就是求排列!组合不强调顺序, (1,5)和(5,1)是同⼀个组合。排列强调顺序, (1,5)和(5,1)是两个不同的排列。
解题思路分析:
dp[i]: 凑成⽬标正整数为i的排列个数为dp[i]
求装满背包有⼏种⽅法,递推公式⼀般都是dp[i] += dp[i - nums[j]];
- dp[0]要初始化为1
- 初始化长度为target+1的数组空间
个数可以不限使⽤,说明这是⼀个完全背包。 得到的集合是排列,说明需要考虑元素之间的顺序
- 如果求组合数就是外层for循环遍历物品,内层for遍历背包。
- 如果求排列数就是外层for遍历背包,内层for循环遍历物品
target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历。
for (int j = 0; j <= amount; j++) { // 遍历背包容量
for (int i = 0; i < coins.size(); i++) { // 遍历物品
if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
}
}
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
dp = [0] * (target + 1)
dp[0] = 1
for i in range(target + 1):
for j in range(len(nums)):
if i - nums[j] >= 0: # 要保证第j个元素能够取到,所以这里的判断条件一定是大于等于零
dp[i] += dp[i-nums[j]]
return dp[target]
解题思路分析:
dp[j]:凑⾜总额为j所需钱币的最少个数为dp[j]
dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
凑⾜总⾦额为0所需钱币的个数⼀定是0,那么dp[0] = 0
考虑到递推公式的特性, dp[j]必须初始化为⼀个最⼤的数,否则就会在min(dp[j - coins[i]] + 1, dp[j])⽐
较的过程中被初始值覆盖。
4.确定遍历顺序
- 本题求钱币最⼩个数,并不强调集合是组合还是排列。 那么钱币有顺序和没有顺序都可以,都不影响钱币的最⼩个数。
- 如果求组合数就是外层for循环遍历物品,内层for遍历背包。
- 如果求排列数就是外层for遍历背包,内层for循环遍历物品。
考虑第i个物品选不选的情况,j的取值范围还是nums[j]~amount的闭区间
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
if amount == 0:
return 0
else:
dp = [amount+1] * (amount + 1)
dp[0] = 0
for i in range(len(coins)):
for j in range(coins[i],amount+1):
dp[j] = min(dp[j], dp[j-coins[i]]+1)
return dp[amount] if dp[amount] < amount + 1 else -1
- dp[i]:和为i的完全平⽅数的最少数量为dp[i]
- dp[j] = min(dp[j - i * i] + 1, dp[j]);
- dp[0]⼀定是0;⾮0下标的dp[i]⼀定要初始为最⼤值,这样dp[j]在递推的时候才不会被初始值覆盖。
- 求最⼩数,遍历顺序都可以
- j的取值为1到根号i的闭区间
class Solution:
def numSquares(self, n: int) -> int:
dp = [n+1] * (n+1) # 非0下标要初始化为最大值,dp[j]在递推的时候才不会被覆盖
dp[0] = 0
for i in range(n+1):
for j in range(1, int(pow(n,0.5))+1): # j的取值为1到根号i的闭区间
dp[i] = min(dp[i],dp[i-j*j] + 1)
return dp[n]