回溯专题leetcode

这期刷leetcode上所有的回溯专题

93. 复原IP地址

在这里插入图片描述

这题先不考虑一些剪枝类问题,只是单纯考虑AC,我们设置的两个参数,一个index表示索引到哪个字符串下标了,另一个strs双端队列放入表示合法的字符串(0-255)

class Solution {
    int[] segment = new int[4];
    List<String> res;
    public List<String> restoreIpAddresses(String s) {
        res = new ArrayList<>();
        char[] ch = s.toCharArray();
        dfs(ch, 0, 0);
        return res;
    }

    public void dfs(char[] ch, int segId, int index){
        if(segId == 4){
            if(index == ch.length){
                StringBuilder sb = new StringBuilder();
                for(int i = 0; i<4; i++){
                    sb.append(segment[i]);
                    if(i < 3) sb.append(".");
                }
                res.add(new String(sb));
            }
            return;
        }
        if(index == ch.length) return;
        if(ch[index] == '0'){
            segment[segId] = 0;
            dfs(ch, segId+1, index+1);
        }
        int sum = 0;
        for(int i = index; i<ch.length; i++){
            sum = sum * 10 + ch[i]-'0';
            if(sum > 0 && sum < 256){
                segment[segId] = sum;
                dfs(ch, segId+1, i+1);
            }else break;
        }
    }
}

131. 分割回文串
在这里插入图片描述

这里的思路和上题差不多,但是坑比较多,我太菜的原因,先看代码:

class Solution {

    public List<List<String>> partition(String s) {
        List<List<String>> res = new ArrayList<>();
        if(s.length() == 0) return res;
        helper(s,0,new ArrayList<>(),res);
        return res;
    }
    public boolean isPalindrome(String str){
        int start = 0;
        int end = str.length()-1;
        while(end > start){
            if(str.charAt(start) != str.charAt(end)) return false;
            end--;
            start++;
        }
        return true;
    }
    public void helper(String s, int index, List<String> r, List<List<String>> res){
        if(index == s.length()) {
            res.add(new ArrayList<>(r));  // 这里不能直接res.add(r),否则r改变后,res里面随之改变
            return;
        }
        for(int i = 1; i+index<=s.length(); i++){
            String choose = s.substring(index,index+i);
            if(isPalindrome(choose)){
                r.add(choose);
                helper(s,index+i,r,res);
                r.remove(r.size()-1);
            }
            else continue;  // 坑二
        }
        return;
    }

坑一: List列表的删除会影响到已经加到res里面的列表
坑二:该位置之前写的return,直接影响到数组后面元素的添加,比如说 aba,我添加到ab时显示的是非回文字符,return之后就没有继续下去考虑aba的情况,所以此处用continue


排列问题,这类问题在面试中会常问到

  1. 全排列
    在这里插入图片描述
class Solution {

    List<List<Integer>> res = new ArrayList<>();
    int[] visited;
    public List<List<Integer>> permute(int[] nums) {
        if(nums.length == 0) return res;
        visited = new int[nums.length];
        helper(nums, new LinkedList<>(),0);
        return res;
    }
    public void helper(int []num, Deque<Integer> t, int index){
        if(index == num.length){
            res.add(new ArrayList<>(t));
            return ;
        }
        if(index > num.length) return;
        for(int i = 0; i < num.length; i++){
            if(visited[i] == 1) continue; 
            visited[i] = 1;
            t.offerLast(num[i]);
            helper(num,t,index+1);
            visited[i] = 0;
            t.pollLast();
        }
        return;
    }
}

组合问题

在这里插入图片描述

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        if(k > n || n == 0 || k == 0) return res;
        helper(n,k,0,new ArrayList<>());
        return res;
    }
    public void helper(int n, int k, int index, List<Integer>list){
        if(list.size() == k){
            res.add(new ArrayList<>(list));
            return;
        }
        for(int i = index; i<n; i++){
            list.add(i+1);
            helper(n,k,i+1,list); // i+1 表示用过的元素,后面不用了,这里不能用index+1
            list.remove(list.size()-1);
        }
        return;
    }
}

全套组合总和问题

组合总和 I

在这里插入图片描述

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        if(candidates.length == 0) return res;
        helper(candidates,target,new ArrayList<>(),0,0);
        return res;
    }
    public void helper(int [] candidates, int target, List<Integer> list, int sum, int index){
        if(sum == target){
            res.add(new ArrayList<>(list));
            return;
        }
        if(sum > target) return;
        for(int i = index; i < candidates.length; i++){
            list.add(candidates[i]);
            sum += candidates[i];
            helper(candidates,target,list,sum, i);
            sum -= candidates[i];
            list.remove(list.size()-1);
        }
        return;
    }
}

