题目描述
解题思路
转移方程的话已经在代码中写到了,解题思路感觉这个大佬写的最清晰,即如果当前对比的位置字符相同,我可以选择进行匹配或者不进行匹配,因为从右往左的顺序进行匹配的话,即使相同前面可能也会有相同的,所以有两种选择。
代码实现
class Solution:
def numDistinct(self, s: str, t: str) -> int:
#统计两个字符串的长度
m,n=len(s),len(t)
#以t为子串,如果t比s长,不符合题意
if m<n:
return 0
#建立储存的状态矩阵
dp=[[0]*(n+1) for _ in range(m+1)]
#初始化,如果n=0,那么它可以使s的任何子串
for i in range(m+1):
dp[i][n]=1
#如果m=0,没有字串
#开始遍历
for i in range(m-1,-1,-1):
for j in range(n-1,-1,-1):
#如果当前字母匹配
if s[i]==t[j]:
#那么有可能是从s+1,j+1转移,也可能是s+1,j转移
dp[i][j]=dp[i+1][j+1]+dp[i+1][j]
else:
#如果不相等,只能考虑s+1,j
dp[i][j]=dp[i+1][j]
return dp[0][0]
题目描述
解题思路
代码实现
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
#设置头节点
dummy=ListNode(-1)
dummy.next=head
#前节点
pre=dummy
#定位到left的前一个位置
for i in range(left-1):
pre=pre.next
#定位到left的第一个位置
cur=pre.next
#遍历需要反转的区域
for j in range(right-left):
#找到前一个节点
next=cur.next
#将cur指向前一个的前一个
cur.next=next.next
#将next指向cur
next.next=pre.next
#将pre指向next
pre.next=next
return dummy.next
题目描述
解题思路
这题想通过还是很简单的,不做赘述了。
代码实现
class ParkingSystem:
def __init__(self, big: int, medium: int, small: int):
self.big=big
self.medium=medium
self.small=small
def addCar(self, carType: int) -> bool:
if carType==3:
if self.small>=1:
self.small-=1
return True
else:
return False
elif carType==2:
if self.medium>=1:
self.medium-=1
return True
else:
return False
else:
if self.big>=1:
self.big-=1
return True
else:
return False
题目描述
解题思路
代码实现
class Solution:
def wordPattern(self, pattern: str, s: str) -> bool:
word2chr={}
chr2word={}
#如果长度不相等,直接返回错
word_list=s.split()
if len(pattern)!=len(word_list):
return False
for char,word in zip(pattern,word_list):
#如果词在词典中出现,但对应的字母和word2chr的不同
#如果字母在字典中出现,但对应的词和chr2word存储的不同
if (word in word2chr and word2chr[word] !=char) or (char in chr2word and chr2word[char]!=word):
return False
word2chr[word]=char
chr2word[char]=word
return True
题目描述
解题思路
把这道题想得简单些,加入输入的numsRows有三行,那么添加字符串中的顺序应该是012|1|012|1
也就是说只要定义一个变量去存储方向状态即可
代码实现
class Solution:
def convert(self, s: str, numRows: int) -> str:
if numRows < 2: return s
res = ["" for _ in range(numRows)]
i, flag = 0, -1
for c in s:
res[i] += c
if i == 0 or i == numRows - 1: flag = -flag
i += flag
return "".join(res)
作者:jyd
链接:https://leetcode-cn.com/problems/zigzag-conversion/solution/zzi-xing-bian-huan-by-jyd/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
题目描述
代码实现+步骤详解
class Solution:
def hasGroupsSizeX(self, deck: List[int]) -> bool:
#统计牌组中每一种牌出现的次数
count_list=collections.Counter(deck)
#统计长度
n=len(deck)
#对X进行遍历
for x in range(2,n+1):
#条件1:x是长度的约数
if n%x==0:
#条件2:x是每个牌出现次数的约数
if all(v%x==0 for v in count_list.values()):
return True
#如果遍历完所有x取值的可能都没达到条件,返回False
return False
题目描述
代码实现
第一次遍历,存储matrix位置为0的row和col,第二次遍历将row和
col为0的所有位置都变成0。
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
row=len(matrix)
col=len(matrix[0])
r,c=[0]*row,[0]*col
for i in range(row):
for j in range(col):
if matrix[i][j]==0:
r[i]=1
c[j]=1
for i in range(row):
for j in range(col):
if r[i]==1 or c[j]==1:
matrix[i][j]=0
题目描述
解题思路
维护一个栈,遍历tokens
代码实现
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
stack=[]
for token in tokens:
if token in ["+","-","/","*"]:
b=stack.pop()
c=stack.pop()
if token=="*":
stack.append(b*c)
elif token=="/":
stack.append(int(c/b))
elif token=="+":
stack.append(b+c)
else:
stack.append(c-b)
else:
stack.append(int(token))
return int(stack[-1])
题目描述
解题思路
这种简单的题是真实存在的么…
代码实现
class MyQueue:
def __init__(self):
"""
Initialize your data structure here.
"""
self.queue=[]
def push(self, x: int) -> None:
"""
Push element x to the back of queue.
"""
self.queue.append(x)
def pop(self) -> int:
"""
Removes the element from in front of queue and returns that element.
"""
return self.queue.pop(0)
def peek(self) -> int:
"""
Get the front element.
"""
return self.queue[0]
def empty(self) -> bool:
"""
Returns whether the queue is empty.
"""
return len(self.queue)==0
题目描述
解题思路
做多了dp感觉这种题还是很简单的。主要说一下三个核心点:
代码实现
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
n=len(nums)
#维护一个列表储存每个下标对应的下一个最大值
res=[-1]*n
#建立一个栈,存储的是下标
stack=[]
#因为是循环数组,遍历2n次
for i in range(n*2-1):
#只要栈中的元素比当前元素小,就pop
while stack and nums[stack[-1]]<nums[i%n]:
res[stack.pop()]=nums[i%n]
stack.append(i%n)
return res
题目描述
解题思路
这道题分为递归和dp两个部分
1、dp部分::判断
判断i到j是否为回文串,如果当前i和j的位置相等,且i+1到j-1的位置也是回文串,那么 d p [ i ] [ j ] dp[i][j] dp[i][j]是回文串。
2、递归部分::遍历
对于当前位置的i,遍历i后面的所有元素,判断i,j是否为回文串,如果是,将level中存储这部分的值,因为i,j已经是回文串了,那么要对j+1开始判断是j+1,n是否为回文串,当j到达n时,把这部分的值存储到res中。返回上一层,每返回一层还要讲该层的信息删除pop掉。
代码实现
class Solution:
def partition(self, s: str) -> List[List[str]]:
n=len(s)
#DP部分,表示i-j部分的字符串是否为回文串的判断
dp=[[True]*n for _ in range(n)]
for i in range(n-1,-1,-1):
for j in range(i+1,n):
#i=j是回文串的条件是当前元素对应值相等且i+1:j-1也是回文串
dp[i][j]=dp[i+1][j-1] and s[i]==s[j]
#存储所有结果
res=[]
#递归过程中存储每个结果
level=[]
def dfs(i):
#终止条件:
if i==n:
res.append(level[:])
return
#对于每个i进行分析
for j in range(i,n):
#如果当前是回文串
if dp[i][j]:
level.append(s[i:j+1])
#对于j+1开始判断
dfs(j+1)
#删除前面的可能
level.pop()
dfs(0)
return res
分治+最优子结构
将一个复杂的问题分解成很多简单的子问题
关键点
题目描述
解题思路:
代码实现
Approach 1
每一个位置都由左边或者上边状态转移得到
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
#建立dp矩阵
dp=[[0] *(n+1) for i in range(m+1)]
for i in range(m+1):
dp[i][1]=1
for j in range(n+1):
dp[1][j]=1
#开始循环
for i in range(2,m+1):
for j in range(2,n+1):
dp[i][j]=dp[i-1][j]+dp[i][j-1]
return dp[m][n]
Approach 2
只需要维护一层的状态列表就可以,因为每个点的状态等于上面的状态加左边的状态,在累加每一行的时候,上面的状态自动地被类加上了。
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
dp=[1]*n
for i in range(1,m):
for j in range(1,n):
dp[j]+=dp[j-1]
return dp[-1]
题目描述
解题思路
和上一题思路差不多,只不过对于dp的状态更新要保证点不是障碍物即可
代码实现
#二维矩阵
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
#行和列
row=len(obstacleGrid)
col=len(obstacleGrid[0])
#建立dp矩阵
dp=[[0]*col for i in range(row)]
#初始化矩阵的值
for i in range(col):
if obstacleGrid[0][i]==0:
dp[0][i]=1
else:
break
for j in range(row):
if obstacleGrid[j][0]==0:
dp[j][0]=1
else:
break
#开始遍历
for i in range(1,row):
for j in range(1,col):
if obstacleGrid[i][j]!=1:
dp[i][j]=dp[i-1][j]+dp[i][j-1]
return dp[-1][-1]
为了让j可以在边界取到j-1,在每一行的左边加入一个1充当障碍物,所以最后也要返回dp[-2]
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
#行和列
row=len(obstacleGrid)
col=len(obstacleGrid[0])
#建立一维dp矩阵
dp=[1]+[0]*col
#开始遍历
for i in range(0,row):
for j in range(0,col):
if obstacleGrid[i][j]:
dp[j]=0
else:
dp[j]+=dp[j-1]
return dp[-2]
题目描述
解题思路
状态转移:
可以分为两种情况:
代码实现
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
#判断特殊条件
if not text1 or not text2:
return 0
#两个字符串的长度
m=len(text1)
n=len(text2)
#建立dp矩阵
dp=[[0]*(n+1) for i in range(m+1)]
for i in range(1,m+1):
for j in range(1,n+1):
#如果当前对比的字母是一样的,那么就是i-1和j-1的最长公共子序列+1
if text1[i-1]==text2[j-1]:
dp[i][j]=dp[i-1][j-1]+1
#如果当前对比的字母不相同,那么就是i-1,j或者i,j-1两者之间最长的公共子序列。
else:
dp[i][j]=max(dp[i-1][j],dp[i][j-1])
return dp[-1][-1]
重写,不多解释了
代码
class Solution:
def climbStairs(self, n: int) -> int:
dp=[1]+[2]*(n-1)
for i in range(2,n):
dp[i]=dp[i-2]+dp[i-1]
return dp[-1]
题目描述
解题思路
代码
class Solution:
def minimumTotal(self, triangle: List[List[int]]) -> int:
#建立一个三角形的动态矩阵
n=len(triangle)
dp=[[0]*n for i in range(n)]
#初始化,第一层的值就等于他本身
dp[0][0]=triangle[0][0]
#开始遍历
for i in range(1,n):
#三角形左斜边的值的累加只能来自于边
dp[i][0]=dp[i-1][0]+triangle[i][0]
for j in range(1,i):
#不在三角形边上的值可以来自于左斜上方或者右斜上
dp[i][j]=min(dp[i-1][j-1],dp[i-1][j])+triangle[i][j]
#三角形右斜边的值累加只能来自于边
dp[i][i]=dp[i-1][i-1]+triangle[i][i]
#返回最后一行的最小值
return min(dp[n-1])
题目描述
解题思路
这道题已经写了很多遍了,就不多说。
代码实现
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
#特殊情况,如果长度为1,输出本身
if len(nums)==1:
return nums[0]
#定义两个变量,一个存储当前的0,i的和,一个存储上一次的
cur=last=nums[0]
#定义一个储存最大值的变量
max_count=nums[0]
#遍历
for i in range(1,len(nums)):
#如果之前的加当前值比当前值还小:
if last+nums[i]<=nums[i]:
#储存的状态变为当前值
cur=nums[i]
else:
cur=last+nums[i]
#对比当前的最大子序和max的关系
if max_count<cur:
#更新
max_count=cur
#更新last
last=cur
#返回最大值
return max_count
题目描述
解题思路
因为列表中有负数的存在,如果对于每个位置的遍历只看它子序列中最大的乘积乘以它和它自身的话,是不正确的。对于当前位置为负数,乘以它子序列的最小值,即也为负的情况才是对于当前位置的最大状态。
所以在进行动态规划时,对于每个时刻,它的前面子序列的最大乘积就是在max(i-1),min(i-1)和nums[i]中选择最大的。那么计算min(i-1),就是在max(i-1),min(i-1),和nums[i]中选择最小的。
代码实现
class Solution:
def maxProduct(self, nums: List[int]) -> int:
if not nums:
return 0
#最大值的状态统计
max_pre=nums[0]
#最小值的状态统计
min_pre=nums[0]
#最后输出的值
res=nums[0]
#开始遍历
for i in range(1,len(nums)):
#如果i为正数,那要判断乘不乘当前的值
max_cur=max(max_pre*nums[i],min_pre*nums[i],nums[i])
min_cur=min(max_pre*nums[i],min_pre*nums[i],nums[i])
#更新一下最大值
res=max(max_cur,res)
#移动
max_pre=max_cur
min_pre=min_cur
return res
题目描述
解题思路
对于amount+1,遍历一遍coins中的硬币c,它的转移是从amount+1-c这个状态加1得到的。即对于凑11元,coins中有一个5元,3元,和1元,那么可行的方案就是:
代码实现
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
#建立dp
dp=[0]*(amount+1)
#当前最大值
#开始遍历:
for i in range(1,amount+1):
cur=amount+1
#遍历每个coins
for c in coins:
#如果c比当前target小的话
if c<=i:
cur=min(cur,dp[i-c])
dp[i]=cur+1 if cur <amount+1 else amount+1
return -1 if dp[-1]==amount+1 else dp[-1]
题目描述
解题思路
对于长度为0 :i的偷窃方案,只能选择从0 :i-2加上当前的值或者0:i-1的值。
在这里n-1和n-2的状态值是一样的。
代码实现
class Solution:
def rob(self, nums: List[int]) -> int:
if not nums:
return 0
#如果只有一个数
if len(nums)==1:
return nums[0]
#建立dp列表
dp=[0]*len(nums)
#对于只有第一间房,直接偷
dp[0]=nums[0]
#对于只有两间房,偷较大的
dp[1]=max(nums[0],nums[1])
#开始遍历
for i in range(2,len(nums)):
dp[i]=max(dp[i-2]+nums[i],dp[i-1])
return dp[len(nums)-1]
题目描述
解题思路
与第九题的状态转移是一样的概念,但是加入了环以后,第一个和最后一个连接到了一起,表示第一个和最后一个不可能同时偷盗,故将列表刨除第一个和最后一个分开讨论,取到最大值。
代码实现
class Solution:
def rob(self, nums: List[int]) -> int:
if len(nums)==1:
return nums[0]
def Dp(nums):
n=len(nums)
if not nums:
return 0
if n==1:
return nums[0]
dp=[0]*(n)
dp[0]=nums[0]
#初始化
dp[1]=max(nums[0],nums[1])
for i in range(2,n):
dp[i]=max(dp[i-2]+nums[i],dp[i-1])
return dp[n-1]
p1=Dp(nums[1:])
p2=Dp(nums[:-1])
return max(p1,p2)
题目描述
解题思路
使用dp列表去存储对于0到n每个时刻的利润最大的值。对于每个时刻i,有两种操作:
代码实现
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n=len(prices)
#特殊条件
if n==0:
return 0
#动态列表
dp=[0]*n
#定义一个存储最低价格的变量
min_price=prices[0]
#开始遍历
for i in range(n):
#找到对于每个时刻前面最低的价格
min_price=min(min_price,prices[i])
#对于每个时刻,要么不抛,要么卖掉,利润为当前价格减去之前的最低价格
dp[i]=max(dp[i-1],prices[i]-min_price)
return dp[-1]
题目描述
解题思路
现在是凌晨12:30,写不动了,大致的思路是,对于dp增加一个维度表示当前是否持有股票的状态。分情况讨论
代码实现
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n=len(prices)
dp=[[0]*2for _ in range(n)]
#如果第一天不持有股票,利润为0
dp[0][0]=0
#如果第一天就买入,利润为负
dp[0][1]=-prices[0]
for i in range(1,n):
#如果第i天没持有,可能上一时刻也没有,也可能i-1持有,今天卖掉了
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i])
#如果第i天持有,可能上一时刻持有,也可能当前买入了
dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i])
#最后时刻持有一定小于不持有
return dp[n-1][0]
题目描述
解题思路
对于在第i天的状态,大概有四个操作:
状态转移方程:
初始化的时候,在i=0天的时候,如果是买入一次和两次都是-prices[0],如果是买卖一次和买卖两次,对于第一天,手里的收益为0.
代码实现
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n=len(prices)
#初始化状态
#对于只买入一次和买入两次
buy1=-prices[0]
buy2=-prices[0]
#对于买入卖出一次和两次
sell1,sell2=0,0
#开始循环
for i in range(1,n):
#对于买入一次,可以选择什么都不做,和当前时刻买入
buy1=max(buy1,-prices[i])
#对于买卖一次的,可以选择当前继续持有,也可以选择卖出
sell1=max(sell1,buy1+prices[i])
#对于买入两次,可以选择当前不买入,也可以选择卖过一次后买入
buy2=max(buy2,sell1-prices[i])
#对于买卖两次,可以选择当前继续持有,也可以选择卖出
sell2=max(sell2,buy2+prices[i])
return sell2
题目描述
解题思路
三个状态:
取n-1时刻最大的值
代码实现
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n=len(prices)
#dp列表
dp=[[0]*3 for i in range(n)]
#状态初始化
dp[0][0]=-prices[0]
dp[0][1],dp[0][2]=0,0
for i in range(1,n):
#对于当前持有股票,可能是一直持有,也可能是在前一天且不在冷静期下当天买入
dp[i][0]=max(dp[i-1][0],dp[i-1][2]-prices[i])
#对于当前未持有,且在冷静期中,说明前一天持有,今天卖出了
dp[i][1]=dp[i-1][0]+prices[i]
#对于当天未持有,且不在冷静期,可能是前一天也未持有,也可能是在前一天处于冷静期
dp[i][2]=max(dp[i-1][2],dp[i-1][1])
return max(dp[n-1][1],dp[n-1][2])
题目描述
解题思路
将dp列表扩充到二维数组,第二个维度储存完成了第j次交易。
那么对于i时刻当前持有股票的buy来说:
对于i时刻当前未持有股票
代码实现
class Solution:
def maxProfit(self, k: int, prices: List[int]) -> int:
if not prices:
return 0
n=len(prices)
#将k的范围缩小
k=min(k,n//2)
#建立dp矩阵
buy=[[0]*(k+1) for _ in range(n)]
sell=[[0]*(k+1) for _ in range(n)]
#初始化buy
buy[0][0]=-prices[0]
for i in range(1,k+1):
buy[0][i]=-99999
sell[0][i]=-99999
#开始循环
for i in range(1,n):
#j=0,不需要考虑sell
buy[i][0]=max(buy[i-1][0],sell[i-1][0]-prices[i])
for j in range(1,k+1):
#对于当前持有,可能是上一个时刻就持有,也可能是上一时刻未持有当前时刻买入
buy[i][j]=max(buy[i-1][j],sell[i-1][j]-prices[i])
#对于当前未持有,可能是上一个时刻未持有,也可能是上一时刻持有,当前时刻卖出,完成了第j笔交易
sell[i][j]=max(sell[i-1][j],buy[i-1][j-1]+prices[i])
#最大利润一定是卖出的
return max(sell[n-1])
题目描述
解题思路
这题与买卖股票的最佳时机ii类似,但多了一个手续费,由于一次买入卖出只交一次手续费,那么选择在卖出时交手续费即可。两种状态:
代码实现
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
n=len(prices)
dp=[[0]*2 for _ in range(n)]
#初始化
#持有股票
dp[0][0]=-prices[0]
#未持有股票
dp[0][1]=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]-fee)
#最大利润一定是未持有的状态
return dp[n-1][1]
题目描述
解题思路
解释起来感觉好复杂,直接看题解吧,,,
代码实现
class Solution:
def longestValidParentheses(self, s: str) -> int:
n=len(s)
if n==0:
return 0
#建立dp
dp=[0]*n
#开始遍历
for i in range(n):
if i>0 and s[i]==")":
if s[i-1]=="(":
dp[i]=dp[i-2]+2
elif s[i-1]==")" and i-dp[i-1]-1>=0 and s[i-dp[i-1]-1]=="(":
dp[i]=dp[i-1]+2+dp[i-dp[i-1]-2]
return max(dp)
题目描述
解题思路
这道题的思路其实和之前的不同路径的思路是一样的,只不过在计算中计算的是每一个格子的具体值是多少,因为走法只能是往下走或往右走,所以对于一个不在上边界和左边界的格子,它要么从上面来,要么从左边来,选择两个前状态最小的路径值。对于边界条件的考虑,除了初始点为当前值以外,上边界和左边界的值只能来自左边和上面,对于这两条路径来说,它的值路径值是固定的。
代码实现
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
#定义一下矩阵的长宽
row=len(grid)
col=len(grid[0])
#建立dp
dp=[[0]*(col) for _ in range(row)]
dp[0][0]=grid[0][0]
#边际处理
#对于最上层的格子只能横着走
for j in range(1,col):
dp[0][j]=dp[0][j-1]+grid[0][j]
#对于j=0的格子只能往下走
for i in range(1,row):
dp[i][0]=dp[i-1][0]+grid[i][0]
#开始遍历
for i in range(1,row):
for j in range(1,col):
#每一个位置可能是从上面或者左边过来的
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j]
return dp[-1][-1]
题目描述
解题思路
虽然在dp中是困难题,但做过好多遍了,对于当前i和j相等的情况很好分析。当二者不相等时,就是插入、添加和删除三种情况
代码实现
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
#定义两个word的长度
m=len(word1)
n=len(word2)
#建立dp
dp=[[0]*(n+1) for _ in range(m+1)]
#考虑边界条件,如果word1为空,
for i in range(1,n+1):
dp[0][i]=i
#如果word2为空
for j in range(1,m+1):
dp[j][0]=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-1][j-1],
dp[i][j-1])+1
return dp[-1][-1]
题目描述
解题思路
对于状态的转移,可以有以下两种情况:
代码实现
class Solution:
def numDecodings(self, s: str) -> int:
n=len(s)
if n==0:
return 0
#建立dp状态列表
dp=[0]*(n+1)
#如果第一位是0,无法编译
if s[0]=="0":
return 0
#初始化
dp[0],dp[1]=1,1
#开始遍历
for i in range(1,n):
#只要s[i]不等于0,那么它自身可以编译
if s[i]!="0":
dp[i+1]=dp[i]
#计算i-1和i是否可以组成一个被编译的十位数
nums=10*(ord(s[i-1])-ord("0"))+ord(s[i])-ord("0")
if 10<=nums<=26:
dp[i+1]+=dp[i-1]
return dp[n]
题目描述
解题思路
对于 d p [ i ] [ j ] dp[i][j] dp[i][j]的定义是以(i,j)为右底角所能达到的最多的正方形的个数,初始化的限制条件为对于上层和左边的每一个点值为1,那么最多就是1,值为0,则为0。
对于那些在matrix中值为1的点,它的状态转移来自于(i-1,j-1)(i,j-1)(i-1,j)中最小的加1,因为要在三个方向上保证都是正方形,那么要取最小的,否则不满足正方形的边长的限制。由于返回值不一定由最后时刻的dp决定,所以要维护一个变量去在迭代中更新最小值。
代码实现
class Solution:
def maximalSquare(self, matrix: List[List[str]]) -> int:
row=len(matrix)
col=len(matrix[0])
if row==0 or col==0:
return 0
#建立dp
dp=[[0]*col for _ in range(row)]
max_side=0
#开始遍历
for i in range(row):
for j in range(col):
#如果当前为1
if matrix[i][j]=="1":
if i==0 or j==0:
dp[i][j]=1
else:
dp[i][j]=min(dp[i-1][j-1],dp[i-1][j],dp[i][j-1])+1
#更新max_side
max_side=max(max_side,dp[i][j])
return max_side*max_side
题目描述
解题思路
定义 d p [ i ] [ j ] dp[i][j] dp[i][j]为跳跃至i位置,所需要的跳跃数量,j表示上一个位置,True or False
代码中的diff代表上一次跳跃的的单元格的数量,那么当前只能跳跃diff+1,diff或者diff-1的单元格的数量。
代码实现
class Solution:
def canCross(self, stones: List[int]) -> bool:
n = len(stones)
dp = [[False] * n for _ in range(n)]
dp[0][1] = True
for i in range(1, n):
for j in range(i):
diff = stones[i] - stones[j]
# print(i, diff)
if diff < 0 or diff >= n or not dp[j][diff]: continue
dp[i][diff] = True
if diff - 1 >= 0: dp[i][diff - 1] = True
if diff + 1 < n: dp[i][diff + 1] = True
return any(dp[-1])
题目描述
解题思路
d p [ i ] [ j ] dp[i][j] dp[i][j]代表对于第i个位置分成j段后每种情况下每段的最大值的最小值。
那么状态的转移可以定义为
假设在0-i区间第j段分为k:i,那么转移的就是 d p [ k ] [ j − 1 ] dp[k][j-1] dp[k][j−1]和i-k区间所有元素加和的最大值,对于每种k 注意这里j即分割的段数不能超过m也不能超过i。
这里的代码应该是没错的,但超时了好烦,,,,
代码实现
class Solution:
def splitArray(self, nums: List[int], m: int) -> int:
#nums的长度
n=len(nums)
#建立dp,表示前i个数中可分为j段
dp=[[10**18]*(m+1) for _ in range(n+1)]
#初始化
dp[0][0]=0
#定义一个列表,每个位置是之前的nums的累加
sub=[0]
for num in nums:
sub.append(sub[-1]+num)
#开始循环
for i in range(1,n+1):
#j的范围不能超过m也不能超过i
for j in range(1,min(i,m)+1):
#所有可能的分割情况:0-k,k-i
for k in range(i):
#当前分段后的最大值应该是前k个数分为j-1段的和或者第j段的和中的最大值中的最小值
dp[i][j]=min(dp[i][j],max(dp[k][j-1],sub[i]-sub[k]))
return dp[n][m]
题目描述
解题思路
d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]中i表示出勤的长度,j表示A的个数,K表示末尾连续的L的个数。
根据题目要求,A不得超过1,K不得超过2
1、初始化状态的分析,如果长度为1,那么只能是A,P,L的其中一个。
2、当出现连续的k时,且k不超过2,那么把这些k删掉的状态转移是相同的
d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j ] [ k − 1 ] dp[i][j][k]=dp[i-1][j][k-1] dp[i][j][k]=dp[i−1][j][k−1]
3、如果末尾没有L,当没有A时,末尾的为P,那么可以删掉也不影响,删掉p,可能末尾是一个L,两个L或者0个L
d p [ i ] [ 0 ] [ 0 ] = d p [ i − 1 ] [ 0 ] dp[i][0][0]=dp[i-1][0] dp[i][0][0]=dp[i−1][0]
4、当末尾没有L,但却是A时,那么吧i位置的A删掉与情况3相同。
代码实现
class Solution:
def checkRecord(self, n: int) -> int:
mod=pow(10,9)+7
# 长度、'A'出现的次数、末尾连续'L'的次数
dp=[[[0]*3 for _ in range(2)]for _ in range(n+1)]
# base case:长度为1,那么是'A'、'L'、'P'中的一种
dp[1][1][0]=1 #A
dp[1][0][1]=1 #L
dp[1][0][0]=1 #P
for i in range(2,n+1):
# 若末尾是k个连续的L(即k>0)
for j in range(2):
for k in range(1,3):
dp[i][j][k]=dp[i-1][j][k-1]%mod
# 若末尾没有L(即k=0)
dp[i][0][0]=noA=sum(dp[i-1][0])%mod
dp[i][1][0]=noA+sum(dp[i-1][1])%mod
# 结果等于dp[n]的每种可能
return sum(sum(col) for col in dp[-1])%mod
题目描述
解题思路
使用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示对于字符串s,从i到j位置是否是回文串,由于每个子串都要计数,那么只要 d p [ i ] [ j ] = 1 dp[i][j]=1 dp[i][j]=1时就count+1,对于每个字符本身,肯定是回文串,所以count+1,对于不相连得位置,如果它们对应元素相等,且它们距离小于2说明它们是连续的,那么也为1,如果距离大于2,就要看i和j中间的字符是不是回文串了。对于每个为1得dp位置,count+1。最后返回count的值。
代码实现
class Solution:
def countSubstrings(self, s: str) -> int:
n=len(s)
#建立dp,表示i到j位置有多少个回文数
dp=[[0]*n for _ in range(n)]
count=0
#开始循环
for i in range(n-1,-1,-1):
#自身肯定是回文数
dp[i][i]=1
count+=1
for j in range(i+1,n):
#如果i和j对应的相同
if s[i]==s[j]:
#只有两个数的前提下
if j-i<=2:
dp[i][j]=1
else:
dp[i][j]=dp[i+1][j-1]
if dp[i][j]:
count+=1
return count
题目描述
解题思路
这题做了挺久的,第一次做滑动窗口的题,直接上解题思路吧,复习的时候再回来完善自己的思路。
代码实现
class Solution:
def minWindow(self, s: str, t: str) -> str:
#建立一个空字典储存t中的元素和需要的个数
need=collections.defaultdict(int)
#遍历t,将t存储进去
for char in t:
need[char]+=1
#统计滑动窗口需要找到的数量
count=len(t)
res=(0,float("inf"))
#滑动窗口的起始点
i=0
#开始遍历s
for j,c in enumerate(s):
#如果当前c出现在t中,将count-1
if need[c]>0:
count-=1
#如果没出现,把c添加到need,并-1,为了i增加时可以把包括的在抛出出去
need[c]-=1
#当count已经为0时,代表当前窗口已经包含了所有的t中元素,那么要将i扩大
if count==0:
#让i扩大,直到s[i]找到t中的元素
while True:
c=s[i]
#找到了
if need[c]==0:
break
#没找到的话,把原先那些-1的值加回来
need[c]+=1
#移动i
i+=1
#当前i,j为第一次找到的最小字串区间,对比更新
if j-i<res[1]-res[0]:
#更新
res=(i,j)
#将i移动一个,重新寻找一个滑动窗口
#先要把need中的属于t的元素加一个1
need[s[i]]+=1
#将count+1
count+=1
#移动i
i+=1
#返回时如果j大于了长度,那么说明没找到合适的窗口
return ""if res[1]>len(s) else s[res[0]:res[1]+1]
题目描述
解题思路
对于i,j区间内的所有可能的数字k,它可以和i,j组成一个戳爆气球后的硬币组合,对于每个位置k,它的硬币总数为val[i]*val[k]*val[j]再加上 d p [ i ] [ k ] + d p [ k ] [ j ] dp[i][k]+dp[k][j] dp[i][k]+dp[k][j]的值,找到最大的位置k,并存储在 d p [ i ] [ j ] dp[i][j] dp[i][j]中。
代码实现
class Solution:
def maxCoins(self, nums: List[int]) -> int:
#长度
n=len(nums)
#建立dp
dp=[[0]*(n+2) for _ in range(n+2)]
#nums的扩充
val=[1]+nums+[1]
#开始遍历
for i in range(n-1,-1,-1):
for j in range(i+2,n+2):
for k in range(i+1,j):
total=val[i]*val[k]*val[j]
total+=dp[i][k]+dp[k][j]
dp[i][j]=max(total,dp[i][j])
return dp[0][n+1]
做完了课程中所有的dp题,对于做出dp题最重要的三点: