深搜经典题目小总结
深搜是递归的使用。通常用于n*n矩阵组成的地图中。从某个状态出发,沿着定义的几个方向遍历地图,通过定义边界条件或记录路径定义递归输出。directions常用来定义深搜的方向。
一、岛屿数量(Leetcode 200)
对于每个陆地,从某一点出发(grid[i][j]=“1”),使用深搜的方式,把可以连接在一起的陆地连接起来并把陆地值设置成水域值“0”(也可以设置成其他值),防止陷入死循环。
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
##定义搜索
directions=[(-1,0),(1,0),(0,-1),(0,1)]
##从(x,y)开始遍历,
def dfs(x,y):
grid[x][y]="0"
for i in directions:
ix,iy=i
dx=x+ix
dy=y+iy
#定义边界条件
if(dx<0 or dx>=len(grid) or dy<0 or dy>=len(grid[0])):
continue
else:
if(grid[dx][dy]=="1"):
dfs(dx,dy)
return
if(grid==[]):
return 0
total=0
for i in range(len(grid)):
for j in range(len(grid[0])):
if(grid[i][j]=="1"):
dfs(i,j)
total+=1
return total
二、矩阵中的最长递增路径(Leetcode 329)
1.使用DP优化深搜过程
2.使用step=max(step,1+dfs(dx,dy))寻找当前点出发的最长递增路径。
class Solution:
def longestIncreasingPath(self, matrix: List[List[int]]) -> int:
if(matrix==[]):
return 0
directions=[(-1,0),(1,0),(0,-1),(0,1)]
xlen=len(matrix)
ylen=len(matrix[0])
dp=[[0]*ylen for i in range(xlen)]
def dfs(x,y):
if(dp[x][y]>0):
return dp[x][y]
step=1
for i in directions:
ix,iy=i
dx=x+ix
dy=y+iy
if(dx<0 or dx>=xlen or dy<0 or dy>=ylen):
continue
if(matrix[dx][dy]>matrix[x][y]):
step=max(step,1+dfs(dx,dy))
dp[x][y]=step
return step
maxLength=0
for i in range(xlen):
for j in range(ylen):
if(dp[i][j]!=0):
temp=dp[i][j]
else:
temp=dfs(i,j)
if(temp>maxLength):
maxLength=temp
return maxLength
三、岛屿的最大面积(Leetcode 695)
在循环外定义count=1,在dfs返回值处定义count+=dfs(dx,dy)。这样,在dfs处返回的就是要求的x,y处的值。
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
directions=[(-1,0),(1,0),(0,-1),(0,1)]
icount=0
def dfs(x,y):
grid[x][y]=0
count=1
for i in directions:
ix,iy=i
dx=x+ix
dy=y+iy
if(dx<0 or dx>=len(grid) or dy<0 or dy>=len(grid[0])):
continue
if(grid[dx][dy]==1):
count+=dfs(dx,dy)
return count
maxSize=0
for i in range(len(grid)):
for j in range(len(grid[0])):
if(grid[i][j]==1):
num=dfs(i,j)
if(num>maxSize):
maxSize=num
return maxSize
四、机器人的运动范围(剑指offer 13)
岛屿数量的变型
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
book=[([0] * n) for i in range(m)]
directions=[(-1,0),(0,-1),(1,0),(0,1)]
def sum_gezi(x,y):
total=0
while(x!=0):
total+=x%10
x=int(x/10)
while(y!=0):
total+=y%10
y=int(y/10)
return total
def get_dir():
total=0
for i in range(m):
for j in range(n):
if(book[i][j]==1):
total+=1
return total
def dfs(x,y):
book[x][y]=1
if(x==m-1 and y==n-1):
return get_dir()
for res in directions:
ix,iy=res
dx=x+ix
dy=y+iy
if(dx<0 or dx>=m or dy<0 or dy>=n):
continue
if(book[dx][dy]==0 and sum_gezi(dx,dy)<=k):
dfs(dx,dy)
return get_dir()
return dfs(0,0)
上述程序没有直接返回运动格子总数,优化一下,边搜索,边记录走过路径的长度,可以降低时间复杂度。
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
book=[([0] * n) for i in range(m)]
directions=[(-1,0),(0,-1),(1,0),(0,1)]
def sum_gezi(x,y):
total=0
while(x!=0):
total+=x%10
x=int(x/10)
while(y!=0):
total+=y%10
y=int(y/10)
return total
def dfs(x,y):
book[x][y]=1
step=1
if(x==m-1 and y==n-1):
return step
for res in directions:
ix,iy=res
dx=x+ix
dy=y+iy
if(dx<0 or dx>=m or dy<0 or dy>=n):
continue
if(book[dx][dy]==0 and sum_gezi(dx,dy)<=k):
step+=dfs(dx,dy)
return step
return dfs(0,0)
五、全排列(LeetCode 46)
使用path记录路径,book记录nums中的元素是否被访问过。
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
def dfs(nums,length,path,deepth):
if(deepth==length):
# path[:]:复制path。否则,因为pop的存在,所有的path最后都是0
res.append(path[:])
return
for i in range(length):
if(book[i]==0):
book[i]=1
path.append(nums[i])
dfs(nums,length,path,deepth+1)
book[i]=0
path.pop()
return
length=len(nums)
book=[0]*length
res=[]
dfs(nums,length,[],0)
return res
六、全排列(LeetCode 47)
搜索+剪枝。深搜的两种变型,深搜+DP,深搜+剪枝。
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
def dfs(nums,length,depth,path):
if(depth==length):
res.append(path[:])
return
for i in range(length):
if(book[i]==0):
#如果前后两个元素相同,且刚退出,则说明已经存在该节点,不需再搜索,所以剪枝。
if(i>0 and nums[i]==nums[i-1] and book[i-1]==0):
continue
path.append(nums[i])
book[i]=1
dfs(nums,length,depth+1,path)
book[i]=0
path.pop()
return
length=len(nums)
book=[0]*length
res=[]
nums.sort()
dfs(nums,length,0,[])
return res
七、字符串的排列(剑指Offer 38)
根据字符串排列的特点,考虑深度优先搜索所有排列方案。即通过字符交换,先固定第 1位字符( n 种情况)、再固定第 2位字符(n-1种情况)、… 、最后固定第 n 位字符(1 种情况)。
当字符串存在重复字符时,排列方案中也存在重复方案。为排除重复方案,需在固定某位字符时,保证 “每种字符只在此位固定一次” ,即遇到重复字符时不交换,直接跳过。
class Solution:
def permutation(self, s: str) -> List[str]:
c,res=list(s),[]
def dfs(x):
if(x==len(c)-1):
res.append("".join(c))
return
dic=[]
for i in range(x,len(c)):
if(c[i] in dic):continue
dic.append(c[i])
c[i],c[x]=c[x],c[i]
dfs(x+1)
c[i],c[x]=c[x],c[i]
dfs(0)
return res
根据这几道题,发现一个规律
1.在遍历开始前定义step=1,step=max(step,1+dfs(dx,dy))。记录某个路径step数。
2.在遍历开始前定义step=1,step+=dfs(dx,dy)。记录经过所有点的大小。
除此以外,参考上面的题,还有剪枝和DP两种用法。
八、矩阵中的路径(剑指offer 12)
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
def dfs(i, j, k):
if not 0 <= i < len(board) or not 0 <= j < len(board[0]) or board[i][j] != word[k]: return False
if k == len(word) - 1: return True
tmp, board[i][j] = board[i][j], '/'
res = dfs(i + 1, j, k + 1) or dfs(i - 1, j, k + 1) or dfs(i, j + 1, k + 1) or dfs(i, j - 1, k + 1)
board[i][j] = tmp
return res
for i in range(len(board)):
for j in range(len(board[0])):
if dfs(i, j, 0): return True
return False
九、打印从1 到最大的n位数
class Solution:
def printNumbers(self, n: int) -> [int]:
def dfs(x):
if x == n:
s = ''.join(num[self.start:])
if s != '0': res.append(int(s))
if n - self.start == self.nine: self.start -= 1
return
for i in range(10):
if i == 9: self.nine += 1
num[x] = str(i)
dfs(x + 1)
self.nine -= 1
num, res = ['0'] * n, []
self.nine = 0
self.start = n - 1
dfs(0)
return res