算法总结三:DFS可以套用模版吗?

本文详细介绍了DFS(深度优先搜索)、回溯和递归的概念,并通过N-Queens等经典算法实例探讨了它们之间的关系。区分了回溯作为DFS核心动作的特性,同时指出并非所有DFS都需要回溯。文章还讨论了剪枝策略在算法优化中的应用,并列举了多个使用DFS解决的实际问题,如岛屿数量、最短路径等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习目标:

辨别: 什么是DFS深度优先? 什么是back tracking回溯? 什么是recursion递归?

学习内容:

1、 DFS深度优先的高频题

学习时间:

1、 周一至周日晚上 7 点—晚上9点

学习产出:

1、 技术笔记 博客 1 篇

=================
什么是DFS深度优先?
什么是back tracking回溯?
什么是recursion递归?

在学习leetcode中backtracking题目时,碰到了经典的DFS题目N-Queens。
为啥这个题目既是backtracking又是DFS呢?
DFS中为啥又有base case+recursion rule呢?

======================
一。区别几个定义

recursion是将当前的问题,分解成小一号的问题(divide and conquer)。在解决小问题的时候,然后recursively用同样的逻辑就把大问题“递归”好了。
recursion中,我们关心:
子问题是什么?当前做点什么?返回给上一层什么?

DFS是在进行“”的搜索,或者问题可以被逻辑视为“树”、“层”、“叉”时,人为规定一个深度优先搜索的方式。
DFS有base case,规定何时return,触底反弹。
有recursion rule,“递归”规则为:
cur.add(i);
dfs(index+1,res,cur); //这里是recursion,进入下一层问题
cur.remove(cur.size()-1);
即加入一个元素i,index+1进入下一层,若回溯时需要移除该元素,方可回到这一层,然后试另外一个元素(叉)。
N-Queens可以视为:每一个row是一层,有N个可能性,放好第一个Q就进入下一层。

在这里插入图片描述
深度优先遍历顺序为 1->2->4->8->5->3->6->7 from jianshu.com
其实是pre-order traversal根左右的顺序。

 				public void depthOrderTraversal(){ 
                    if(root==null){ 
                      System.out.println("empty tree"); 
                      return; 
                    } 
                    ArrayDeque<TreeNode> stack=new ArrayDeque<TreeNode>(); 
                    stack.push(root); 
                    while(stack.isEmpty()==false){ 
                        TreeNode node=stack.pop();   
                         System.out.print(node.value+" ");
                         if(node.right!=null){ 
                              stack.push(node.right); 
                          }
                         if(node.left!=null){ 
                              stack.push(node.left); 
                          } 
                  	} 
                  	System.out.print("\n");
             	}

联系recursion
进入下一层又可以视为分解为小一号的问题
小一号的问题,一直被递归到最小,然后触底反弹,“归来”解决了大问题。
无特殊情况,DFS就用递归实现!!!

back tracking回溯定义
Backtracking is a general algorithm for finding all (or some) solutions to some computational problems, notably constraint satisfaction problems, that incrementally builds candidates to the solutions, and abandons a candidate (“backtracks”) as soon as it determines that the candidate cannot possibly be completed to a valid solution.
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法。
即:放进去,进入下一层,剔除返回上一层的过程。
cur.add(i);
dfs(index+1,res,cur);
cur.remove(cur.size()-1);//这里是back tracking回溯
这岂不就是dfs?
其实,回溯是dfs的核心动作。
但不是所有DFS都有回溯这个动作。
例如,在下面的几个“岛屿”题中,并不需要回溯!

======================
二。back tracking算法

51. N-Queens
The n-queens puzzle is the problem of placing n queens on an n x n chessboard such that no two queens attack each other.

Given an integer n, return all distinct solutions to the n-queens puzzle.
如果返回值是List<Integer>
这里利用DFS深度优先搜索的方法:

public class Solution {
  public List<List<Integer>> nqueens(int n) {
    List<List<Integer>> res=new ArrayList<>();
    List<Integer> oneRes=new ArrayList<>();
    dfs(n,res,oneRes);
    return res;
  }
  private void dfs (int n,List<List<Integer>>res,List<Integer>oneRes){
    //base case
    if(oneRes.size()==n){
      res.add(new ArrayList<Integer>(oneRes));//new !
      return;
    }
    //recurs rule:这里没有使用idx因oneRes.size()即 idx
     for(int i=0;i<n;i++){
      if(checkValid(oneRes,i)){
        oneRes.add(i);
        dfs(n,res,oneRes);
        oneRes.remove(oneRes.size()-1);//back tracking
      } 
    }
  }
  private boolean checkValid (List<Integer> oneRes,int colIdx){ 
    int currow=oneRes.size();
      for(int i=0;i<currow;i++){
        if(oneRes.get(i)==colIdx || Math.abs(oneRes.get(i)-colIdx)==currow-i){
          return false;
        }
      }
      return true;
  }
}

78. Subsets
Given an integer array nums, return all possible subsets (the power set).
The solution set must not contain duplicate subsets.

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> res=new ArrayList<>();
        List<Integer> cur=new ArrayList<>();
        int n=nums.length;
        dfs(nums,res,cur,0,n);
        return res;       
    }
    
    void dfs(int[] nums,List<List<Integer>> res,List<Integer> cur,int index,int n){
        //base
        if(index==n){
            res.add(new ArrayList<>(cur));
            return;
        }
        //recursion rule
        //不+
        dfs(nums,res,cur,index+1,n);
        //+
        cur.add(nums[index]);
        dfs(nums,res,cur,index+1,n);
        cur.remove(cur.size()-1);//back tracking
        
    }
}

22. Generate Parentheses
Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> res=new ArrayList<String> ();
        StringBuilder sb=new StringBuilder();
        
        dfs(res,sb,0,n,n);
        return res;
    }
    public void dfs(List<String> res,StringBuilder sb,int index,int leftRemain,int rightRemain){
        //base
        if(leftRemain==0 && rightRemain==0){
            res.add(sb.toString());
            return;
        }
        //recursive rule
        if(leftRemain>0){
            sb.append('(');
            dfs(res,sb,index+1,leftRemain-1,rightRemain);
            sb.deleteCharAt(sb.length()-1);//back tracking
        }
        
        if(leftRemain<rightRemain){
            sb.append(')');
            dfs(res,sb,index+1,leftRemain,rightRemain-1);
            sb.deleteCharAt(sb.length()-1);//back tracking
        }
    }
}

46. Permutations
Given an array nums of distinct integers, return all the possible permutations. You can return the answer in any order.

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        //swap and swap
        List<Integer> cur=new ArrayList<>();
        List<List<Integer>> res=new ArrayList<>();
  
        for(int num:nums){
            cur.add(num);
        }
        int n=nums.length;
        dfs(n,cur,res,0);
        return res;
    }
    
    void dfs(int n, List<Integer> cur,List<List<Integer>> res,int index){
         //base case
        if(index==n){
            res.add(new ArrayList<>(cur));
            return;
        }
        //recursion rule
        for(int i=index;i<n;i++){
            Collections.swap(cur,index,i);
            dfs(n,cur,res,index+1);
            Collections.swap(cur,index,i);//back tracking
            //区别laicode原题: 这里是swap ArrayList内的数, 所以直接调用Collections.swap
        }
    }    
    // void swap(int[] nums,int a,int b){
    //     int tmp=nums[a];
    //     nums[a]=nums[b];
    //     nums[b]=tmp;
    // }
}

39. Combination Sum

Given an array of distinct integers candidates and a target integer target, return a list of all unique combinations of candidates where the chosen numbers sum to target. You may return the combinations in any order.

The same number may be chosen from candidates an unlimited number of times. Two combinations are unique if the frequency of at least one of the chosen numbers is different.

