LeetCode——回溯算法(Java)

简介

记录一下自己刷题的历程以及代码。写题过程中参考了 代码随想录的刷题路线。会附上一些个人的思路,如果有错误,可以在评论区提醒一下。

回溯题模板:

class Solution {
    public 主方法(参数) {
        //把递归参数传入递归函数
        recursion(0);
        return ans;
    }

    public void recursion(int n){
        if(达到终止条件) {
            //存放结果
            ans.add(answer);
            return;
        }
        for(循环调用后续的递归){
            //递归前处理,比如把当前数加入答案集合
            recursion(n + 1);
            //递归后处理,回溯,撤销递归前处理的内容
        }
    }
}

[中等] 77. 组合

原题链接

注意每次ans.add()时需要添加nums的深拷贝,而不是单纯把当前nums放入,否则放入的其实是nums数组的引用,最后ans的所有元素实际都指向了同一个数组。

class Solution {
    public List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> ans = new ArrayList<>();
        List<Integer> nums = new ArrayList<>();
        recursion(ans, nums, 1, n, k);
        return ans;
    }

    public void recursion(List<List<Integer>> ans, List<Integer> nums, int p, int n, int k){
        //终止条件
        if(nums.size() == k){
            ans.add(new ArrayList<>(nums));
            return;
        }
        for(; p <= n; p++){
            nums.add(p);
            recursion(ans, nums, p+1, n, k);
            nums.remove(nums.size() - 1);
        }
        return;
    }
}

剪枝操作: k - nums.size()是还需要多少个元素构成一组答案

for(; p <= n - (k - nums.size()) + 1; p++){
            nums.add(p);
            recursion(ans, nums, p+1, n, k);
            nums.remove(nums.size() - 1);
        }

[中等] 216. 组合总和 III

原题链接

sum函数直接用参数替代也可以

class Solution {
    public List<List<Integer>> combinationSum3(int k, int n) {
        List<Integer> nums = new ArrayList<>();
        List<List<Integer>> ans = new ArrayList<>();
        recursion (ans, nums, k, n, 1);
        return ans;
    }

    public void recursion(List<List<Integer>> ans, List<Integer> nums, int k, int n, int p){
        if(nums.size() == k){
            if(sum(nums) == n)
                ans.add(new ArrayList<>(nums));
            return;
        }
        if(sum(nums) < n) {
            for (; p <= 9; p++) {
                nums.add(p);
                recursion(ans, nums, k, n, p + 1);
                nums.remove(nums.size() - 1);
            }
        }
    }

    public int sum(List<Integer> nums){
        int sum = 0;
        for(Integer i: nums){
            sum += i;
        }
        return sum;
    }
}

[中等] 17. 电话号码的字母组合

原题链接

使用StringBuilder做字符串操作

class Solution {
    public List<String> letterCombinations(String digits) {
        StringBuilder sb = new StringBuilder();
        List<String> ans = new ArrayList<>();
        String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
        if(digits.length() == 0) return ans;
        recursion(digits, sb, ans, numString, 0);
        return ans;
    }

    public void recursion(String digits, StringBuilder sb, List<String> ans, String[] numString, int p){
        if(p == digits.length()){
            ans.add(sb.toString());
            return;
        }
        for(int i = 0; i < numString[digits.charAt(p) - '0'].length(); i++) {
            sb.append(numString[digits.charAt(p) - '0'].charAt(i));
            recursion(digits, sb, ans, numString, p + 1);
            sb.deleteCharAt(p);
        }
    }
}

[中等] 39. 组合总和

原题链接

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> ans = new ArrayList<>();
        List<Integer> nums = new ArrayList<>();
        recursion(ans, nums, candidates, target, 0, 0);
        return ans;
    }

    public void recursion(List<List<Integer>> ans, List<Integer> nums, int[] candidates, int target, int sum, int p){
        if(sum > target) return;
        //终止条件
        if(sum == target){
            ans.add(new ArrayList<>(nums));
            return;
        }
        for(int i = p; i < candidates.length; i++){
            nums.add(candidates[i]);
            recursion(ans, nums, candidates, target, sum+candidates[i], i);
            nums.remove(nums.size() - 1);
        }
        return;
    }
}

[中等] 40. 组合总和 II

原题链接

对原数组进行排序,相同的数字顺序存放,对相同数字的使用保证从左到右。比如选择[1,1,6] 中的 [1,6] 作为答案,只会选到第一个1

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        List<List<Integer>> ans = new ArrayList<>();
        List<Integer> nums = new ArrayList<>();
        int[] flag = new int[110];
        recursion(ans, nums, candidates, flag, target, 0, 0);
        return ans;
    }

    public void recursion(List<List<Integer>> ans, List<Integer> nums, int[] candidates, int[] flag, int target, int sum, int p){
        if(sum > target) return;
        //终止条件
        if(sum == target){
            ans.add(new ArrayList<>(nums));
            return;
        }
        for(int i = p; i < candidates.length; i++){
            //存在与之相同的前一个数,且前一个数还没被取过,则不会取当前数
            if(i>0 && flag[i-1] == 0 && candidates[i-1] == candidates[i]) continue;
            nums.add(candidates[i]);
            flag[i] = 1;
            recursion(ans, nums, candidates, flag, target, sum+candidates[i], i+1);
            flag[i] = 0;
            nums.remove(nums.size() - 1);
        }
        return;
    }
}

[中等] 131. 分割回文串

原题链接

这个递归自己想还想了挺久的,就是从左往右切割,终止条件就是判断有没有切到最右端
在这里插入图片描述

class Solution {
    public List<List<String>> partition(String s) {
        List<List<String>> ans = new ArrayList<>();
        List<String> group = new ArrayList<>();
        if(s == null || s.length() == 0) return ans;
        recursion(ans, group, s, 0);
        return ans;
    }

    private void recursion(List<List<String>> ans, List<String> group, String s, int start) {
        if(start == s.length()) {
            ans.add(new ArrayList<>(group));
            return;
        }
        for(int i = start; i < s.length(); i++) {
            if(isPalindrome(s, start, i)) {
                group.add(s.substring(start, i + 1));
                recursion(ans, group, s, i + 1);
                group.remove(group.size() - 1);
            }
        }
    }

    private boolean isPalindrome(String s, int begin, int end) {
        while(begin < end) {
            if(s.charAt(begin++) != s.charAt(end--)) return false;
        }
        return true;
    }
}

[中等] 93. 复原 IP 地址

原题链接

注意分割段到4时也需要return,往后的递归都是无意义的

class Solution {
    public List<String> restoreIpAddresses(String s) {
        List<String> ans = new ArrayList<>();
        List<String> answer = new ArrayList<>();
        if(s == null || s.length() == 0) return ans;
        //p表示目前划分到第几段,ip地址总共四段, index表示s下标
        recursion(ans, s, 0, answer);
        return ans;
    }

    public void recursion(List<String> ans, String s, int index, List<String> answer){
        if(answer.size() == 4 && index == s.length()){
            ans.add(createAnswer(answer));
        }
        if(index == s.length() || answer.size() == 4) return;
        if(s.charAt(index) == '0'){
            answer.add("0");
            recursion(ans, s, index + 1, answer);
            answer.remove(answer.size() - 1);
        }else {
            for (int i = index; i < s.length() && i - index <= 3; i++) {
                String string = s.substring(index, i + 1);
                int num = Integer.parseInt(string);
                if (num >= 0 && num <= 255){
                    answer.add(string);
                    recursion(ans, s, i + 1, answer);
                    answer.remove(answer.size() - 1);
                }
            }
        }
    }

    public String createAnswer(List<String> answer){
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < answer.size(); i++){
            if(i != 0){
                sb.append(".");
            }
            sb.append(answer.get(i));
        }
        return sb.toString();
    }
}

[中等] 78. 子集

原题链接

