目录
- 注意双向BFS怎么写
- 注意回溯的时间复杂度怎么计算
- 注意回溯的各种剪枝:mark、避免相同元素重复、
2021/4/16
1091. 二进制矩阵中的最短路径
BFS 注意:
- 遍历每一层完之后 ret+1;不是遍历每一个就ret+1
- 每一层遍历的时候,只要节点被遍历过,就修改为1;就不会再append这个节点了
- 不要设置 -1 直接设置1 不然超时,不知道为啥
class Solution:
def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:
if(grid==None):
return -1
if(grid[0][0]==1):
return -1
n=len(grid)
queue=[(0,0)]
grid[0][0]=1
ret=1
while(queue):
queue_len=len(queue) # 每一层的元素数量
while(queue_len>0):
# 取首元素
cur=queue[0]
if(cur[0]==n-1 and cur[1]==n-1):
return ret
for direct in [(0,1),(0,-1),(1,-1),(1,0),(1,1),(-1,-1),(-1,0),(-1,1)]:
id1,id2=cur[0]+direct[0],cur[1]+direct[1]
# 判断是否出界
if(id1<0 or id1>=n or id2<0 or id2>=n):
continue
# 是否可达
if(grid[id1][id2]==0):
queue.append((id1,id2))
grid[id1][id2]=1
# # 到达右下角 这样写忽略一种情况 [[0]]
# if(id1==n-1 and id2==n-1):
# return ret+1
del queue[0]
queue_len-=1
ret+=1
return -1
127. 单词接龙
单向bfs 从头到尾 太慢了 超出时间限制
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
if(endWord not in wordList):
return 0
ret=1
queue=[beginWord]
mark=set()
mark.add(beginWord)# 已经可达的单词
wordList=set(wordList)
while(queue):
# 对这一层的所有元素 遍历
queue_len=len(queue)
while(queue_len>0):
s_cur=queue[0]
# 搜寻所有
for s in (wordList-mark):
if(self.isTrans(s_cur,s)):
if(s==endWord):
return ret+1
queue.append(s)
mark.add(s)
del queue[0]
queue_len-=1
ret+=1
return 0
def isTrans(self,s1,s2):
# 判断两个不同的字符能否 通过一个字符转化
if(len(s1)!=len(s2)):
return False
s1_list=list(s1)
s2_list=list(s2)
if(s1_list==s2_list):
return False
for i in range(len(s1)):
# 逐个去字符 判断是否相等
ss1=s1_list.pop(i)
ss2= s2_list.pop(i)
# bug:replace默认替换等于该字符的第一个位置
#if(s1.replace(s1[i],'', 1)==s2.replace(s2[i],'',1)):
if(s1_list==s2_list):
return True
s1_list.insert(i,ss1)
s2_list.insert(i,ss2)
return False
别人的代码 单向bfs 可以成功 关键点:
- ①mark wordlist 都用set 查找复杂度变为1
- ②直接对于当前出队列的元素 修改每个位置 上 每个元素 看修改之后在不在 wordlist里;而不是遍历wordlist 再和当前元素判断
② 对于一个元素来说;这个代码时间复杂度 1×26×len(cur_word);自己的是 len(word_list)×len(cur_word);很明显当word_list很大的时候,自己这种写法就慢了,导致超出时间限制。
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
st=set(wordList)
if endWord not in st:
return 0
m=len(beginWord)
queue=collections.deque()
queue.append(beginWord)
visited=set()
visited.add(beginWord)
step=0
while queue:
step+=1
for k in range(len(queue)):
cur=queue.popleft()
if cur==endWord:
return step
for i in range(m):
for j in range(26):
tmp=cur[:i]+chr(97+j)+cur[i+1:]
if tmp not in visited and tmp in st:
queue.append(tmp)
visited.add(tmp)
return 0
官方:双向BFS,主要是构造两个队列,每次以短的遍历;当一阶可达word in 右队列时,终止
class Solution:
import collections
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
wordList=set(wordList)
if endWord not in wordList:
return 0
m=len(beginWord)
queueLeft=collections.deque()
queueRight=collections.deque()
queueLeft.append(beginWord) # 短队列
queueRight.append(endWord) # 长队列
visited=set([beginWord,endWord]) # 共用visited
step=0
while queueLeft:
step+=1
# 对每一层的元素遍历
for k in range(len(queueLeft)):
cur=queueLeft.popleft()
# 判断当前元素所能可达的元素 并append到左队列
for i in range(len(cur)):
for j in range(26):
tmp=cur[:i]+chr(97+j)+cur[i+1:] # 构造new word
if(tmp in queueRight):
return step+1
if(tmp not in visited and tmp in wordList):
queueLeft.append(tmp)
visited.add(tmp)
#更新左队列 右队列 短的为左
if(len(queueRight)<len(queueLeft)):
queueLeft,queueRight=queueRight,queueLeft
return 0
2021/4/24
695. 岛屿的最大面积
DFS
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
self.row=len(grid)
self.col=len(grid[0])
maxArea=0
for i in range(self.row):
for j in range(self.col):
maxArea=max(maxArea,self.dfs(grid,i,j))
return maxArea
# 从 [i,j] 点出发的最大area
def dfs(self,grid,i,j):
# 超出当前grid
if(i>=self.row or i<0 or j>=self.col or j<0):
return 0
if(grid[i][j]==0):
return 0
else:
area=1
grid[i][j]=0 #标记为visited
for direct in ([1,0],[-1,0],[0,1],[0,-1]):
area+=self.dfs(grid,i+direct[0],j+direct[1])
return area
DFS+栈
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
row=len(grid)
col=len(grid[0])
maxArea=0
for i in range(row):
for j in range(col):
if(grid[i][j]==0):
continue
# 只有遇到1 再用栈
stack=[[i,j]]
grid[i][j]=0
area=0
while(stack):
area+=1
cur=stack.pop() # 出栈
for direct in ([1,0],[-1,0],[0,1],[0,-1]):
left_idx=cur[0]+direct[0]
right_idx=cur[1]+direct[1]
if(left_idx>=0 and left_idx<row and right_idx>=0 and right_idx<col and grid[left_idx][right_idx]==1):
stack.append([left_idx,right_idx])
grid[left_idx][right_idx]=0 # 注意一定入栈之后就mark,如果出栈再mark,结果可能会增大,因为可能被同一个栈的元素visit多次
maxArea=max(maxArea,area)
return maxArea
2021/4/25
200. 岛屿数量
按照area面积来判断的,官方给的是将遍历过的1标记为2.
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
num=0
for i in range(len(grid)):
for j in range(len(grid[0])):
if(self.dfs(grid,i,j)>0):
num+=1
return num
# 返回[i,j] 所在岛屿面积
def dfs(self,grid,i,j):
row=len(grid)
col=len(grid[0])
if(i<0 or i>=row or j<0 or j>=col or grid[i][j]=='0'):
return 0
else:
area=1
grid[i][j]='0' # mark 1 为 0
for direct in ([0,1],[0,-1],[-1,0],[1,0]):
area+=self.dfs(grid,i+direct[0],j+direct[1])
return area
547. 省份数量
从每个城市出发,去mark所有相连的城市,标记为2:
- 注意当 dp[i][j]为1时 且 i!=j时,就将dp[j][j] 也mark
- 最终看对角线元素 有几个1即可
class Solution:
def findCircleNum(self, isConnected: List[List[int]]) -> int:
n=len(isConnected)
num=0
for i in range(n):
if(isConnected[i][i]==1):
num+=1
self.dfs(isConnected,i)
return num
# 从 城市i出发 mark所有相连的
def dfs(self,grid,i):
n=len(grid)
for j in range(n):
if(grid[i][j]==1 and j!=i):
grid[i][j]=2
grid[j][j]=2
self.dfs(grid,j)
使用一个set来标记所有visit的城市 能更快一点
class Solution:
def findCircleNum(self, isConnected: List[List[int]]) -> int:
n=len(isConnected)
num=0
visit=set()
# 从城市i出发 去mark所有相连的
def dfs(i):
for j in range(n):
if(isConnected[i][j]==1 and j not in visit):
visit.add(j)
dfs(j)
for i in range(n):
if(isConnected[i][i]==1 and i not in visit):
visit.add(i)
dfs(i)
num+=1
return num
130. 被围绕的区域
自己写的意思对了,就是太繁琐了。。
class Solution:
def solve(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
self.row=len(board)
self.col=len(board[0])
if(self.row<=1 or self.col<=1):
return 0
# 先遍历 四个边 mark为 '1'
for i in range(self.col):
self.dfs(board,0,i,'O','1')
for i in range(self.col):
self.dfs(board,self.row-1,i,'O','1')
for i in range(self.row):
self.dfs(board,i,0,'O','1')
for i in range(self.row):
self.dfs(board,i,self.col-1,'O','1')
# print(board)
# 在遍历所有 将 '0' mark为'X'
for i in range(self.row):
for j in range(self.col):
self.dfs(board,i,j,'O','X')
# print(board)
# 最后将'1' mark为 '0'
for i in range(self.row):
for j in range(self.col):
self.dfs(board,i,j,'1','O')
# print(board)
def dfs(self,grid,i,j,orgin,new):
if(i>=0 and i<self.row and j>=0 and j<self.col and grid[i][j]==orgin):
grid[i][j]=new
for direct in ([0,1],[0,-1],[-1,0],[1,0]):
self.dfs(grid,i+direct[0],j+direct[1],orgin,new)
看了看官方的,其实最主要的区别是一个,自己写冗余了:
- 1,先遍历四个边,标记O为T
- 2,遍历整个grid
- 如果是T,转为O
- 如果是O,说明不和边相连,那么它肯定就会被X替换 (自己又额外判断了这个)
2021/4/30
417. 太平洋大西洋水流问题
逆流而上的思路,从四个边往上拓展
自己刚开始这个内存太大。。 用两个set存储分别的点
class Solution:
def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
self.row=len(heights)
self.col=len(heights[0])
left=set()
right=set()
# 从太平洋的初始边开始遍历
for j in range(self.col):
left.add((0,j))
self.dfs(heights,0,j,left)
for i in range(self.row):
left.add((i,0))
self.dfs(heights,i,0,left)
# 从大西洋的初始边开始遍历
for j in range(self.col):
left.add([self.row-1,j])
self.dfs(heights,self.row-1,j,right)
for i in range(self.row):
left.add([i,self.col-1])
self.dfs(heights,i,self.col-1,right)
return list(left&right)
# 从[i,j]开始流动
def dfs(self,grid,i,j,curSet):
for direct in ([0,1],[0,-1],[1,0],[-1,0]):
idx1=i+direct[0]
idx2=j+direct[1]
if(idx1<0 or idx1>=self.row or idx2<0 or idx2>=self.col):
continue
if(grid[idx1][idx2]>=grid[i][j]):
curSet.add((idx1,idx2))
self.dfs(grid,idx1,idx2,curSet)
看了一个DFS的题解,用两个数组 0/1 来标记能否到达 left和right
class Solution:
def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
self.row=len(heights)
self.col=len(heights[0])
left=[[0]*self.col for _ in range(self.row)]
right=[[0]*self.col for _ in range(self.row)]
# 从太平洋的初始边开始遍历
for j in range(self.col):
left[0][j]=1
self.dfs(heights,0,j,left)
for i in range(self.row):
left[i][0]=1
self.dfs(heights,i,0,left)
# 从大西洋的初始边开始遍历
for j in range(self.col):
right[self.row-1][j]=1
self.dfs(heights,self.row-1,j,right)
for i in range(self.row):
right[i][self.col-1]=1
self.dfs(heights,i,self.col-1,right)
return [[i,j] for i in range(self.row) for j in range(self.col) if left[i][j] and right[i][j]]
# 从[i,j]开始流动
def dfs(self,grid,i,j,visit):
for direct in ([0,1],[0,-1],[1,0],[-1,0]):
idx1=i+direct[0]
idx2=j+direct[1]
if(idx1<0 or idx1>=self.row or idx2<0 or idx2>=self.col):
continue
if(grid[idx1][idx2]>=grid[i][j] and visit[idx1][idx2]==0):
visit[idx1][idx2]=1
self.dfs(grid,idx1,idx2,visit)
17. 电话号码的字母组合
回溯法 2-3-4
- 顺序遍历,按了这个元素 就不会用到了
- combine是一个组成的字符串,可能是 ‘’,a,ad,adg,只有当长度=num数量时,才会加入到result中
- i是表明当前按的元素的idx
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
nums=list(digits)
if(len(nums)==0):
return []
numDict={
'2':['a','b','c'],
'3':['d','e','f'],
'4':['g','h','i'],
'5':['j','k','l'],
'6':['m','n','o'],
'7':['p','q','r','s'],
'8':['t','u','v'],
'9':['w','x','y','z']
}
result=[]
# 按了这个元素 就不会用到了
# combine是一个组成的字符串 i 表示从idx==i的数字开始
def backTrack(combine,i):
if(i==len(digits)):
result.append(combine)
else:
for letter in numDict[nums[i]]:
backTrack(combine+letter,i+1)
backTrack('',0)
return result
2021/5/3
93. 复原 IP 地址
自已写的第一版 还可以剪枝优化
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
if(len(s)<4 or len(s)>12):
return []
result=[]
def trackBack(cur,i):
if(i==len(s)):
result.append(cur[:-1])
else:
for j in range(1,4):
if(i+j<=len(s) and int(s[i:i+j])>=0 and int(s[i:i+j])<=255):
trackBack(cur+str(int(s[i:i+j]))+'.',i+j)
trackBack('',0)
ips=[]
for ip in result:
ipList=ip.split('.')
if(len(ipList)==4):
curLen=0
for ss in ipList:
curLen+=len(ss)
if(curLen==len(s)):
ips.append(ip)
return ips
简化了一下代码 但仍然很慢。。
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
if(len(s)<4 or len(s)>12):
return []
result=[]
def trackBack(cur,i):
if(i==len(s) and len(cur.split('.'))==5 and len(cur)==len(s)+4):
result.append(cur[:-1])
else:
for j in range(1,4):
if(i+j<=len(s) and len(cur.split('.'))<5): # 之前写的<4错误,因为.后面的''会占一个元素
new=int(s[i:i+j])
# 保证数字0-255
if(new>0 and new<=255):
trackBack(cur+str(new)+'.',i+j)
# 避免 '00' 出现
if(new==0 and j==1):
trackBack(cur+str(new)+'.',i+j)
trackBack('',0)
return result
79. 单词搜索
79. 单词搜索 这道题着实卡了很久
一直写的是注释里的函数,发现错误的原因竟然是要额外判断board[i][j]==word[0],不让的话,第一个=word[0]的元素就无法回溯了。。。。。。。!!!!
- 一直想写一个函数就完事了,但是考虑第一个上述的元素的回溯!!
- 注意这里:下一个元素不是word路径上,不代表当前元素不在正确路径上
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
m=len(board)
n=len(board[0])
if(len(word)>m*n):
return False
mark=[[0]*n for _ in range(m)]
# 从[i,j]判断是不是==word[idx],且位于word路径上
def backTrack(i,j,idx,mark):
if(board[i][j]!=word[idx]):
return False
if(idx==len(word)-1):
return True
#print(i,j,idx)
# [i,j]等于idx对应char,就mark[i,j]
mark[i][j]=1
for direct in [(0,1),(0,-1),(1,0),(-1,0)]:
i1=i+direct[0]
j1=j+direct[1]
# 未超界
if(i1>=0 and i1<m and j1>=0 and j1<n and mark[i1][j1]==0):
mark[i1][j1]=1
# 如果下一个元素是word路径上的
if(backTrack(i1,j1,idx+1,mark)):
return True
# 如果下一个元素不是word路径上的,那么回溯,即mark下一个元素为0
else:
mark[i1][j1]=0
# 注意这里!!:下一个元素不是word路径上,不代表当前元素不在正确路径上
# return False
for i in range(m):
for j in range(n):
if(backTrack(i,j,0,mark)):
return True
else:
mark[i][j]=0
return False
# class Solution:
# def exist(self, board: List[List[str]], word: str) -> bool:
# m=len(board)
# n=len(board[0])
# if(len(word)>m*n):
# return False
# mark=[[0]*n for _ in range(m)]
# # 从[i,j]判断是不是==word[idx],且位于word路径上
# def backTrack(i,j,idx,mark):
# if(board[i][j]!=word[idx]):
# return False
# if(idx==len(word)-1):
# return True
# print(i,j,idx)
# # [i,j]等于idx对应char,就mark[i,j]
# mark[i][j]=1
# for direct in [(0,1),(0,-1),(1,0),(-1,0)]:
# i1=i+direct[0]
# j1=j+direct[1]
# # 未超界
# if(i1>=0 and i1<m and j1>=0 and j1<n and mark[i1][j1]==0):
# mark[i1][j1]=1
# # 如果下一个元素是word路径上的
# if(backTrack(i1,j1,idx+1,mark)):
# return True
# # 如果下一个元素不是word路径上的,那么回溯,即mark下一个元素为0
# else:
# mark[i1][j1]=0
# # 注意这里!!:下一个元素不是word路径上,不代表当前元素不在正确路径上
# # return False
# for i in range(m):
# for j in range(n):
# if(backTrack(i,j,0,mark)):
# return True
# return False
257. 二叉树的所有路径
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def binaryTreePaths(self, root: TreeNode) -> List[str]:
if(root==None):
return []
result=[]
def backTrack(curPath,curNode):
# 遇到叶子节点了
if(curNode.left==None and curNode.right==None):
curPath=curPath+'->'+str(curNode.val)
result.append(curPath[2:])
if(curNode.left):
backTrack(curPath+'->'+str(curNode.val),curNode.left)
if(curNode.right):
backTrack(curPath+'->'+str(curNode.val),curNode.right)
backTrack('',root)
return result
46. 全排列
- ①自己写的,有点固定思路了,只会写这种str,split,join这种办法。。
- ②修改为list形式的,注意用cur+[]传参,别直接在cur上append
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
if(len(nums)<1):
return []
result=[]
mark=[0]*len(nums)
# 当前组合str;当前用了多少Num
def combine(cur,numCount,mark):
if(numCount==len(nums)):
result.append([int(tmp) for tmp in cur[1:].split('.')])
else:
# 从当前未mark的nums中选一个 遍历所有可能
for idx in range(len(mark)):
if(mark[idx]==0):
mark[idx]=1
combine(cur+'.'+str(nums[idx]),numCount+1,mark)
# 遍历一条depth==numlen的路径之后 回溯为0
mark[idx]=0
combine('',0,mark)
return result
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
if(len(nums)<1):
return []
result=[]
mark=[0]*len(nums)
def combine(cur,idx):
if(idx==len(nums)):
result.append(cur)
for i in range(len(mark)):
if(mark[i]==0):
mark[i]=1
#cur.append(nums[i]) #注意别用这个,就直接修改掉cur的值了。。。
combine(cur+[nums[i]],idx+1)
mark[i]=0
combine([],0)
return result
自己写的多用了标记数组,官方去掉了标记数组,将题目给定的 nn 个数的数组 \textit{nums}nums 划分成左右两个部分,左边的表示已经填过的数,右边表示待填的数,我们在回溯的时候只要动态维护这个数组即可。
- 注意nums[:] 是返回局部值,nums返回原对象
- 嵌套函数修改不了外部函数的参数
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
if(len(nums)<1):
return []
result=[]
# diff是区分[用了的|没用的] nums[diff]是未用过的首元素
def combine(diff=0):
# 未用过元素的idx超出了len-1,说明用完所有元素了
if(diff==len(nums)):
#result.append(nums[]) # 注意这个!!!nums[:] 是返回值,nums返回原值
result.append(nums[:])
# 所有下一个可用的集合中选 遍历
for i in range(diff,len(nums)):
nums[diff],nums[i]=nums[i],nums[diff]
combine(diff+1)
# 当一条path完成之后 回溯
nums[i],nums[diff]=nums[diff],nums[i]
combine()
return result
2021/5/4
47. 全排列 II
个人真的不理解这道题。。
- 关键是如何保证idx位置重复的num不会填进去;然后在添加一个元素时,判断这个元素是否等于前一个元素,如果等于,并且前一个元素还未访问,那么就跳过这个元素。但这个mark[i-1]==0着实不理解,如果前一个未访问,怎么跳过呢??
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
if(len(nums)<1):
return []
result=[]
mark=[0]*len(nums)
def backTrack(cur,idx,mark):
if(idx==len(nums)):
result.append(cur)
for i in range(len(nums)):
# # 使用nums[i]的条件1:必须没有mark过
# if(mark[i]==0):
# # 条件2:相同的元素没有被mark过 (因为本题中对nums进行了排序 所以和上一个比即可)
# if(i==0 or (i>0 and nums[i]!=nums[i-1] and mark[i-1]==1)):
# mark[i]=1
# backTrack(cur+[nums[i]],idx+1,mark)
# # 1 回溯当前元素用没用过
# mark[i]=0
if(mark[i]==1):
continue
# Q:为什么要加上mark[i-1]==0!!!??
# A:为了保证当[x1,x2,x3]是逐个填入的,即x2必须在x1填入之后填入
if(i>0 and nums[i]==nums[i-1] and mark[i-1]==0):
continue
mark[i]=1
backTrack(cur+[nums[i]],idx+1,mark)
mark[i]=0
nums.sort()
backTrack([],0,mark)
return result
77. 组合
自己先写的一版,关键是避免重复即可,但是时间较慢。
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
if(k>n or n<1):
return []
result=[]
# 从 1,2,..,n 中选k个数
mark=[0]*n
# idx是k的index
def combine(cur,idx,mark):
if(idx==k):
result.append(cur)
# 在未选择的数中选择 且为了避免重复 保证新增的大于上一个(上一个即目前最大的)
for i in range(n):
if(mark[i]==0):
if(len(cur)==0 or (i>0 and i+1>cur[-1])):
mark[i]=1
combine(cur+[i+1],idx+1,mark)
mark[i]=0
combine([],0,mark)
return result
不需要额外mark数组
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
if(k>n or n<1):
return []
result=[]
# 保证填入的比上一个填入的大即可
def combine(cur,idx):
if(idx==k):
result.append(cur)
# 1 cur还是空 随便填
if(idx==0):
for i in range(n):
combine(cur+[i+1],idx+1)
# 2 cur不是空 大于最后一个即可
else:
for i in range(cur[-1],n):
combine(cur+[i+1],idx+1)
combine([],0)
return result
39. 组合总和
- 关键避免重复组合即可
- 不知道怎么剪枝时,可以画树图来观察,发现只要进行排序,保证每次新的Num>=当前最大的就ok了
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
if(len(candidates)<1 or target<1):
return []
result=[]
def dfs(cur,curSum):
if(curSum==target):
result.append(cur)
# 避免重复组合 sort 候选List,保证新的>=上一个即可
for num in candidates:
if(curSum==0 or(curSum>0 and num>=cur[-1]) and num<=target and num+curSum<=target):
dfs(cur+[num],curSum+num)
candidates.sort()
dfs([],0)
return result
40. 组合总和 II
关键是重复有两种情况:
- 1,和39题一样,[1,2,3] [2,3,1]\,[3,2,1] 这种重复;候选list排序,保证新的>=cur[-1]即可
- 2,[1,1,2,5] 中[1,2]\,[1,2] 这种重复,和47题一样,这会理解那个nums[i] ==nums[i-1] 时,必须确保nums[\i-1]为mark的理由了。。
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
if(len(candidates)<1 or target<1):
return []
result=[]
mark=[0]*len(candidates)
def backTrack(cur,curSum,mark):
if(curSum==target):
result.append(cur)
for i in range(0,len(candidates)):
# 保证新增的未mark
if(mark[i]==1):
continue
# 保证新增的大于等于当前最大值 (避免重复1)
if(curSum>0 and candidates[i]<cur[-1]):
continue
# 保证curSum时nums[i]不被重复搜索(避免重复2);
# 即nums[i]==nums[i-1]时 必须保证nums[i-1]被mark
if(i>0 and candidates[i]==candidates[i-1] and mark[i-1]==0):
continue
# 保证新增之后sum <= target
if(curSum+candidates[i]>target):
continue
mark[i]=1
backTrack(cur+[candidates[i]],curSum+candidates[i],mark)
mark[i]=0 # 回溯
candidates.sort()
backTrack([],0,mark)
return result
但是自己的有点费时间,又看了了下某题解,可以不需要mark数组:
- 但自己还是没写出来重复那里的剪枝:
- 其实目的都是即nums[i]==nums[i-1]时 必须保证nums[i-1]被append进去了
- 因为一次只append一个元素 所以如果在i>begin的情况下nums[i]==nums[i-1]了,那么直接跳过;
- 自己写的虽然逻辑上好像正确,但是有种情况会出错:出现多个[2,2,2,2,2,]重复值时,会append多个2进去,但实际append一个2才正确
正确的是:
if(i>begin and candidates[i]==candidates[i-1]):
自己写的是:
if(i>begin and candidates[i]==candidates[i-1]):
if(curSum>0 and cur[-1]!=candidates[i-1]):
continue
elif(curSum==0):
continue
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
if(len(candidates)<1 or target<1):
return []
result=[]
# 由于从小到大排 所以不需要mark数组 只需要一个begin下标即可
#mark=[0]*len(candidates)
def backTrack(cur,curSum,begin):
if(curSum==target):
result.append(cur)
for i in range(begin,len(candidates)):
# 保证curSum时nums[i]不被重复搜索;
# 即nums[i]==nums[i-1]时 必须保证nums[i-1]被append进去了
# 因为一次只append一个元素 所以如果在i>begin的情况下nums[i]==nums[i-1]了,那么直接跳过
if(i>begin and candidates[i]==candidates[i-1]):
continue
# if(curSum>0 and cur[-1]!=candidates[i-1]):
# continue
# elif(curSum==0):
# continue
# 保证新增之后sum <= target
if(curSum+candidates[i]>target):
continue
backTrack(cur+[candidates[i]],curSum+candidates[i],i+1)
candidates.sort()
backTrack([],0,0)
return result
216. 组合总和 III
感觉还是40难一点
class Solution:
def combinationSum3(self, k: int, n: int) -> List[List[int]]:
if(k<1 or n<1):
return []
result=[]
# begin是未选择的数的第一个
def backTrack(cur,curSum,begin):
if(curSum==n and len(cur)==k):
result.append(cur)
# 数量保证<=k 正整数<=9
if(len(cur)>k or begin>9):
return
for num in range(begin,10):
if(num+curSum<=n):
backTrack(cur+[num],curSum+num,num+1)
backTrack([],0,1)
return result
78. 子集
第一版,就是最简单的从子集长度1,2,3…len(nums)一直做
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
if(len(nums)<1):
return [[]]
result=[[]]
# 子集的长度 1,2,...len(nums)
# begin是未选择的num的第一个index
def dfs(cur,length,begin):
if(len(cur)==length):
result.append(cur)
# 遍历完nums了
if(begin>=len(nums)):
return
for idx in range(begin,len(nums)):
dfs(cur+[nums[idx]],length,idx+1)
for k in range(1,len(nums)+1):
dfs([],k,0)
return result
但是可以看出性质,假如长度为5,那么长度=1和长度=4的子集是互补的;但是注意一种情况:nums长度为6,长度=3的子集不需要append互补子集,因为其互补子集还是自己长度=3的子集
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
if(len(nums)<1):
return [[]]
result=[]
# 子集的长度 1,2,...len(nums)
# begin是未选择的num的第一个index
def dfs(cur,length,begin):
if(len(cur)==length):
result.append(cur) # {长度=length}的子集
# 长度偶数时,一半length时不需要考虑互补子集
if(len(nums)%2==0 and length==len(nums)//2):
return
result.append(list(set(nums)-set(cur))) # 互补的{长度=len(nums)-length}的子集
# 遍历完nums了
if(begin>=len(nums)):
return
for idx in range(begin,len(nums)):
dfs(cur+[nums[idx]],length,idx+1)
#nums.sort()
for k in range(0,len(nums)//2+1):
dfs([],k,0)
return result
90. 子集 II
有重复子集的情况,需要对nums排序,然后排除重复的情况(剪枝)
class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
result=[]
# begin为未选择的num的第一个idx
def trackBack(cur,length,begin):
if(len(cur)==length):
result.append(cur)
# # 互补子集
# if(len(nums)%2==0 and length==len(nums)/2):
# return
# result.append(list(set(nums)-set(cur)))
# 遍历完nums了
if(begin>=len(nums)):
return
# 保证不含重复的子集:对nums排序 当nums[i]==nums[i-1]时 保证nums[i-1]必须被append进去
for idx in range(begin,len(nums)):
# 因为一次append一个元素,肯定要append第一个相等的元素
if(idx>begin and nums[idx]==nums[idx-1]):
continue
trackBack(cur+[nums[idx]],length,idx+1)
nums.sort()
# 5:0,1,2
for k in range(0,len(nums)+1):
trackBack([],k,0)
return result
2021/5/6
131. 分割回文串
自己的答案太耗时了
class Solution:
def partition(self, s: str) -> List[List[str]]:
if(len(s)<1):
return []
result=[]
# 判断s1是否为回文
def isReverseStr(s1):
idx1=0
idx2=len(s1)-1
while(idx1<idx2):
if(s1[idx1]!=s1[idx2]):
return False
else:
idx1+=1
idx2-=1
return True
# begin是未分割的字符中第一个idx
def dfs(cur,begin):
# 分割完s了
if(begin==len(s)):
result.append(cur)
# 进行分割 从可能长度1,2,...len-begin中选择
for i in range(1,len(s)-begin+1):
#print(isReverseStr(s[begin:begin+i]))
if(isReverseStr(s[begin:begin+i])):
# print(s[begin:begin+i])
dfs(cur+[s[begin:begin+i]],begin+i)
dfs([],0)
return result
是因为双指针判断回文字符会出现多次冗余,先dp找出所有的回文字符,再dfs,关键有:
- 怎么写dp,dp[i][j]==(s[i]==\s[j]) and dp[i+1][j-1],默认初始化都True,所以不用担心s[4][5]:①s[4]和s[5]相等判断即可 ②s[5][4]默认True
- dfs的时候注意下标,dp[i][j]表示的是s[i:j+1]是回文
class Solution:
def partition(self, s: str) -> List[List[str]]:
if(len(s)<1):
return []
result=[]
# 预处理 dp[i][j]表示 s[i:j+1] 是否为回文串
dp=[[True]*len(s) for _ in range(len(s))]
# 只有右上三角有意义 dp[i][j] 只和dp[i+1][j-1]有关 倒着遍历
for i in range(len(s)-1,-1,-1):
for j in range(i+1,len(s)):
dp[i][j]=(s[i]==s[j]) and dp[i+1][j-1]
# begin是未分割的字符中第一个idx
def dfs(cur,begin):
# 分割完s了
if(begin==len(s)):
result.append(cur)
# dp[begin][begin]....dp[begin][len(s)-1]
for i in range(begin,len(s)):
if(dp[begin][i]):
dfs(cur+[s[begin:i+1]],i+1)
dfs([],0)
2021/5/7
37. 解数独
这题自以为没问题,但是写出来之后各种小问题。。
- 注意一旦flag=True 就都要return
- 什么时候需要 idxSet.append((i,j)) ,当choose中进行dfs之后没有一条路能让flag变为true,就需要append回溯;这几条路之间不需要append
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
from collections import defaultdict
rowHash=defaultdict(set)
colHash=defaultdict(set)
blockHash=defaultdict(set)
allNum={'1','2','3','4','5','6','7','8','9'}
numCount=0
idxSet=[]
def getBlockIdx(i,j):
return int(i/3)*3+int(j/3)
# 初始化3类哈希表
for i in range(9):
for j in range(9):
if(board[i][j]!='.'):
numCount+=1
rowHash[i].add(board[i][j])
colHash[j].add(board[i][j])
blockHash[getBlockIdx(i,j)].add(board[i][j])
else:
idxSet.append((i,j))
# # dfs 当前idx位置可选的值集合是choose
# def dfs(idxSet,board):
# print(len(idxSet))
# if(len(idxSet)==0):
# return True
# i,j=idxSet.pop()
# curChoose=list(allNum-(rowHash[i]|colHash[j]|blockHash[getBlockIdx(i,j)]))[:]
# for num in curChoose:
# board[i][j]=num
# rowHash[i].add(num)
# colHash[j].add(num)
# blockHash[getBlockIdx(i,j)].add(num)
# if(dfs(idxSet,board)):
# return True
# else:
# # 回溯
# board[i][j]='.'
# rowHash[i].remove(num)
# colHash[j].remove(num)
# blockHash[getBlockIdx(i,j)].remove(num)
# idxSet.append((i,j))
# return False
# dfs
self.flag=False
def dfs(idxSet,board):
# if(len(idxSet)<10):
# print(len(idxSet))
if(len(idxSet)==0):
self.flag=True
print(board)
return
i,j=idxSet.pop()
curChoose=list(allNum-(rowHash[i]|colHash[j]|blockHash[getBlockIdx(i,j)]))[:]
# print(curChoose)
curFlag=False
for num in curChoose:
board[i][j]=num
rowHash[i].add(num)
colHash[j].add(num)
blockHash[getBlockIdx(i,j)].add(num)
# dfs
dfs(idxSet,board)
# 回溯
if(not self.flag):
board[i][j]='.'
rowHash[i].remove(num)
colHash[j].remove(num)
blockHash[getBlockIdx(i,j)].remove(num)
#idxSet.append((i,j))
else:
curFlag=True
return # 必须加这个return 不然board正确的会被修改
if(not curFlag):
idxSet.append((i,j))
dfs(idxSet,board)
稍微精简一下
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
from collections import defaultdict
rowHash=defaultdict(set)
colHash=defaultdict(set)
blockHash=defaultdict(set)
allNum={'1','2','3','4','5','6','7','8','9'}
numCount=0
idxSet=[]
def getBlockIdx(i,j):
return int(i/3)*3+int(j/3)
# 初始化3类哈希表
for i in range(9):
for j in range(9):
if(board[i][j]!='.'):
numCount+=1
rowHash[i].add(board[i][j])
colHash[j].add(board[i][j])
blockHash[getBlockIdx(i,j)].add(board[i][j])
else:
idxSet.append((i,j))
# dfs
self.flag=False
def dfs(idxSet):
if(len(idxSet)==0):
self.flag=True
#print(board)
return
# # 出现无可选情况时 就return
# for idx in idxSet:
# i,j=idx
# if(len(allNum-(rowHash[i]|colHash[j]|blockHash[getBlockIdx(i,j)]))==0):
# return
i,j=idxSet.pop()
curChoose=list(allNum-(rowHash[i]|colHash[j]|blockHash[getBlockIdx(i,j)]))[:]
curFlag=False
for num in curChoose:
board[i][j]=num
rowHash[i].add(num)
colHash[j].add(num)
blockHash[getBlockIdx(i,j)].add(num)
# dfs
dfs(idxSet)
# 回溯
if(not self.flag):
board[i][j]='.'
rowHash[i].remove(num)
colHash[j].remove(num)
blockHash[getBlockIdx(i,j)].remove(num)
#idxSet.append((i,j))
else:
curFlag=True
return # 必须加这个return 不然board第一次是正确,后面又会被修改掉
if(not curFlag):
idxSet.append((i,j))
dfs(idxSet)
51. N 皇后
这题明明比上一题简单。。
关键就是判断每个Q的可选列位置:
- 排除掉同一列
- 排除掉同一斜线(考虑两种斜线,右斜和左斜)
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
# Q1:[i1,j] 皇后1 只能在第一行,列可选
result=[]
def dfs(Q,idx):
if(idx==n):
result.append(['.'*q+'Q'+'.'*(n-q-1) for q in Q])
colHave=set()
for i in range(len(Q)):
# 同一列
colHave.add(Q[i])
# 斜线
if(0<=Q[i]+idx-i<n):
colHave.add(Q[i]+idx-i)
if(0<=Q[i]-idx+i<n):
colHave.add(Q[i]-idx+i)
for j in set(range(n))-set(colHave):
dfs(Q+[j],idx+1)
dfs([],0)
return result