【面试必刷101】递归/回溯算法总结II(十分钟刷爆回溯算法题)


本文是【面试必刷101总结系列】之回溯算法第二篇文章,主要介绍牛客网面试必刷TOP101上我认为比较好的题目的题解,有助于理解回溯法框架和理解回溯法在实战中的应用。以下将分为 TOP101习题和经典问题求解两个模块分别介绍。

1 TOP101习题题解

1.1 有重复数字的全排列问题

没有重复数字的全排列、子集问题在第一篇博客中已经有非常详细的介绍了,加大点难度。有重复数字相当于解集是没有重复数字解空间的子集。因此需要特别注意重复导致的判重,可以通过set来做,也可以通过判定条件来限制。
有重复数字的全排列问题

import java.util.*;
 
public class Solution {
    ArrayList<ArrayList<Integer>> res = new ArrayList<>();
    public ArrayList<ArrayList<Integer>> permuteUnique(int[] num) {
        Arrays.sort(num);
        Boolean[] visit = new Boolean[num.length];
        Arrays.fill(visit, false);
        ArrayList<Integer> tmp = new ArrayList<>();
        recursion(num, tmp, visit);
        return res;
    }
     
    public void recursion(int[] num, ArrayList<Integer> tmp, Boolean[] visit) {
        if (tmp.size() == num.length) {
            res.add(new ArrayList<Integer>(tmp));
            return;
        }
        for (int i = 0; i < num.length; i++) {
            if (visit[i]) {
                continue;
            }
            // 上一个元素没被访问过就要跳出来,因为会出现重复的内容
            if (i > 0 && num[i - 1] == num[i] && !visit[i - 1]) {
                continue;
            }
            // 标记为使用过
            visit[i] = true;
            tmp.add(num[i]);
             
            recursion(num, tmp, visit);
             
            // 回溯
            visit[i] = false;
            tmp.remove(tmp.size() - 1);
        }
    }
}

就是通过添加判断条件来避免重复,但是整体框架还是回溯的框架。

1.2 有重复数字的子集问题

NC221 集合的所有子集(二)

import java.util.*;


public class Solution {
    ArrayList<ArrayList<Integer>> res = new ArrayList<>();
    public ArrayList<ArrayList<Integer>> subsets (int[] nums) {
        // write code here
        
        Arrays.sort(nums);
        dfs(nums, new ArrayList<Integer>(), 0);
        return res;
    }
    
    public void dfs(int []nums,ArrayList<Integer> temp,int index){
        // 当前加入结果集中
        res.add(new ArrayList<>(temp));
        for(int i=index;i<nums.length;i++){
            // 剪枝
            if(i>index && nums[i] == nums[i-1])
                continue;
            temp.add(nums[i]);
            dfs(nums,temp,i+1);
            temp.remove(temp.size()-1);
        }
    }
}

1.3 岛屿数量

岛屿数量
题目很经典,需要注意的地方有:1、主函数的n*m的遍历;2、添加二维的数组判别哪些被访问了;3、返回值,因为一个入口进去可以扩展一片区域,因此返回1就好了,不符合条件的返回0就好了。(这里需要理解一下,其实入口进去之后,tag数组就记录了该入口能到达的所有地点,同一个岛屿不同的入口再次进入,返回值就是0了)

import java.util.*;
 
public class Solution {
    int row;
    int col;
    public int solve (char[][] grid) {
        row = grid.length;
        col = grid[0].length;
        boolean[][] tag = new boolean[row][col];
        int res = 0;
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                res += findIland(grid, i, j, tag);
            }
        }
        return res;
    }
     
    public int findIland(char[][] grid, int i, int j, boolean[][] tag) {
        if (i < 0 || i >= row || j < 0 || j >= col) {
            return 0;
        }
        if (grid[i][j] == '0' || tag[i][j]) {
            return 0;
        }
        tag[i][j] = true;
        findIland(grid, i + 1, j, tag);
        findIland(grid, i, j + 1, tag);
        findIland(grid, i - 1, j, tag);
        findIland(grid, i, j - 1, tag);
        return 1;
    }
}