组合总和这个,这个必须消化,这是一个模板了:组合总和 II

在这里插入图片描述

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        if(candidates.length == 0) return res;
        helper(candidates,target,new ArrayList<>(),0,0);
        return res;
    }
    public void helper(int [] candidates, int target, List<Integer> list, int sum, int index){
        if(sum == target){
            res.add(new ArrayList<>(list));
            return;
        }
        if(sum > target) return;
        for(int i = index; i < candidates.length; i++){
            if(i > index && candidates[i] == candidates[i-1]) continue;
            list.add(candidates[i]);
            sum += candidates[i];
            helper(candidates,target,list,sum, i+1);
            sum -= candidates[i];
            list.remove(list.size()-1);
        }
        return;
    }
}

继续延伸到 组合总和 III,将组合问题贯彻到底

在这里插入图片描述

class Solution {
    public List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combinationSum3(int k, int n) {
        if(k == 0 || n < 1) return res;
        helper(k,n,new ArrayList<>(),0,1);
        return res;
    }
    public void helper(int k, int n, List<Integer> list, int sum, int index){
        if(k == list.size() && sum == n){
            res.add(new ArrayList<>(list));
            return;
        }
        if(sum > n) return;
        if(index > 9) return;
        for(int i = index; i<=9; i++){
            sum += i;
            list.add(i);
            helper(k,n,list,sum,i+1);
            sum -= i;
            list.remove(list.size()-1);
        }
        return;
    }
}

组合问题 IV(超时了,需要剪枝)

在这里插入图片描述

class Solution {
    int res = 0;
    public int combinationSum4(int[] nums, int target) {
        if(nums.length == 0) return res;
        helper(nums, target, 0);
        return res;
    }
    public void helper(int []nums, int target, int sum){
        if(sum == target){
            res += 1;
            return;
        }
        if(sum > target) return;
        for(int i = 0; i<nums.length; i++){
            sum += nums[i];
            if(sum > target) return;
            helper(nums,target,sum);
            sum -= nums[i];
        }
        return;
    }
}

记忆化搜索AC:

class Solution {
    int[] memo;
    public int combinationSum4(int[] nums, int target) {
        int len = nums.length;
        if(len == 0) return 0;
        memo = new int[target+1];
        Arrays.fill(memo, -1);
        return helper(nums, target);
    }
    public int helper(int[] nums, int target){
        if(target < 0) return 0;
        if(target == 0) return 1;
        if(memo[target] != -1) return memo[target];
        int sum = 0;
        for(int i = 0; i<nums.length; i++) sum += helper(nums, target-nums[i]);
        memo[target]= sum;
        return sum;
    }
}

这里再补充一个 用背包解决的,这里显然是一个完全背包问题:


357. 计算各个位数不同的数字个数

在这里插入图片描述
这道题目,我看题解大部分都是用动态规划做的,但是由于是回溯专题,强行用回溯做了 ,结果可想而知:
在这里插入图片描述

class Solution {
    
    int res = 0;
    public int countNumbersWithUniqueDigits(int n) {
       if(n == 0) return 1;
       if(n == 1) return 10;
       helper(n, new ArrayList<>());
       return res;
    }
    public void helper(int n, List<Integer> list){
        if(list.size() > 1 && list.get(0) == 0) return;
        if(list.size() > n) return;
        if(list.size() > 0){
            res += 1;  // 这里有一个坑,习惯性在后面写个return; 结果直接不考虑后面的情况,只输出一位数的情况
        }
        for(int i = 0; i < 10; i++){
            if(list.contains(i)) continue;
            list.add(i);
            helper(n, list);
            list.remove(list.size()-1);
        }
        return;
    }
}

子集 II


在这里插入图片描述

class Solution {
    public List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        if(nums.length == 0) return res;
        helper(nums, 0, new ArrayList<>(),new HashMap<>());
        return res;
    }
    public void helper(int[] nums, int index, List<Integer> list, HashMap<List<Integer>,Integer> map){
        if(index > nums.length) return;
        if(list.size() > nums.length) return;
        List<Integer> temp = new ArrayList<>(list);
        Collections.sort(temp);
        if(map.get(temp) == null){
            res.add(new ArrayList<>(temp));
            map.put(temp,0);
        }
        for(int i = index; i<nums.length; i++){
            list.add(nums[i]);
            helper(nums, i+1, list, map);
            list.remove(list.size()-1);
        }
        return;
    }
}