It is guaranteed that the number of unique combinations that sum up to target is less than 150 combinations for the given input.
Example 1:
Input: candidates = [2,3,6,7], target = 7
Output: [[2,2,3],[7]]

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res=new ArrayList<>();
        List<Integer> oneRes=new ArrayList<Integer>();
        dfs(res,oneRes,candidates,target,0);
        return res;
    }
    
    void dfs(List<List<Integer>> res,List<Integer> oneRes,int[] candidates, int target,int index){
        //base case
        if(target==0){
            res.add(new ArrayList<>(oneRes));
            return;
        }else if(target<0){
            return; //bug
        }
        
        //recursive rule
        int n=candidates.length;
         
        for(int i=index;i<n;i++){
            oneRes.add(candidates[i]); //bug2
            dfs(res,oneRes,candidates,target-candidates[i],i);
            //bug2: 这里是有没有实现level++?如何实现数字的重复?想清楚(看图)
            oneRes.remove(oneRes.size()-1);//back tracking
        }
    }
}

延伸高频题:
698. Partition to K Equal Sum Subsets
Given an array of integers nums and a positive integer k, find whether it’s possible to divide this array into k non-empty subsets whose sums are all equal.
example:[2,3,5,6],k=3 result:false
思路:排序,然后先排除一些可能明显错误的例子,如上:无法整除为三份,或者6大于1/3。
如果[2,3,5,5]可以整除3,那么得到target为5。

class Solution {
    public boolean canPartitionKSubsets(int[] nums, int k) {
        if(nums==null || nums.length==0){
            return false;
        }
        
        int sum=Arrays.stream(nums).sum();
        if(sum%k!=0) return false;
        int target= sum/k;//subsum
        
        Arrays.sort(nums);
        int beginIndex=nums.length-1;
        if(nums[beginIndex]>target){
            return false;
        }
        while(beginIndex>=0 && nums[beginIndex]==target){
            beginIndex--;//自成一组
            k--;
        }
        return partition(new int[k],nums,beginIndex,target);//child problem
    }
    
    boolean partition(int[]subsets,int[] nums,int index, int target){
        //base case
        if(index<0) return true;
        
        //recursion rule  :  1 2 3 4
        int selected=nums[index];
        for(int i=0;i<subsets.length;i++){ //对subset每一个数,尝试放入
            if(subsets[i]+selected<=target){ //if 小于目标,还可以放入
                subsets[i]+=selected;
                if(partition(subsets,nums,index-1,target)){
                    return true;//如果index-1子问题成立,那么当前层成立
                    //即成功放入subset中,就返回true
                }
                subsets[i]-=selected;//back tracking//如果不能的话,就减掉
            }
        }
        return false;
    }   
}

Restore IP Addresses
这题也是用back tracking,但细节处理比较多:
下面这个解法会出现Time Limit Exceeded。
bug:限制了s的长度后,下面解法可行了。
if(s.length()>12) return res;
重点理解:offset的作用:
用来iterate字符array,三种情况中,offset是指针,每次尝试跳一个char,两个char,三个char。

class Solution {
    public List<String> restoreIpAddresses(String s) {
        List<String> res=new ArrayList<String>();
        if(s==null || s.length()==0 || s.length()>12) return res;
        StringBuilder sb=new StringBuilder();
        dfs(s.toCharArray(),res,sb,0,0);
        return res;
    }
    void dfs(char[] s,List<String> res,StringBuilder sb,int index,int offset){
        //base case
        if(index==4){//第四层,放入第四个点后,
            if(sb.length()==s.length+4){//并且长度增加四个char
                res.add(sb.substring(0,sb.length()-1)); //常规是sb从0到sb.length-1,这里排除sb.length-1这一位
            }
        }
        
        //offset的作用就是iterate字符array,三种情况中,offset是尝试每次跳一个char,两个char,三个char
        if(offset<s.length){  //offset每次会加1,加2,或者加3,
            //只要长度还在要求范围内,就可以添加、删除。
            sb.append(s[offset]).append('.');
            //2.   删除2位
            dfs(s,res,sb, index+1,offset+1);
            sb.delete(sb.length()-2,sb.length()); //back tracking
            //比如2.   可以删除2.
        }
        
        if(offset+1<s.length){
            char a=s[offset];
            char b=s[offset+1];
            if(a!='0'){//10.    19.   99.
                sb.append(a).append(b).append('.');
                dfs(s,res,sb, index+1,offset+2);
                sb.delete(sb.length()-3,sb.length()); //25.删除3位
            }
        } 
        
        if(offset+2<s.length){ //a,b,c 
            char a=s[offset];
            char b=s[offset+1];
            char c=s[offset+2];
            
            if(a=='1' //0-199
            ||a=='2' && b>='0' && b<='4' //200  249
            ||a=='2' && b=='5' && c>='0' && c <='5'){ //250-255
                sb.append(a).append(b).append(c).append('.');
                dfs(s,res,sb, index+1,offset+3);
                sb.delete(sb.length()-4,sb.length());//255.删除4位
            }
        }
    }
}

