先写了一段回溯
class Solution:
def findTargetSumWays(self, nums: List[int], S: int) -> int:
n = len(nums)
length = n
cnt = 0
def walk(index, ss):
nonlocal cnt
if index == length and ss == S:
cnt += 1
if index < length:
n = nums[index]
walk(index+1, ss+n)
walk(index+1, ss-n)
ss = 0
walk(0, ss)
return cnt
果然超时了。呜呜呜
其实这道题是一道背包问题, dp[i][j]为用前i个数字组成和为j的方案数目。
那么 dp[i][j] = dp[i-1][j-nums[i]] + dp[i-1][j+nums[i]]
然后这道题在初始化dp的时候还有一个坑,必须用+=,因为如果第一维是0,-+0还是0,则用一个数字组成和为0的方案就是2不是1
class Solution:
def findTargetSumWays(self, nums: List[int], S: int) -> int:
n = len(nums)
length = n
cnt = 0
dp = [[0 for _ in range(2001)] for _ in range(n)]
dp[0][nums[0]] += 1
dp[0][-nums[0]] += 1
for i in range(1, n):
for j in range(-1000, 1001):
if j - nums[i] >= -1000:
dp[i][j] += dp[i-1][j-nums[i]]
if j + nums[i] < 1001:
dp[i][j] += dp[i-1][j+nums[i]]
if S > 1000:
return 0
return dp[-1][S]
联想到之前leetcode 5. 最长回文子串,但第五题是要求子串是挨在一起的,这道题则不是,之间隔有其他字符都行,且找的是长度而不是具体哪个子串。
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
n = len(s)
dp = [[0] * n for _ in range(n)]
for i in range(n):
dp[i][i] = 1
for i in range(n-1, -1, -1):
for j in range(i+1, n):
if s[i] == s[j]:
dp[i][j] = dp[i+1][j-1] + 2
else:
dp[i][j] = max(dp[i+1][j], dp[i][j-1])
return dp[0][-1]
在LeetCode商店中, 有许多在售的物品。
然而,也有一些大礼包,每个大礼包以优惠的价格捆绑销售一组物品。
现给定每个物品的价格,每个大礼包包含物品的清单,以及待购物品清单。请输出确切完成待购清单的最低花费。
每个大礼包的由一个数组中的一组数据描述,最后一个数字代表大礼包的价格,其他数字分别表示内含的其他种类物品的数量。
任意大礼包可无限次购买。
思路:
物品可以单买,也可以买大礼包,但不能超过指定的物品数目。因此每次递归,都可以去过滤一下大礼包。仅保留数目小于等于需求的大礼包。然后当需求为0的时候,就是递归最深的时候。另外,如果大礼包都被过滤掉了,那就只能单买。可是如何确保大礼包一定比单买实惠呢?这就要先去过滤掉不实惠的礼包了。
class Solution:
def shoppingOffers(self, price: List[int], special: List[List[int]], needs: List[int]) -> int:
l = len(price) # number of object
def walk(special, needs):
if sum(needs) == 0:
return 0
special = list(filter(lambda x : all(x[i] <= needs[i] for i in range(l)), special))
if not special:
return sum(price[i]*needs[i] for i in range(l))
res = []
for pac in special:
res.append(pac[-1]+ walk(special, [needs[i]-pac[i] for i in range(l)]))
return min(res)
# 先过滤不实惠的大礼包 则递归的时候优先使用大礼包,只有没有礼包的时候才单买。
special = list(filter(lambda x: x[-1] < sum(price[i]*x[i] for i in range(l)), special))
return walk(special,needs)
题目:
数组的每个索引作为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 cost[i]
每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。
您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。
思路:
我刚才以为是打家劫舍的翻版,后来思考了一下,二者差不多吧。
设dp[i]为,为来到第i层付出的代价(打家劫舍是偷了第i家获得的最大价值),注意我用的是来到,而非离开第i个台阶。
为了来到第i个台阶,我们可以用第i-1个台阶出发,付出的代价是dp[i-1]+cost[i-1];也能从第i-2个台阶出发,付出的代价是多少不用说了吧。
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
n = len(cost)
dp = [0] * (n)
dp[0] = dp[1] = 0
for i in range(2, n):
if i == 2:
dp[i] = min(cost[0], cost[1])
else:
dp[i] = min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2])
return min(dp[n-1]+cost[n-1], dp[n-2]+cost[n-2])
题目:给定一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。
思路:题解有用栈做的,我看不明白。这里介绍一下用dp矩阵的解法。
dp[i]描述的是以第i个字符为结尾的最大有效括号数目。
class Solution:
def longestValidParentheses(self, s: str) -> int:
n = len(s)
dp = [0] * (n + 1)
for i in range(2, n + 1):
if s[i - 1] == ')':
if s[i - 2] == '(':
dp[i] = dp[i - 2] + 2
elif s[i - 2] == ')':
if i - dp[i - 1] - 2 >= 0 and s[i-dp[i-1]-1-1] == '(':
dp[i] += dp[i - 1] + 2
dp[i] += dp[i - dp[i - 1] - 2]
# print(dp)
return max(dp)
leetcode知名题目
有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。求所能获得硬币的最大数量。
class Solution:
def maxCoins(self, nums) -> int:
nums = [1] + nums + [1]
res = 0
def walk(choices, sum):
nonlocal res
if len(choices) == 2:
res = max(res, sum)
return
for i in range(1, len(choices)-1):
p = choices[i]
walk(choices[:i]+choices[i+1:], sum+(p*choices[i-1]*choices[i+1]))
walk(nums, 0)
return res
动态规划的思路是
class Solution:
def maxCoins(self, nums) -> int:
n = len(nums)
nums = [1] + nums + [1]
dp = [[0]*(n+2) for _ in range(n+2)]
for i in range(n, -1, -1):
for j in range(i+1, n+2):
for k in range(i+1, j):
dp[i][j] = max(dp[i][j], dp[i][k]+dp[k][j]+ nums[i]*nums[j]*nums[k])
return dp[0][-1]
还是单调栈
假设你有一个特殊的键盘包含下面的按键:
Key 1: (A):在屏幕上打印一个 ‘A’。
Key 2: (Ctrl-A):选中整个屏幕。
Key 3: (Ctrl-C):复制选中区域到缓冲区。
Key 4: (Ctrl-V):将缓冲区内容输出到上次输入的结束位置,并显示在屏幕上。
现在,你只可以按键 N 次(使用上述四种按键),请问屏幕上最多可以显示几个 'A’呢?
class Solution:
def maxA(self, N: int) -> int:
dp = [0] * (N+1)
n = N
dp[1] = 1
for i in range(2, n+1):
dp[i] = 1+dp[i-1]
for j in range(1, i-2):
dp[i] = max(dp[i], dp[j]*(i-j-2+1))
# print(dp)
return dp[-1]
允许连续颜色出现,但最大连续长度是2个。因为设dp[i][0]为选择的颜色和i-1不一样。dp[i][1]代表选择的颜色和i-1一样。
class Solution:
def numWays(self, n: int, k: int) -> int:
if not n or not k:
return 0
dp = [[0,0] for _ in range(n)]
# dp[i][0] : 和i-1的颜色不一样
# dp[i][1] : 和i-1的颜色一样
dp[0][0] = k
dp[0][1] = 0
for i in range(1, n):
dp[i][0] = (k-1)*(dp[i-1][0]+dp[i-1][1])
dp[i][1] = dp[i-1][0]
return sum(dp[-1])
题目:
给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。
这道题的解法也是很巧妙了,不愧是hard难度的题目。
首先第一种解法,我们可以暴力搜索,每个递归里面有两种选择,当前遍历的数字可以附加到之前的数组中,也可以自己作为一个新的子数组。(超时)
动态规划的办法确实很巧妙。
class Solution:
def splitArray(self, nums: List[int], m: int) -> int:
n = len(nums)
if n <= m:
return max(nums)
dp = [[float('inf')]*(m+1) for _ in range(n+1)]
sub = [0] * (n+1)
for i in range(n):
sub[i+1] = sub[i] + nums[i]
dp[0][0] = 0 # 这里的初始化太精辟了。用于解决 max(dp[k][j-1]
# , sub[i]-sub[k]))始终返回float(inf)的问题
for i in range(1, n+1):
for j in range(1, m+1):
for k in range(i):
# if j == 1:
# print(dp[k][j-1])
dp[i][j] = min(dp[i][j], max(dp[k][j-1], sub[i]-sub[k]))
print(dp)
return dp[-1][-1]
一只青蛙想要过河。 假定河流被等分为 x 个单元格,并且在每一个单元格内都有可能放有一石子(也有可能没有)。 青蛙可以跳上石头,但是不可以跳入水中。
给定石子的位置列表(用单元格序号升序表示), 请判定青蛙能否成功过河(即能否在最后一步跳至最后一个石子上)。 开始时, 青蛙默认已站在第一个石子上,并可以假定它第一步只能跳跃一个单位(即只能从单元格1跳至单元格2)。
如果青蛙上一步跳跃了 k 个单位,那么它接下来的跳跃距离只能选择为 k - 1、k 或 k + 1个单位。 另请注意,青蛙只能向前方(终点的方向)跳跃。
解法:
我写的暴力搜索超时。原因是重复的状态太多。这道题我学到一个东西,如果某种状态的选择数目不固定,我们不能用dp[][]的方式初始化dp矩阵,但我们可以用散列表,比如dp[i] = set()。第i个位置的各种状态都加到set中去,还能去重。
class Solution:
def canCross(self, stones: List[int]) -> bool:
# end = stones[-1]
# def walk(pos, steps):
# if pos > end:
# return False
# if pos not in stones:
# return False
# if pos == end:
# return True
# next_step = [steps-1, steps, steps+1]
# for step in next_step:
# if step <=0:
# continue
# if walk(pos+step, step):
# return True
# return False
# return walk(1, 1)
# 当需要记录每个状态的不同选择的值,但选择数目不固定 不好用dp,则可以使用散列表
rec = {}
for s in stones:
rec[s] = set()
rec[stones[0]].add(0)
for stone in stones[:-1]:
if stone == 0:
if stone+1 not in rec:
return False
else:
rec[1].add(1)
else:
steps = rec[stone]
for step in steps:
for new_step in range(step-1, step+2):
if new_step >0 and stone+new_step in rec:
if stone+new_step == stones[-1]:
return True
rec[stone+new_step].add(new_step)
if len(rec[stones[-1]]):
return True
return False
给定一个字符串 S 和一个字符串 T,计算在 S 的子序列中 T 出现的个数。
一个字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)
和编辑距离那道题有点相似。都是怎么把S变到T,但是这题要统计的是有多少种变法,编辑距离统计的是最小步数;且这题仅支持删除操作。
思路:dp[i][j]表示S的前i个字符变到T的前j个的次数。如果s[i]=T[j],则dp[i][j]有两个来源,首先我们可以选择用S的i来匹配T的j,也可以不用S的i,则继承dp[i-1][j]。如果S[i]!=T[j], 我们只能继承dp[i-1][j]。 那为啥不是dp[i][j-1]呢,因为我们的目的是S变到T,而不是T变到S。
class Solution:
def numDistinct(self, s: str, t: str) -> int:
n1, n2 = len(s), len(t)
dp = [[0]* (n2+1) for _ in range(1+n1)]
for i in range(1+n1): # 初始化 s的字符全部删除的方案数为1
dp[i][0] = 1
for i in range(1, n1+1):
for j in range(1, n2+1):
if s[i-1] == t[j-1]:
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
else:
dp[i][j] = dp[i-1][j]
return dp[-1][-1]
有 n 个城市通过 m 个航班连接。每个航班都从城市 u 开始,以价格 w 抵达 v。
现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到从 src 到 dst 最多经过 k 站中转的最便宜的价格。 如果没有这样的路线,则输出 -1。
解法:最大能走K+1步。我们可以使用递归(超时),我写了记忆化的递归但是有些案例不能通过。动态规划的解法是:dp[k][v]是在最多k步之后,到达v的最小费用。初始化需要把dp[0][src]设置为0,其他设置为数值上限。
class Solution:
def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, K: int) -> int:
dp = [[float('inf')]*n for _ in range(K+2)]
dp[0][src] = 0
for i in range(1, K+2):
for u,v,w in flights:
dp[i][v] = min(dp[i-1][v], dp[i-1][u] + w, dp[i][v])
print(dp)
return dp[-1][dst] if dp[-1][dst] != float('inf') else -1