二维平面中的回溯算法

单词搜索
在这里插入图片描述

class Solution {
    public boolean exist(char[][] board, String word) {
        if(word.length() == 0) return false;
        for(int i = 0; i<board.length; i++){
            for(int j = 0; j<board[0].length; j++){
                if(helper(board,word,i,j,0)) return true;
            }
        }
        return false;
    }
    public boolean helper(char[][] board, String word, int x, int y, int index){
        if(index == word.length()){  // 没有考虑边界问题,看下面代码
            return true;
        }
        if(board[x][y] != word.charAt(index)) return false;
        board[x][y] = '1';  // 这个相当于以前常用的 visited[] = true,表明这个点已经被访问过了
        if(x+1<board.length && helper(board, word, x+1, y, index+1)) return true;
        if(x-1>=0 && helper(board, word, x-1, y, index+1)) return true;
        if(y-1>=0 && helper(board, word, x, y-1, index+1)) return true;
        if(y+1<board[0].length && helper(board, word, x, y+1, index+1)) return true;
        board[x][y] = word.charAt(index);  // 还原
        return false;
    }
}

上面的代码没有考虑到边界问题,碰到一个元素的情况,就gg了,改成下面这种:

class Solution {
    public boolean exist(char[][] board, String word) {
        if(word.length() == 0) return false;
        for(int i = 0; i<board.length; i++){
            for(int j = 0; j<board[0].length; j++){
                if(helper(board,word,i,j,0)) return true;
            }
        }
        return false;
    }
    public boolean helper(char[][] board, String word, int x, int y, int index){
        if(index == word.length()-1){  // 这里是重点
            return board[x][y] == word.charAt(index);
        }
        if(board[x][y] != word.charAt(index)) return false;
        board[x][y] = '1';
        if(x+1<board.length && helper(board, word, x+1, y, index+1)) return true;
        if(x-1>=0 && helper(board, word, x-1, y, index+1)) return true;
        if(y-1>=0 && helper(board, word, x, y-1, index+1)) return true;
        if(y+1<board[0].length && helper(board, word, x, y+1, index+1)) return true;
        board[x][y] = word.charAt(index);
        return false;
    }
}

接下来是floodfill类型的题目,也是二维平面的回溯:

岛屿数量
在这里插入图片描述

class Solution {
    int res = 0;
    int [][]direct = {{0,1},{0,-1},{1,0},{-1,0}};
    public int numIslands(char[][] grid) {
        if(grid.length == 0) return 0;
        for(int i = 0; i<grid.length; i++){
            for(int j = 0; j<grid[0].length; j++){
                if(grid[i][j] == '1'){
                    res ++;
                    helper(grid,i,j);
                }
            }
        }
        return res;
    }
    public void helper(char[][] grid, int x, int y){
        grid[x][y] = '2';   // 不用 visited数组,节省空间
        for(int i = 0; i<4; i++){
            int new_x = x + direct[i][0];
            int new_y = y + direct[i][1];
            if(inArea(new_x,new_y,grid) && grid[new_x][new_y] == '1'){
                helper(grid,new_x,new_y);
            }
        }

    }
    public boolean inArea(int x, int y, char[][] grid){
        if(x<grid.length && x>=0 && y<grid[0].length && y>=0) return true;
        return false;
    }
}

130. 被围绕的区域
在这里插入图片描述

class Solution {
    int [][] direct = {{1,0},{-1,0},{0,1},{0,-1}};
    int [][]visited;
    public void solve(char[][] board) {
        if(board.length == 0 || board[0].length == 0) return;
        visited = new int[board.length][board[0].length];
        for(int i = 0; i<board.length; i++){
            if(board[i][0] == 'O' && visited[i][0] == 0)
                helper(board,i,0);
            if(board[i][board[0].length-1] == 'O' && visited[i][board[0].length-1] == 0)
                helper(board,i,board[0].length-1);
        }
        for(int i = 0; i<board[0].length; i++){
            if(board[0][i] == 'O' && visited[0][i] == 0)
                helper(board,0,i);
            if(board[board.length-1][i] == 'O' && visited[board.length-1][i] == 0)
                helper(board,board.length-1,i);
        }
        for(int i = 0; i < board.length; i++)
        for(int j = 0; j<board[0].length; j++){
            if(board[i][j] == 'O') board[i][j] = 'X';
            if(board[i][j] == 'B') board[i][j] = 'O';
        }

    }
    public void helper(char[][] board, int x, int y){
        visited[x][y] = 1;
        board[x][y] = 'B';
        for(int i = 0; i<4; i++){
            int new_x = x + direct[i][0];
            int new_y = y + direct[i][1];
            if(inArea(new_x,new_y, board) && board[new_x][new_y] == 'O' && visited[new_x][new_y] == 0)
                helper(board,new_x,new_y);
        }
    }
    public boolean inArea(int x, int y, char[][] board){
        if(x >= 0 && x < board.length && y >= 0 && y < board[0].length) return true;
        else return false;
    }
}