迷宫:
给定一个M*N的矩阵(二维数组),分别用0和1表示通路和障碍物。即 0 表示 通路;1 表示 障碍物。从矩阵的左上角开始,每次只能向右,下,左,上移动位置,不能斜着走。请给出从入口到出口的路线。

public class Maze {
	static String path = "";
    static String shortestPath = "";

    public static void searchInMaze(int x, int y, int[][] maze) {
        int m=maze.length;
        int n=maze[0].length;
        //base cases: out of bound, ==1,  //cut the branches,
        if(x<0 || y<0) return;
        if (x > m - 1 || y > n - 1) return;
        if(maze[x][y]==1) return;
        //the destination : m-1,n-1
        if(x==m-1 && y==n-1){
            path=path+"("+x+","+y+")";
            //update the shortest path
            if(shortestPath.length()==0 || path.length()<shortestPath.length()){
                shortestPath=path;
            }
            //System.out.println("找到路线:" + path);
            return;
        }

        //recursion rule:
        String temp=path;
        path=path+"("+x+","+y+")"+"->";
        maze[x][y]=1;

        searchInMaze(x + 1, y, maze);  //向右搜索
        searchInMaze(x, y + 1, maze);  //向下搜索
        searchInMaze(x, y - 1, maze);  //向上搜索
        searchInMaze(x - 1, y, maze);  //向左搜索
        //清除
        maze[x][y]=0;//back tracking
        path=temp;
    }
    public static void main(String[] args) {
        int[][] maze = {
                {0, 0, 1, 1, 1, 1, 1, 1, 1},
                {1, 0, 0, 0, 0, 0, 0, 0, 1},
                {1, 0, 1, 1, 0, 1, 1, 0, 1},
                {1, 0, 1, 0, 0, 1, 0, 0, 1},
                {1, 0, 1, 0, 1, 0, 1, 0, 1},
                {1, 0, 0, 0, 0, 0, 1, 0, 1},
                {1, 1, 0, 1, 1, 0, 1, 1, 1},
                {1, 0, 0, 0, 0, 0, 0, 0, 0},
                {1, 1, 1, 1, 1, 1, 1, 1, 0}
        };

        searchInMaze(0, 0, maze);
        if (shortestPath.length() != 0)
            System.out.println("最短路线:" + shortestPath);
        else
            System.out.println("没有找到路线!");
    }
}

最短路线:(0,0)->(0,1)->(1,1)->(2,1)->(3,1)->(4,1)->(5,1)->(5,2)->(6,2)->(7,2)->(7,3)->(7,4)->(7,5)->(7,6)->(7,7)->(7,8)->(8,8)

注意:

能不能找到路径时(是否存在某一条路径),需要进行回溯。
查找走过的最大格子数时(也就是路径的长度),不需要进行回溯。
涉及“岛屿”的相关问题,都不需要进行回溯。(200.岛屿的数量,463. 岛屿的周长、695. 岛屿的最大面积) from csdn blog

二。Depth First Search算法(不含back tracking)
岛屿题:
200. Number of Islands