一道标准的子集问题模板题,子集是收集树形结构中树的所有节点的结果。
而组合问题、分割问题是收集树形结构中叶子节点的结果。子集问题不需要在找到一组答案之后就进行回退。

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> ans = new ArrayList<>();
        List<Integer> answer = new ArrayList<>();
        recursion(ans, answer, nums, 0);
        return ans;
    }

    public void recursion(List<List<Integer>> ans, List<Integer> answer, int[] nums, int index){
        ans.add(new ArrayList<>(answer));
        for(int i = index; i < nums.length; i++){
            answer.add(nums[i]);
            recursion(ans, answer, nums, i + 1);
            answer.remove(answer.size() - 1);
        }
    }
}

[中等] 90. 子集 II

原题链接

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        List<List<Integer>> ans = new ArrayList<>();
        List<Integer> answer = new ArrayList<>();
        boolean[] used = new boolean[nums.length];
        Arrays.sort(nums);
        recursion(ans, answer, used, nums, 0);
        return ans;
    }

    public void recursion(List<List<Integer>> ans, List<Integer> answer, boolean[] used, int[] nums, int index){
        ans.add(new ArrayList<>(answer));
        for(int i = index; i < nums.length; i++){
            if(i > 0 && nums[i] == nums[i - 1] && !used[i - 1]){
                continue;
            }
            used[i] = true;
            answer.add(nums[i]);
            recursion(ans, answer, used, nums, i + 1);
            answer.remove(answer.size() - 1);
            used[i] = false;
        }
    }
}

[中等] 491. 非递减子序列

原题链接

不能简单的强制类似[1,1,1,1,1]序列从左到右选,这道题不能排序,有可能会有[1,1,2,1,1],这样2之前的1没取的时候2后面的1也可以取,需要用集合set来做去重

class Solution {
    public List<List<Integer>> findSubsequences(int[] nums) {
        List<List<Integer>> ans = new ArrayList<>();
        List<Integer> answer = new ArrayList<>();
        recursion(ans, answer, nums, 0);
        return ans;
    }

    public void recursion(List<List<Integer>> ans, List<Integer> answer, int[] nums, int index){
        if(answer.size() > 1)
            ans.add(new ArrayList<>(answer));
        HashSet<Integer> hs = new HashSet<>();
        for(int i = index; i < nums.length; i++){
            if(hs.contains(nums[i]) || (answer.size() > 0 && nums[i] < answer.get(answer.size() - 1)))
                continue;
            hs.add(nums[i]);
            answer.add(nums[i]);
            recursion(ans, answer, nums, i + 1);
            answer.remove(answer.size() - 1);
        }
    }
}

[中等] 46. 全排列

原题链接

开一个数组标记不重复取数,每次循环都从头开始即可

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> ans = new ArrayList<>();
        List<Integer> answer = new ArrayList<>();
        boolean[] used = new boolean[nums.length];
        recursion(ans, answer, used, nums);
        return ans;
    }

    public void recursion(List<List<Integer>> ans, List<Integer> answer, boolean[] used, int[] nums){
        if(answer.size() == nums.length) {
            ans.add(new ArrayList<>(answer));
            return;
        }
        for(int i = 0; i < nums.length; i++){
            if(used[i]) continue;
            used[i] = true;
            answer.add(nums[i]);
            recursion(ans, answer, used, nums);
            answer.remove(answer.size() - 1);
            used[i] = false;
        }
    }
}

[中等] 47. 全排列 II

原题链接

对数组排序,并对相同数字,保证从左到右取数,同时不重复取数

class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> ans = new ArrayList<>();
        List<Integer> answer = new ArrayList<>();
        boolean[] used = new boolean[nums.length];
        Arrays.sort(nums);
        recursion(ans, answer, used, nums);
        return ans;
    }

    public void recursion(List<List<Integer>> ans, List<Integer> answer, boolean[] used, int[] nums){
        if(answer.size() == nums.length) {
            ans.add(new ArrayList<>(answer));
            return;
        }
        for(int i = 0; i < nums.length; i++){
            if(used[i]) continue;
            if(i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue;
            used[i] = true;
            answer.add(nums[i]);
            recursion(ans, answer, used, nums);
            answer.remove(answer.size() - 1);
            used[i] = false;
        }
    }
}

[困难] 51. N 皇后

原题链接