1.4 括号问题

括号生成
这题激动地我花了张图,虽然我的算法慢得很,但是我觉得这题的官方题解花里胡哨的,没我的好理解。
在这里插入图片描述
我的方法就是插入法,一次插入一个()。在哪插入呢?有空隙就插进去就完事了。会不会出现不合格的括号呢?不会,插入之前的已经是合格的,再插入一个合格的,还是合格的。问题在于会出现重复值,而且会出现大量重复计算,不管了,先写代码。

import java.util.*;
 
public class Solution {
    ArrayList<String> res = new ArrayList<String>();
    Set<String> set = new HashSet<String>();
    public ArrayList<String> generateParenthesis (int n) {
        // 一对括号只能插入到左右两边或者已经有的一对括号内部
        StringBuilder sb = new StringBuilder();
        insert(n, sb);
        for (String item : set) {
            res.add(item);
        }
        return res;
    }
     
    public void insert(int n, StringBuilder sb) {
        if (n <= 0) {
            set.add(sb.toString());
            return;
        }
         
        sb.insert(0, "()");
        insert(n - 1, sb);
        sb.delete(0, 2);
        int size = sb.length();
        for (int i = 1; i < size; i++) {
            sb.insert(i, "()");
            insert(n - 1, sb);
            sb.delete(i, i + 2);
        }
         
        sb.append("()");
        insert(n - 1, sb);
        sb.delete(sb.length() - 2, sb.length());
    }
}

很符合框架,但是有点慢。

1.5 矩阵最长递增路径

矩阵最长递增路径
解法与上面那道岛屿数量题一样呀,但是这题可以用dp来优化,也很好理解,过去走过的路不用再走一遍。

import java.util.*;
 
 
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 递增路径的最大长度
     * @param matrix int整型二维数组 描述矩阵的每个数
     * @return int整型
     */
    int max = 0;
    public int solve (int[][] matrix) {
        // write code here
        int row = matrix.length;
        int col = matrix[0].length;
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                find(matrix, i, j, -1, 0);
            }
        }
        return max;
    }
     
    public void find (int[][] matrix, int i, int j, int v, int cnt) {
        if (i < 0 || i >= matrix.length || j < 0 || j >= matrix[0].length) {
            return;
        }
        if (matrix[i][j] <= v) {
            return;
        }
        cnt ++;
        max = Math.max(max, cnt);
         
        int tmp = matrix[i][j];
        find(matrix, i + 1, j, tmp, cnt);
        find(matrix, i - 1, j, tmp, cnt);
        find(matrix, i, j + 1, tmp, cnt);
        find(matrix, i, j - 1, tmp, cnt);
    }
}

2 经典问题求解

2.1 N皇后问题

这是一种非递归实现的算法,其实还有递归版本,但是这种非递归的版本比较精妙。这个i值代表层数,第二个while循环内代表去找i行放置皇后的位置。精妙。

import java.util.*;

public class Solution {
    int cnt = 0;
    int[] q = new int[10]; // 用于记录i行的皇后放置的列数
    public int Nqueen (int n) {
        // write code here
        int i = 1;
        q[i] = 0;
        while (i >= 1) {
            q[i] ++;
            while (q[i] <= n && !place(i)) {
                q[i] ++;
            }
            if (q[i] <= n) {
                if (i == n) {
                    cnt++;
                } else {
                    i++;
                    q[i] = 0;
                }
            } else {
                i--;
            }
        }
        return cnt;
    }
     
    boolean place (int i) {
        int j = 1;
        if (i == 1) return true;
        while (j < i) {
            if (q[j] == q[i] || (Math.abs(q[i] - q[j]) == Math.abs(i - j))){
                return false;
            }
            j++;
        }
        return true;
    }
}

在这里插入图片描述
在这里插入图片描述
递归版下次再抽时间写写,

总结

写博客挺累的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值