class Solution {
    static final int[][]dirs={{0,1},{0,-1},{1,0},{-1,0}};
    public int numIslands(char[][] grid) {
        int m=grid.length;
        int n=grid[0].length;
        int num=0;
        
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]=='1'){                 
                    dfs(grid,i,j);//infect
                    num++; 
                }
            }
        }
        return num;
    }
    
    void dfs(char[][] grid,int i,int j){
        //base case 
        if(i<0 || i>=grid.length ||
           j<0 || j>=grid[0].length || grid[i][j]!='1'){
            return;
        }
        
        grid[i][j]='0';//标记走过
        for(int[] dir:dirs){
            dfs(grid,i+dir[0],j+dir[1] );//dfs往下一层走
        }
    }
}

BFS solution:

class Solution {
    int[][] dirs={{1,0},{-1,0},{0,1},{0,-1}};
    public int numIslands(char[][] grid) {
        if(grid==null ||grid.length==0) return 0;//bug
       
        int count=0;
        for(int i=0;i<grid.length;i++){
            for(int j=0;j<grid[0].length;j++){
                if(grid[i][j]=='1'){
                    
                   count++;
                   Queue<List<Integer>> q=new LinkedList<>();
                   List<Integer> start=Arrays.asList(i,j);
                    
                   q.offer(start);
                   grid[i][j]='0';
                    
                   while(!q.isEmpty()){
                        int size=q.size();
                        List<Integer> cur=q.poll();
                        for(int s=0;s<size;s++){
                            for(int[] dir:dirs){
                            
                                int nextX=cur.get(0)+dir[0];
                                int nextY=cur.get(1)+dir[1];

                                if(nextX>=0 && nextX<grid.length && nextY>=0 && nextY<grid[0].length && grid[nextX][nextY]=='1'){
                                    q.offer(Arrays.asList(nextX,nextY));
                                    grid[nextX][nextY]='0';
                                }
                            }
                        }
                    }
                }
            }
        }
        return count;
    }     
 }

463. Island Perimeter
推荐:DFS
在这里插入图片描述

public class Solution {
    public static int islandPerimeter(int[][] grid) {
        if(grid.length == 0) {
            return 0;
        }
        int m = grid.length;
        int n = grid[0].length;
        boolean visited[][] = new boolean[m][n];
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(grid[i][j] == 1) {
                    // 题目限制只有一个岛屿,计算一个岛屿即可,即找到一个岛屿后立刻返回即可
                    return dfs(i, j, m, n, grid, visited);
                }
            }
        }
        return 0;
    }

    public int dfs(int i, int j, int m, int n, int grid[][], boolean visited[][]) {
        // 从一个岛屿方格走向网格边界,周长加 1
        if(i < 0 || i >= m || j < 0 || j >= n) {
            return 1;
        }
        // 从一个岛屿方格走向水域方格,周长加 1
        if(grid[i][j] == 0) {
            return 1;
        }
        if(visited[i][j]) {
            return 0;
        }
        //标记访问
        visited[i][j] = true;
        return dfs(i + 1, j, m, n, grid, visited) + dfs(i - 1, j, m, n, grid, visited) + dfs(i, j + 1, m, n, grid, visited) + dfs(i, j - 1, m, n, grid, visited);
    }
}

from csdn blog

数学方法:

class Solution {
    public int islandPerimeter(int[][] grid) {
        if(grid==null || grid.length==0) return 0;
        int m=grid.length;
        int n=grid[0].length;
        int res=0;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]==1){
                    //规定下方有1,右方有1,都-2
                    res+=4;
                    if(i<m-1 && grid[i+1][j]==1){
                        res-=2;
                    }
                    if(j<n-1 && grid[i][j+1]==1){
                        res-=2;
                    }
                }
            }
        }
        return res;
    }
}

695. Max Area of Island

class Solution {
    static int[][] dirs={{0,1},{0,-1},{1,0},{-1,0}};
    public int maxAreaOfIsland(int[][] grid) {
        if(grid==null || grid.length==0) return 0;
        int m=grid.length;
        int n=grid[0].length;
        int res=0;
   
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
              //  if(grid[i][j]==1){
                    res=Math.max(res,dfs(i,j,grid));
               // }
            }
        }
        return res;
    }
    
    public int dfs(int i, int j, int[][] grid) {
        //base case
       if(i<0 || i>=grid.length ||
          j<0 || j>=grid[0].length || grid[i][j]!=1){
            return 0;
        }
        //recursion rule
        int count=1;
        grid[i][j]=0;
        for(int[] dir:dirs){
            count+=dfs(i+dir[0],j+dir[1],grid);
        }
        return count;
    }
}
在这里插入代码片
在这里插入代码片