class Solution {
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> ans = new ArrayList<>();
        //初始化棋盘,声明一个flag数组,标记每一行填充的元素在哪一列,初始化为-1
        int[] flag = new int[n];
        for(int i = 0; i < n; i++){
            flag[i] = -1;
        }
        recursion(ans, flag, 0);
        return ans;
    }

    public void recursion(List<List<String>> ans, int[] flag, int row){
        if(row == flag.length){
            ans.add(createAnswer(flag));
            return;
        }
        for(int i = 0; i < flag.length; i++){
            if(isFree( flag, row, i)){
                flag[row] = i;
                recursion(ans, flag, row + 1);
                flag[row] = -1;
            }
        }
    }

    //可否放置皇后棋子的判定
    public boolean isFree(int[] flag, int row, int p){
        //对角线判定 以及 列未判定
        for(int i = 0; i < row; i++ ) {
            if(flag[i] == p) return false;
            if (flag[i] - i == p - row || flag[i] + i == row + p) {
                return false;
            }
        }
        return true;
    }

    //创建答案
    public List<String> createAnswer(int[] flag) {
        List<String> answer = new ArrayList<>();
        for (int i = 0; i < flag.length; i++) {
            StringBuilder sb = new StringBuilder();
            for (int j = 0; j < flag.length; j++) {
                if (flag[i] == j) {
                    sb.append("Q");
                } else {
                    sb.append(".");
                }
            }
            answer.add(sb.toString());
        }
        return answer;
    }
}

[困难] 37. 解数独

原题链接

代码随想录给到的是一种暴力二维搜索,是比较容易想到的,每次递归都去判断行、列、九宫格里是否重复,这样效率会比较低,可以牺牲空间来简化这部分的判断,分别开设三个二维数组,记录每一行每一列每一个九宫格,1-9的数字是否被使用过,后续比较直接从数组里去读取信息就可以。

class Solution {
    public void solveSudoku(char[][] board) {
        //行、列、九宫格是否被占用
        boolean[][] rowOccupy = new boolean[9][9];
        boolean[][] colOccupy = new boolean[9][9];
        boolean[][] nineOccupy = new boolean[9][9];
        char a = board[1][2];
        for(int i = 0; i < 9; i++){
            for(int j = 0; j < 9; j++){
                char ch = board[i][j];
                if(ch != '.') {
                    rowOccupy[i][ch - '1'] = true;
                    colOccupy[j][ch - '1'] = true;
                    nineOccupy[i / 3 * 3 + j / 3][ch - '1'] = true;
                }
            }
        }
        recursion(rowOccupy, colOccupy, nineOccupy, board, 0, 0);
    }

    public boolean recursion(boolean[][] rowOccupy, boolean[][] colOccupy, boolean[][] nineOccupy, char[][]board, int i, int j){
        if(i > 8 || j > 8) return true;
        if(board[i][j] != '.'){
            if(j == 8) {
                if (recursion(rowOccupy, colOccupy, nineOccupy, board, i + 1, 0)) {
                    return true;
                }
            }
            else if (recursion(rowOccupy, colOccupy, nineOccupy, board, i, j + 1)) {
                return true;
            }
        }else {
            for (int k = 1; k <= 9; k++) {
                if (!rowOccupy[i][k - 1] && !colOccupy[j][k - 1] && !nineOccupy[i / 3 * 3 + j / 3][k - 1]) {
                    board[i][j] = String.valueOf(k).charAt(0);
                    rowOccupy[i][k - 1] = true;
                    colOccupy[j][k - 1] = true;
                    nineOccupy[i / 3 * 3 + j / 3][k - 1] = true;
                    if (j == 8) {
                        if (recursion(rowOccupy, colOccupy, nineOccupy, board, i + 1, 0)) {
                            return true;
                        }
                    }
                    else if (recursion(rowOccupy, colOccupy, nineOccupy, board, i, j + 1)) {
                        return true;
                    }
                    board[i][j] = '.';
                    rowOccupy[i][k - 1] = false;
                    colOccupy[j][k - 1] = false;
                    nineOccupy[i / 3 * 3 + j / 3][k - 1] = false;
                }
            }
        }
        return false;
    }
}
  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值