思想: 首先对所有的边界为 “O” 的坐标进行遍历,对O位置以及与O位置相连的坐标全都标注为“B”,对于剩下的坐标为“O”
的,肯定是被包围的,全都设为“X”,再把之前标注为B的,全都还原为O


417. 太平洋大西洋水流问题

在这里插入图片描述

class Solution {
    public List<List<Integer>> pacificAtlantic(int[][] matrix) {
        List<List<Integer>> res = new ArrayList<>();
        if(matrix == null || matrix.length == 0){
            return res;
        }
        for(int i=0; i<matrix.length; i++){
            for(int j=0; j<matrix[0].length; j++){
                if(dfsPacific(matrix,i,j,Long.MAX_VALUE) 
                        && dfsAtlantic(matrix,i,j,Long.MAX_VALUE)){
                    res.add(Arrays.asList(i,j));
                }
            }
        }
        return res;
    }

    //“太平洋”处于大陆的左边界和上边界
    private boolean dfsPacific(int[][] matrix, int i, int j, long prev){
        if(i > matrix.length-1 || j > matrix[0].length-1 || matrix[i][j] == -1 || matrix[i][j] > prev){
            return false;
        }
        //到了太平洋的边界,说明可以流入,返回true
        if(i <= 0 || j <= 0){
            return true;
        }
        //temp临时记录当前matrix[i][j]的值,返回重复遍历
        int temp = matrix[i][j];
        matrix[i][j] = -1;
        boolean result = dfsPacific(matrix,i-1,j,temp)
                || dfsPacific(matrix,i+1,j,temp)
                || dfsPacific(matrix,i,j-1,temp)
                || dfsPacific(matrix,i,j+1,temp);
        matrix[i][j] = temp;
        return result;
    }

    //“大西洋”处于大陆的右边界和下边界
    private boolean dfsAtlantic(int[][] matrix, int i, int j, long prev){
        if(i < 0 || j < 0 || matrix[i][j] == -1 ||  matrix[i][j] > prev){
            return false;
        }
        //到了大西洋的边界,说明可以流入,返回true
        if(i >= matrix.length-1 || j >= matrix[0].length-1){
            return true;
        }
        //temp临时记录当前matrix[i][j]的值,返回重复遍历
        int temp = matrix[i][j];
        matrix[i][j] = -1;
        boolean result = dfsAtlantic(matrix,i-1,j,temp)
                || dfsAtlantic(matrix,i+1,j,temp)
                || dfsAtlantic(matrix,i,j-1,temp)
                || dfsAtlantic(matrix,i,j+1,temp);
        matrix[i][j] = temp;
        return result;
    }
}

作者:YanShaoJiangHu
链接:https://leetcode-cn.com/problems/pacific-atlantic-water-flow/solution/shen-du-you-xian-sou-suo-jian-dan-yi-dong-by-yansh/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这个问题我的几次答案都是stack溢出,题解里面好多题主提到了上面的floodfill方法,需要反复试验一下


在这里插入图片描述
这道题wr一直没找到原因,直到在lc评论区看到,原来少了点什么

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        if(root == null) return res;
        helper(root, sum, new ArrayList<>());
        return res;
    }
    public void helper(TreeNode root, int sum, List<Integer> list){
        if(root == null) return;
        if(root.left == null && root.right == null && root.val == sum){
            list.add(root.val);
            res.add(new ArrayList<>(list));
            list.remove(list.size()-1); // 这里一定要注意,一定要在return之前要删除之前加进的元素
            return;
        }
        list.add(root.val);
        helper(root.left, sum-root.val, list);
        helper(root.right, sum-root.val, list);
        list.remove(list.size()-1);   // 这里也要
        return;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值