目录
综述
相同点
- 记录当前的选择
dfs采用每次只进行一次选择,大部分题目需要回溯
bfs采用保存所有可以进行的选择,不需回溯。 - 边界条件(按顺序判断):
- 越界
- 是否满足要求
- 做选择时的边界
DFS
分类
- 分为需要回溯
(n皇后) - 不需要回溯
(穷举问题:岛屿问题,打家劫舍3,机器人运动范围,子集问题)
都需要添加标志数组
思路
- 需要回溯的问题一般模式为递归+回溯(编写简单)或者栈迭代(空间复杂度大)
模板
需要回溯问题主函数定义:
- path (需要回溯)
- res(注意添加path需要new)
- flag需要回溯,标记是否经过,一般都要有
作用:减小时间复杂度同时避免结果集重复,
记录是否走过,保证结果集正确(比如子集和排列问题一个需要一个不需要flag) - grid,row,col等(初始数据及做出的选择)
- dfs(path,res,flag,grid,做出的选择)
注意: - flag,做出的选择都是为了判断边界和结果集正确性,有时并不需要。比如子集不需要flag但是需要做出的选择变量,而排列需要flag但是不需要做出的选择变量。需要根据问题属性来进行变通。
- 主函数多种选择问题
有时候主函数也需要进行选择,比如n皇后问题初始点不是固定点,此时可以进行两种选择
- 一种是以一个只有一种选择的初始量进行初始函数递归但是有可能会有边界编写位置问题
比如子集,排列问题,n皇后问题,是否存在字符串问题等 - 一种是为了使用模板尽量采取在dfs函数参数列表添加选择的变量的方式。(详细见n皇后代码)
两种方式都有各自的好处。而且大部分题目可以转化,只是尽量采取第一种会更加简洁。比如n皇后可以采取第一种方法进行把行变量当作选择,边界迁移至for循环部分。
需要回溯问题递归函数定义:
- 注意:
注意有些题目比如单词搜索的回溯只进行了一次,虽然有四种选择,但是path添加和flag置位都是已经选过的元素,不是对选择的回溯。注意理解。
dfs(path,res,flag,grid,做出的选择变量){
//注意边界条件最好不要写在做选择的代码部分,写在初始部分容易编写且不容易出错。
//边界条件需要对做出选择是否越界和是否符合结果集进行判断
//有时不需要边界条件,比如子集问题做选择时自带边界条件
(边界条件:)
if:
grid越界
return
if:
path符合最终要求,
res.add(new (path))
return
//这里需要注意的是for里面进行选择和回溯不能在外面
for(在边界里进行选择):
path添加
flag置位
做出选择
path删除
flag回溯
}
题目
不需回溯
岛屿数量
class Solution {
int count = 0;
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) {
return 0;
}
int row = grid.length;
int col = grid[0].length;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (grid[i][j] == '1') {
count++;
bfs(i, j, row, col, grid);
}
}
}
return count;
}
public void bfs(int i, int j, int row, int col, char[][] grid) {
if (i >= row || j >= col || i < 0 || j < 0||grid[i][j]=='0') {
return;
}
if (grid[i][j] == '1') {
grid[i][j] = '0';
bfs(i + 1, j, row, col, grid);
bfs(i - 1, j, row, col, grid);
bfs(i, j - 1, row, col, grid);
bfs(i, j + 1, row, col, grid);
}
}
}
岛屿最大面积
class Solution {
int res = 0,temp = 0;
public int maxAreaOfIsland(int[][] grid) {
int row = grid.length,col = grid[0].length;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (grid[i][j]==1){
temp = 0;
dfs(grid,i,j);
}
}
}
return res;
}
public void dfs(int [][] grid,int row,int col){
if (row<0||row>=grid.length||col<0||col>=grid[0].length||grid[row][col]==0)return;
grid[row][col] = 0;
temp++;
dfs(grid,row+1,col);
dfs(grid,row-1,col);
dfs(grid,row,col+1);
dfs(grid,row,col-1);
res = Math.max(res,temp);
}
}
被围绕的区域
class Solution {
public void solve(char[][] board) {
if (board==null||board.length==0||board[0].length==0)return;
int row = board.length;
int col = board[0].length;
boolean[][] flag = new boolean[board.length][board[0].length];
for (int i = 0; i < col; i++) {
if (board[0][i]=='O')
dfs(board,flag,0,i,1);
}
for (int i = 0; i < col; i++) {
if (board[board.length-1][i]=='O')
dfs(board,flag,board.length-1,i,1);
}
for (int i = 0; i < row; i++) {
if (board[i][0]=='O')
dfs(board,flag,i,0,1);
}
for (int i = 0; i < row; i++) {
if (board[i][board[0].length-1]=='O')
dfs(board,flag,i,board[0].length-1,1);
}
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (board[i][j]=='O')
dfs(board,flag,i,j,0);
}
}
}
public void dfs(char[][]board,boolean[][]flag,int row,int col,int bianjieflag){
if (row<0||row>=board.length||col<0||col>=board[0].length||flag[row][col]==true||board[row][col]=='X'){
return;
}
flag[row][col] = true;
if (bianjieflag==0){
board[row][col]='X';
}
dfs(board,flag,row+1,col,bianjieflag);
dfs(board,flag,row-1,col,bianjieflag);
dfs(board,flag,row,col+1,bianjieflag);
dfs(board,flag,row,col-1,bianjieflag);
}
}
打家劫舍3
class Solution {
public int rob(TreeNode root) {
int [] res = dfs(root);
return Math.max(res[0],res[1]);
}
public int[] dfs(TreeNode root){
if (root==null)return new int[2];
int[] res = new int[2];
int [] left = dfs(root.left);
int [] right = dfs(root.right);
res[0] = Math.max(left[0],left[1])+Math.max(right[0],right[1]);
res[1] = left[0]+right[0]+root.val;
return res;
}
}
机器人运动范围
class Solution {
int ans = 0;
boolean [][] flag;
public int movingCount(int m, int n, int k) {
flag = new boolean[m][n];
dfs(0,0,m,n,k);
return ans;
}
public void dfs(int row,int col,int rowsize,int colsize,int k){
if (row<0||col<0||row>=rowsize||col>=colsize||flag[row][col]==true||!isstatis(row,col,k)){
return;
}
flag[row][col] = true;
ans++;
dfs(row+1,col,rowsize,colsize,k);
dfs(row-1,col,rowsize,colsize,k);
dfs(row,col+1,rowsize,colsize,k);
dfs(row,col-1,rowsize,colsize,k);
}
public boolean isstatis(int m ,int n,int k){
if ((suoshu(m)+suoshu(n))<=k){
return true;
}else
return false;
}
public int suoshu(int num){
int res = 0;
if (num==0)return res;
res+=num%10;
num/=10;
res+=suoshu(num);
return res;
}
}
下一个排列
- 其实分为两种情况,一种是末端增长趋势(比较简单交换数字位置就好),一种是末端下降趋势(需要找到上升的位置,然后找到第一个比它大的数字,交换后反转)
- 本代码取巧写的很简洁,两种情况合并。
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
class Solution {
public void nextPermutation(int[] nums) {
int i = nums.length-2;
//先找到倒数第一个极大值
while (i>=0&&nums[i]>=nums[i+1]){
i--;
}
//如果存在极大值就说明是第二种情况,需要先交换再遍历再反转
if(i>=0){
int j = nums.length-1;
while (j>=0&&nums[i]>=nums[j]){
j--;
}
swap(nums,i,j);
}
reverse(nums,i+1);
}
public void swap(int [] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public void reverse(int [] nums,int i){
int j=nums.length-1;
while (i<j){
swap(nums,i,j);
i++;
j--;
}
}
}
ip地址划分
题解:
由于我们需要找出所有可能复原出的 IP 地址,因此可以考虑使用递归的方法,对所有可能的字符串分隔方式进行搜索,并筛选出满足要求的作为答案。
设题目中给出的字符串为 ss。我们用递归函数 \textit{dfs}(\textit{segId}, \textit{segStart})dfs(segId,segStart) 表示我们正在从 s[\textit{segStart}]s[segStart] 的位置开始,搜索 IP 地址中的第 \textit{segId}segId 段,其中 \textit{segId} \in {0, 1, 2, 3}segId∈{0,1,2,3}。由于 IP 地址的每一段必须是 [0, 255][0,255] 中的整数,因此我们从 \textit{segStart}segStart 开始,从小到大依次枚举当前这一段 IP 地址的结束位置 \textit{segEnd}segEnd。如果满足要求,就递归地进行下一段搜索,调用递归函数 \textit{dfs}(\textit{segId} + 1, \textit{segEnd} + 1)dfs(segId+1,segEnd+1)。
特别地,由于 IP 地址的每一段不能有前导零,因此如果 s[\textit{segStart}]s[segStart] 等于字符 00,那么 IP 地址的第 \textit{segId}segId 段只能为 00,需要作为特殊情况进行考虑。
在递归搜索的过程中,如果我们已经得到了全部的 44 段 IP 地址(即 \textit{segId} = 4segId=4),并且遍历完了整个字符串(即 \textit{segStart} = |s|segStart=∣s∣,其中 |s|∣s∣ 表示字符串 ss 的长度),那么就复原出了一种满足题目要求的 IP 地址,我们将其加入答案。在其它的时刻,如果提前遍历完了整个字符串,那么我们需要结束搜索,回溯到上一步。
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/restore-ip-addresses/solution/fu-yuan-ipdi-zhi-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Given "25525511135",
return ["255.255.11.135", "255.255.111.35"].
public List<String> restoreIpAddresses(String s) {
List<String> addresses = new ArrayList<>();
StringBuilder tempAddress = new StringBuilder();
doRestore(0, tempAddress, addresses, s);
return addresses;
}
private void doRestore(int k, StringBuilder tempAddress, List<String> addresses, String s) {
if (k == 4 || s.length() == 0) {
if (k == 4 && s.length() == 0) {
addresses.add(tempAddress.toString());
}
return;
}
for (int i = 0; i < s.length() && i <= 2; i++) {
if (i != 0 && s.charAt(0) == '0') {
break;
}
String part = s.substring(0, i + 1);
if (Integer.valueOf(part) <= 255) {
if (tempAddress.length() != 0) {
part = "." + part;
}
tempAddress.append(part);
doRestore(k + 1, tempAddress, addresses, s.substring(i + 1));
tempAddress.delete(tempAddress.length() - part.length(), tempAddress.length());
}
}
}
需要回溯
n皇后
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public static void main(String[] args) {
System.out.println(new Solution().solveNQueens(4));
}
public List<List<String>> solveNQueens(int n) {
ArrayList<Integer> path = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
int curnum = 0;
for (int i = 0; i < n; i++) {
path.add(i);
backtrace(n,i,path,res);
path.remove(path.size()-1);
}
return putout(res,n);
}
public void backtrace(int n,int curnum,ArrayList<Integer> path,List<List<Integer>> res){
if (path.size()>n||!statisfy(curnum,path)){
return;
}
if (path.size()==n){
res.add(new ArrayList<Integer>(path));
return;
}
for (int i = 0; i < n; i++) {
path.add(i);
backtrace(n,i,path,res);
path.remove(path.size()-1);
}
}
public boolean statisfy(int curnum,ArrayList<Integer>path){
if (path.isEmpty()||path.size()==1)
return true;
for (int i = 0; i < path.size()-1; i++) {
int row = i,col = path.get(i);
int currow = path.size()-1,curcol = curnum;
int temp1 = curcol-col;
int temp2 = currow-row;
if (col==curcol||Math.abs(temp1)==Math.abs(temp2))
return false;
}
return true;
}
public List<List<String>> putout(List<List<Integer>> res,int n){
List<List<String>> ans = new ArrayList<>();
int size = n;
for (int i = 0; i < res.size(); i++) {
List<String> list = new ArrayList<>();
List<Integer> path = res.get(i);
for (int j = 0; j < path.size(); j++) {
char[] chars = new char[n];
Arrays.fill(chars,'.');
chars[path.get(j)] = 'Q';
StringBuilder str = new StringBuilder();
for (int k = 0; k < n; k++) {
str.append(chars[k]);
}
list.add(str.toString());
}
ans.add(list);
}
return ans;
}
}
电话号码字母组合
class Solution {
public List<String> letterCombinations(String digits) {
List<String> res = new ArrayList<String>();
if (digits==null||digits.length()==0)return res;
StringBuilder path = new StringBuilder();
String[] dic = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
dfs(dic,path,res,digits);
return res;
}
public void dfs(String [] dic,StringBuilder path,List<String> res,String digits){
if (path.length()==digits.length()){
res.add(new String(path.toString()));
return;
}
int begin = path.length();
char c = digits.charAt(begin);
Integer intdata = Integer.valueOf(c-'2');
String s = dic[intdata];
char[] chars = s.toCharArray();
for (int i = 0; i < chars.length; i++) {
path.append(chars[i]);
dfs(dic,path,res,digits);
path.deleteCharAt(path.length()-1);
}
}
}
单词搜索
- 注意
利用strlen记录单词长度,避免使用path的记录耗费
注意此题目的回溯只进行了一次,虽然有四种选择,但是path添加和flag置位都是已经选过的元素,不是对选择的回溯。注意理解。
class Solution {
public boolean exist(char[][] board, String word) {
if (board==null||board.length==0||board[0].length==0)return false;
if (word==null||word.length()==0)return false;
boolean[][] isvisited = new boolean[board.length][board[0].length];
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (word.charAt(0)==board[i][j]){
int strlen = 0;
if (back(board,word,i,j,strlen,isvisited)){
return true;
}
}
}
}
return false;
}
public boolean back(char[][]board,String word,int row,int col,int strlen,boolean[][]isvisited){
if (row<0||row>=board.length||col<0||col>=board[0].length||isvisited[row][col])return false;
if (word.charAt(strlen)!=board[row][col])return false;
if (strlen==word.length()-1)return true;
isvisited[row][col] = true;
strlen++;
boolean ans =
back(board,word,row+1,col,strlen,isvisited)||
back(board,word,row-1,col,strlen,isvisited)||
back(board,word,row,col+1,strlen,isvisited)||
back(board,word,row,col-1,strlen,isvisited);
strlen--;
isvisited[row][col] = false;
return ans;
}
}
图问题
好友关系数目判断多少个环路
- 注意
此题使用bfs比较麻烦,需要很多个标志位,所以使用dfs进行穷举求解,此题不需要入度表和出度表因为只是求解多少个连通图,不是环路
class Solution {
class Solution {
//bfs判断环形回路存在与否
public int findCircleNum(int[][] M) {
boolean[] isvisited = new boolean[M.length];
int res = 0;
for (int i = 0; i < M.length; i++) {
if (isvisited[i]==false){
res++;
//注意此处需要在主函数进行选择
dfs(isvisited,M,i);
}
}
return res;
}
private void dfs(boolean[] isvisited,int [][]M,int row) {
for (int i = 0; i < M.length; i++) {
if (isvisited[i]==false&&M[row][i]==1){
isvisited[i] = true;
dfs(isvisited,M,i);
}
}
}
}
}
课程表
- 入度数组和出度(二维数组)
- 队列记录入度为0的节点,只有入度为0才可以进行出度的while循环。
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
int[] intable = new int[numCourses];
List<List<Integer>> outtab = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
outtab.add(new ArrayList<Integer>());
}
for (int[] prerequisite : prerequisites) {
intable[prerequisite[0]]++;
outtab.get(prerequisite[1]).add(prerequisite[0]);
}
LinkedList<Integer> sta = new LinkedList<>();
for (int i = 0; i < intable.length; i++) {
if (intable[i]==0)
sta.addLast(i);
}
while (!sta.isEmpty()){
Integer cur = sta.removeFirst();
numCourses--;
for (Integer integer : outtab.get(cur)) {
intable[integer]--;
if (intable[integer]==0)sta.addLast(integer);
}
}
return numCourses==0;
}
}
网络延迟时间
import java.util.*;
//建立一个map存储有向邻接图
//堆优化代替松弛操作
class Solution {
//迪杰斯特拉堆优化
public int networkDelayTime(int[][] times, int N, int K) {
int[] dis = new int[N + 1];
boolean[] vis = new boolean[N + 1];
//存储到各个目的节点的路径值。
HashMap<Integer, List<int[]>> map = new HashMap<>();
for (int[] time : times) {
map.computeIfAbsent(time[0],k->new ArrayList<>()).add(new int[]{time[1],time[2]});
}
Arrays.fill(dis,0x3f3f3f3f);
dis[K] = 0;
dis[0] = 0;
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return dis[o1]-dis[o2];
}
});
queue.offer(K);
while (!queue.isEmpty()){
Integer poll = queue.poll();
//剪枝
if (vis[poll])continue;
vis[poll] = true;
List<int[]> list = map.getOrDefault(poll, Collections.emptyList());
for (int[] ints : list) {
int next = ints[0];
//剪枝
if (vis[next])continue;
dis[next] = Math.min(dis[next],ints[1]+dis[poll]);
queue.offer(next);
}
}
int res = Arrays.stream(dis).max().getAsInt();
return res==0x3f3f3f3f?-1:res;
}
}
子集排列问题综述
- 需要注意的是虽然初始有很多种选择,但是本类型的题目并不需要主函数进行选择,比较简单的做法是把可以选择的初始索引作为可选择的变量,
无重复子集
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
class Solution {
public static void main(String[] args) {
System.out.println(new Solution().subsets(new int[]{1,2,3}));
}
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
ArrayList<Integer> path = new ArrayList<>();
dfs(nums,res,path,0);
return res;
}
public void dfs(int [] nums,List<List<Integer>>res,ArrayList<Integer> path,int start){
//记录的是所有的路径不需要边界条件
res.add(new ArrayList<>(path));
//此处设置边界条件
for (int i = start; i < nums.length; i++) {
path.add(nums[i]);
dfs(nums,res,path,i+1);
path.remove(path.size()-1);
}
}
}
重复子集
//相比于非重复子集多的就是一个判断条件,注意是判断nums[i]==nums[i-1]从头判断。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public static void main(String[] args) {
System.out.println(new Solution().subsetsWithDup(new int[]{1,2,2}));
}
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> list = new ArrayList<>();
ArrayList<Integer> path = new ArrayList<>();
dfs(list,path,nums,0);
return list;
}
public void dfs(List<List<Integer>>list,ArrayList<Integer>path,int [] nums,int start){
list.add(new ArrayList<>(path));
for (int i = start;i<nums.length;i++){
if (i!=start&&nums[i]==nums[i-1])
continue;
path.add(nums[i]);
dfs(list,path,nums,i+1);
path.remove(path.size()-1);
}
}
}
全排列
class Solution {
public List<List<Integer>> permute(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> list = new ArrayList<>();
ArrayList<Integer> path = new ArrayList<>();
HashSet<Integer> set = new HashSet<>();
boolean[] flag = new boolean[nums.length];
dfs(list,path,nums,0,flag);
return list;
}
public void dfs(List<List<Integer>>list,ArrayList<Integer>path,int [] nums,int start,boolean [] flag){
if (path.size()==nums.length)
list.add(new ArrayList<>(path));
for (int i = 0;i<nums.length;i++){
if (flag[i]==false){
flag[i] = true;
path.add(nums[i]);
dfs(list,path,nums,i+1,flag);
path.remove(path.size()-1);
flag[i] = false;
}
}
}
}
重复全排列
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] flag = new boolean[nums.length];
dfs(nums,flag,path,res);
return res;
}
public void dfs(int [] nums,boolean [] flag,List<Integer> path,List<List<Integer>>res){
if (path.size()==nums.length){
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (i>0&&nums[i]==nums[i-1]&&(flag[i-1]==false))
continue;
if (flag[i]==false){
flag[i] = true;
path.add(nums[i]);
dfs(nums,flag,path,res);
path.remove(path.size()-1);
flag[i] = false;
}
}
}
BFS
思路
- queue数据结构保存之前的所有选择
模板
bfs(){
//定义变量
queue,res,flag
//初始选择添加
queue.push(初始值)
//置位flag
flag(初始值)
//bfs循环
while(queue!=empty){
int size = queue.size();
for(int i = 0;i < size;i++){
temp = queue.pop();
for(temp可以进行的选择){
//判断越界
if(grid等)
//判断是否满足最终要求
if(temp满足条件){
path,res添加等;
return;
}
//把可以进行的选择放入queue
queue.push(temp的选择);
}
}
}
}
题目
二进制矩阵最短路径
class Solution {
public int shortestPathBinaryMatrix(int[][] grid) {
LinkedList<Pair<Integer, Integer>> list = new LinkedList<>();
int res = 0;
return bfs(list, grid);
}
public int bfs(LinkedList<Pair<Integer, Integer>> list, int[][] grid) {
int res = 0;
if (grid[0][0]==1)return -1;
list.addLast(new Pair<>(0, 0));
boolean[][] flag = new boolean[grid.length][grid[0].length];
int[][] dir = {{1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}, {0, -1}, {1, -1}};
boolean gettarget = false;
flag[0][0] = true;
while (!list.isEmpty()) {
int size = list.size();
for (int i = 0; i < size; i++) {
Pair<Integer, Integer> temp = list.removeFirst();
int row = temp.getKey();
int col = temp.getValue();
if (row == grid.length - 1 && col == grid[0].length - 1) {
return ++res;
}
for (int j = 0; j < dir.length; j++) {
int newrow = row+dir[j][0];
int newcol = col+dir[j][1];
if (isout(newrow,newcol,grid.length,grid[0].length,grid,flag)){
list.addLast(new Pair<>(newrow,newcol));
flag[newrow][newcol] = true;
}
}
}
res++;
}
return -1;
}
public boolean isout(int row, int col, int sizerow, int sizecol, int[][] grid,boolean[][]flag) {
if (row < 0 || row >= sizecol || col < 0 || col >= sizecol || grid[row][col] == 1||flag[row][col]==true) {
return false;
} else
return true;
}
}