======================
四。剪枝(算法优化)

在DFS 和 BFS 搜索算法中;剪枝策略就是寻找过滤条件,提前减少不必要的搜索路径。
from csnd blog

例题1:N个城市,编号1到N。城市间有R条单向道路。每条道路连接两个城市,有长度和过路费两个属性。Bob只有K块钱,他想从城市1走到城市N。问最短共需要走多长的路。如果到不了N,输出-1

2<=N<=100
0<K<=10000
1<=R<=10000
每条路的长度 L, 1 <= L <= 100
每条路的过路费T , 0 <= T <= 100
from csnd blog
思路:从城市 1开始深度优先遍历整个图,找到所有能过到达 N 的走法,选一个最优的。

如何剪枝?
1.最优化剪枝:如果当前已经找到的最优路径长度为L ,那么在继续搜索的过程中,总长度已经大于等于L的走法,就可以直接放弃,不用走到底了;
2.可行性剪枝:如果当前到达城市的路费已大于k,或者等于k且没有到达终点,就可以直接放弃。

例题2:maze题中,base case的判断,可以视为提前剪枝。【?】

 public static void searchInMaze(int x, int y, int[][] maze) {
        int m=maze.length;
        int n=maze[0].length;
        //base cases: out of bound, ==1,  //cut the branches,
        if(x<0 || y<0) return;
        if (x > m - 1 || y > n - 1) return;
        if(maze[x][y]==1) return;
        //the destination : m-1,n-1

526. Beautiful Arrangement
提前剪枝:每层符合条件才加入:
keep checking the elements while being added to the permutation array at every step for the divisibility condition and can stop creating it any further as soon as we find out the element just added to the permutation violates the divisiblity condition.

 class Solution {
    int res=0;
    public int countArrangement(int n) {
        
        int[] array=new int[n];
        for(int i=1;i<=n;i++){
            array[i-1]=i;
        }
        dfs(array,0);
        return res;
    }
    public void dfs(int[] array,int index){
        //base case
        if(index==array.length){
            res++;
            //return;不加return速度更快!
        }
        //recur rule + restriction
        for(int i=index;i<array.length;i++){
            swap(array,index,i);
            if(array[index]%(index+1)==0 || (index+1)%array[index]==0){
                //每层(index)当前层符合条件就加入
                dfs(array,index+1);
            }
            swap(array,index,i);
        }
        
    }
    
    public void swap(int[] nums, int x, int y) {
        int temp = nums[x];
        nums[x] = nums[y];
        nums[y] = temp;
    }
    
}

常常碰到这样的问题,由第一个得到的结果,再放入同样的公式中,得到第二个结果,同理,得到第三个结果…
这就是recursion的厉害之处,利用同样的逻辑,一直往下走,俄罗斯套娃一样解题。
797. All Paths From Source to Target
这题要得到所有的路径,那么肯定是DFS套路。
每次触底反弹,则添加一条路径到答案集里。

class Solution {
    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
        List<List<Integer>> res=new ArrayList<>();
        List<Integer> oneres=new ArrayList<>(); 
        oneres.add(0);
        dfs(graph,res,oneres,0);
        return res;
    }
    
    void dfs(int[][] graph,List<List<Integer>> res,List<Integer> oneres,int curnode){
        //base case
        //curNode  nextNode
        if(curnode==graph.length-1){//bug
            res.add(new ArrayList<>(oneres)); 
            return;
        }
        //recursion rule
        for(int next:graph[curnode]){//关系在这里!拿到curnode,然后graph[curnode]才能得到nextNode!!!
            //这里有个recursion,一直往下走
            oneres.add(next);//如何继续调用下一个?答:利用nextnode接下去!!!
            dfs(graph,res,oneres,next);//bug:这里不是index+1,而是下一个nextnode接下去
            oneres.remove(oneres.size()-1);
        }
    }
}

这面这题中,大神的解法,真是太妙了。
1.对称:观察发现1379是对称的,2468对称的,5单独为一个情况。
2.skip:dp的想法设置skip数组。解决了大难题。
skip[i][j]表示从i数到j数之间skip了谁。那么skip[i][j]==0则表示i、j两个数相邻。

skip[1][3] = skip[3][1] = 2;

3.remain:如果从1出发,那么如果m为2,表示有两个数构成密码,那么remain为1步,即站在1,可以任意走一步。
所以主函数中,remain为i-1;

rst += DFS(vis, skip, 1, i - 1) * 4;

351. Android Unlock Patterns

public class Solution {
    public int numberOfPatterns(int m, int n) {
        // Skip array represents number to skip between two pairs
        int skip[][] = new int[10][10];
        skip[1][3] = skip[3][1] = 2;
        skip[1][7] = skip[7][1] = 4;
        skip[3][9] = skip[9][3] = 6;
        skip[7][9] = skip[9][7] = 8;
        skip[1][9] = skip[9][1] = skip[2][8] = skip[8][2] = skip[3][7] = skip[7][3] = skip[4][6] = skip[6][4] = 5;
        boolean vis[] = new boolean[10];
        int rst = 0;
        // DFS search each length from m to n
        for(int i = m; i <= n; ++i) {
            rst += DFS(vis, skip, 1, i - 1) * 4;    // 1, 3, 7, 9 are symmetric
            rst += DFS(vis, skip, 2, i - 1) * 4;    // 2, 4, 6, 8 are symmetric
            rst += DFS(vis, skip, 5, i - 1);        // 5
        }
        return rst;
    }
        // cur: the current position
    // remain: the steps remaining
    int DFS(boolean vis[], int[][] skip, int cur, int remain) {
        if(remain < 0) return 0;
        if(remain == 0) return 1;
        vis[cur] = true;
        int rst = 0;
        for(int i = 1; i <= 9; ++i) {
            // If vis[i] is not visited and (two numbers are adjacent or skip number is already visited)
            if(!vis[i] && (skip[cur][i] == 0 || (vis[skip[cur][i]]))) {
                rst += DFS(vis, skip, i, remain - 1);
            }
        }
        vis[cur] = false;
        return rst;
    }
}

980. Unique Paths III
Input: [[1,0,0,0],[0,0,0,0],[0,0,2,-1]]
Output: 2
1 represents the starting square. There is exactly one starting square.
2 represents the ending square. There is exactly one ending square.
0 represents empty squares we can walk over.
-1 represents obstacles that we cannot walk over.
Return the number of 4-directional walks from the starting square to the ending square, that walk over every non-obstacle square exactly once.
即每个非-1的格子都必须走过。

//1.看到1,就可以作为入口开始走了,默认也是只有一个1;  拿到坐标后记住startx,starty
//2.向四个方向延伸,a.在范围内有效 b. grid[x][y]>=0就算有效行走格子;
//3.走过改成一个-4,这样就算走过了,不重复。
//4.每次结束后dfs(nextx,nexty)往下走一层;
//5.DFS扎到底,碰到#最后一个是2#的时候,就返回,并且count++;
//6.#back tracking# 在合适的格子里将0/2改成-4的过程中,不断查看2是不是出口/最后一个。
否的话,把原来的数字还给那个格子。
//recursion rule
        int temp=grid[x][y];
        dfs();
        grid[x][y]=temp;
class Solution {
    //  1
    // /| | \
    // 0 0 0 0
    ///|\
    //000
    //|
    //2
    static final int[][] dirs={{1,0},{-1,0},{0,-1},{0,1}};
    int m;
    int n;
    int res;
    public int uniquePathsIII(int[][] grid) {
        m=grid.length;
        n=grid[0].length;
         
        //dfs
        int validCell=0;
        int startx=0;
        int starty=0;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]>=0){
                    validCell++;
                }
                if(grid[i][j]==1){
                    //visited[i][j]=true;
                    startx=i;
                    starty=j;
                }
            }
        }
        
        res=0;
        dfs(grid,startx,starty,validCell);
        return res;
    }
    
    public void dfs(int[][] grid,int x,int y,int validCell){
        //base case
        if(grid[x][y]==2 && validCell==1){
            res++;
            return;
        }
        //recursion rule
        int temp=grid[x][y];//bug1
        grid[x][y]=-4;//visited 过
        validCell--;
        
        for(int[]dir:dirs){
            int nextx=x+dir[0];
            int nexty=y+dir[1];
            if(nextx<0 || nextx>=m || nexty<0 || nexty>=n){  //跳过无效区间的
                continue;
            }
            if(grid[nextx][nexty]<0){ //==0 ==2都可以走
                continue;
            }                                                                       
            dfs(grid,nextx,nexty,validCell);
            
        }
         grid[x][y]=temp;//bug2: back tracking
        
    }
}

huahua讲解996题
996. Number of Squareful Arrays

https://github.com/Seanforfun/Algorithm-and-Leetcode/blob/master/leetcode/996

DFS方法:

class Solution {
    private int res;
    public int numSquarefulPerms(int[] A) {
        if(A.length == 1) return 0;
        Arrays.sort(A);
        dfs(A, new ArrayList<Integer>(), new boolean[A.length]);
        return this.res;
    }
    private void dfs(int[] A, List<Integer> temp, boolean[] visited){
        if(temp.size() == A.length){
            if(square(temp.get(temp.size() - 1) + temp.get(temp.size() - 2))) this.res++;
        }else if(temp.size() <= 1 || square(temp.get(temp.size() - 1) + temp.get(temp.size() - 2))){
            for(int i = 0; i < A.length; i++){
                if(visited[i]) continue;                
                if(i > 0 && A[i] == A[i - 1] && !visited[i - 1]) continue;
                temp.add(A[i]);
                visited[i] = true;
                dfs(A, temp, visited);
                visited[i] = false;
                temp.remove(temp.size() - 1);
            }
        }

    }
    private boolean square(int x){
        return x == (int)Math.sqrt(x) * (int)Math.sqrt(x);
    }
}

Graph的方法:

class Solution {
    Map<Integer, Integer> count;
    Map<Integer, List<Integer>> graph;
    public int numSquarefulPerms(int[] A) {
        int N = A.length;
        count = new HashMap();
        graph = new HashMap();

        // count.get(v) : number of v's in A
        for (int x: A)
            count.put(x, count.getOrDefault(x, 0) + 1);

        // graph.get(v) : values w in A for which v + w is a square
        //                (ie., "vw" is an edge)
        for (int x: count.keySet())
            graph.put(x, new ArrayList());

        for (int x: count.keySet())
            for (int y: count.keySet()) {
                int r = (int) (Math.sqrt(x + y) + 0.5);
                if (r * r == x + y)
                    graph.get(x).add(y);
            }

        // Add the number of paths that start at x, for all possible x
        int ans = 0;
        for (int x: count.keySet())
            ans += dfs(x, N - 1);
        return ans;
    }

    public int dfs(int x, int todo) {
        count.put(x, count.get(x) - 1);
        int ans = 1;  // default if todo == 0
        if (todo != 0) {
            ans = 0;
            for (int y: graph.get(x)) if (count.get(y) != 0) {
                ans += dfs(y, todo - 1);
            }
        }
        count.put(x, count.get(x) + 1);
        return ans;
    }
}

======================
五。套路

void dfs(int 当前状态){
//base case 
    if(当前状态为边界状态) {
        记录或输出
        return;
    }
    //recursion rule
    for(i=0;i<n;i++) {       //横向遍历解答树所有子节点
         //扩展出一个子状态。
         修改了全局变量
         if(子状态满足约束条件){
             dfs(子状态)
         }
         恢复全局变量//回溯部分
     }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值