基础算法:递归/回溯相关题目

本文探讨了回溯算法在解决信息技术问题中的应用,包括子集、子集II、全排列II、组合求和、组合总和II、电话号码的字母组合和括号生成等经典问题。通过实例解析,展示了如何利用回溯法实现递归遍历并避免重复,同时讲解了在处理重复元素和剪枝条件等方面的关键策略。
摘要由CSDN通过智能技术生成

回溯

递归、回溯思想类似bfs,通常需要考虑触底条件+回溯后处理(例如题目常有将临时结果记录的最后一项删除)

子集

在给定数组中做出所有子集。

public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> res = new LinkedList<>();
        List<Integer> temp = new LinkedList<>();
        backTrack(0, nums, res, temp);
        return res;
    }

    private void backTrack(int i, int[] nums, List<List<Integer>> res, List<Integer> temp) {
        res.add(new LinkedList<>(temp));
        for (int j = i; j < nums.length; j++) {
            temp.add(nums[j]);
            backTrack(j + 1, nums, res, temp);
            temp.remove(temp.size() - 1);
        }
    }

该题用递归回溯类似dfs,只不过是在一维数组内进行dfs,每次遇到新数加入结果,递归探到底,到底后回溯需要将结果数列最后一个数删掉。

子集 II

public List<List<Integer>> subsetsWithDup(int[] nums) {
        List<List<Integer>> res = new LinkedList<>();
        List<Integer> temp = new LinkedList<>();
        boolean[] visited = new boolean[nums.length];
        if (nums.length == 0) {
            res.add(temp);
            return res;
        }
        Arrays.sort(nums);
        subSets(nums, 0, res, temp, visited);
        return res;
    }
    
    private void subSets(int[] nums, int index, List<List<Integer>> res, List<Integer> temp, boolean[] visited) {
        res.add(new LinkedList<>(temp));
        if (index >= nums.length) {
            return;
        }
        for (int i = index; i < nums.length; i++) {
            if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {
                continue;
            }
            temp.add(nums[i]);
            visited[i] = true;
            subSets(nums, i + 1, res, temp, visited);
            visited[i] = false;
            temp.remove(temp.size() - 1);
        }
    }

相比于之前子集,添加了重复内容,所以需要进行重复性判断。
即使用visited进行是否遍历过的记录,当遇到新值nums[i] == nums[i - 1](前提需要数组排序)说明遇到两个相同值中的第二个,那么有两种情况:
①若i-1数值被用过,说明在重复的数据中,i下标值并不是重复数字中第一次出现,这次使用会出现类似12(i-1)2(i)这种情况,这样不会产生重复数据
②若i-1数值没被用,说明当前i下标值是重复数字中第一次出现,但在数组中并不是重复数字的头部,类似数组122,当第二个2想开始构造,就会与第一个2构造出的数据发生重复
所以由!visited[i - 1]划分出两个情况进行处理。

全排列 II

目的得出给定数组的全部排列组合可能。

public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> res = new LinkedList<>();
        if (nums.length == 0) {
            return res;
        }
        Arrays.sort(nums);

        boolean[] visited = new boolean[nums.length];
        dfs(nums, visited, nums.length, 0, new LinkedList<>(), res);
        return res;
    }

    private void dfs(int[] nums, boolean[] visited, int len, int size, List<Integer> path, List<List<Integer>> res) {
        if (size == len) {
            res.add(new LinkedList<>(path));
            return;
        }

        for (int i = 0; i < len; i++) {
            if (visited[i]) {
                continue;
            }
            if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {
                continue;
            }

            path.add(nums[i]);
            visited[i] = true;

            dfs(nums, visited, len, size + 1, path, res);
            visited[i] = false;
            path.remove(path.size() - 1);
        }
    }

相比于子集的区别就是每次以新下标进行遍历时,仍要从头开始遍历,相关注意和子集II相同(相同字符判断等)

组合求和

给定数据数组和目标值,需要得出所有可以相加为目标值的组合。

public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new LinkedList<>();
        if (candidates.length == 0) {
            return res;
        }
        Arrays.sort(candidates);

        dfs(candidates, 0, target, new LinkedList<>(), res);

        return res;
    }

    private void dfs(int[] candidates, int begin, int target, List<Integer> path, List<List<Integer>> res) {
        if (target == 0) {
            res.add(new LinkedList<>(path));
            return;
        }

        for (int i = begin; i < candidates.length; i++) {
            if (target - candidates[i] < 0) {
                break;
            }

            path.add(candidates[i]);
            dfs(candidates, i, target - candidates[i], path, res);
            path.remove(path.size() - 1);
        }
    }

因为该题数字可以重复使用(即目标值为6,数组2 3,结果可以有{2,2,2}和{3,3})且没有重复元素,所以不用进行相同值判断的处理。
因为每次遍历由当前位置起始,所以遍历过的元素不会再次遍历,不用visited记录。

组合总和 II

相比上面题目,该题数字只能用一次

public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        if (candidates.length == 0) {
            return res;
        }

        Arrays.sort(candidates);

        List<Integer> path = new LinkedList<>();
        dfs(candidates, 0, target, path, res);
        return res;
    }

    private void dfs(int[] candidates, int begin, int target, List<Integer> path, List<List<Integer>> res) {
        if (target == 0) {
            res.add(new ArrayList<>(path));
            return;
        }

        for (int i = begin; i < candidates.length; i++) {
            if (target - candidates[i] < 0) {
                break;
            }

            if (i > begin && candidates[i] == candidates[i - 1]) {
                continue;
            }

            path.add(candidates[i]);

            dfs(candidates, i + 1, target - candidates[i], path, res);

            path.remove(path.size() - 1);
        }
    }

因为数字只能用一次且有重复数据,就要防止重复组合情况出现i > begin && candidates[i] == candidates[i - 1]

电话号码的字母组合

给一串数字,得到其9键可组成的所有字串

public List<String> letterCombinations(String digits) {
        List<String> res = new LinkedList<>();
        if (digits.length() == 0) {
            return null;
        }
        String[] map = new String[] {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};

        backTrack(map, res, new StringBuilder(), digits, 0);

        return res;
    }

    private void backTrack(String[] map, List<String> res, StringBuilder stringBuilder, String digits, int index) {
        if (stringBuilder.length() == digits.length()) {
            res.add(stringBuilder.toString());
            return;
        }

        for (char word : map[digits.charAt(index) - '2'].toCharArray()) {
            stringBuilder.append(word);
            backTrack(map, res, stringBuilder, digits, index + 1);
            stringBuilder.deleteCharAt(stringBuilder.length() - 1);
        }
    }

该题目和之前求全排列类似,只是取字符换了一种方式,由String[] map = new String[] {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};方便后续依据数字取可能字符。

括号生成

给定n组左右括号,生成所有组合并且有效

public List<String> generateParenthesis(int n) {
        List<String> res = new LinkedList<>();
        StringBuilder temp = new StringBuilder();

        backTrack(res, temp, n, n);

        return res;
    }

    private void backTrack(List<String> res, StringBuilder temp, int left, int right) {
        if (left == 0 && right == 0) {
            res.add(temp.toString());
            return;
        }

        if (left > right) {
            return;
        }

        if (left > 0) {
            backTrack(res, temp.append("("), left - 1, right);
            temp.deleteCharAt(temp.length() - 1);
        }
        if (right > 0) {
            backTrack(res, temp.append(")"), left, right - 1);
            temp.deleteCharAt(temp.length() - 1);
        }
    }

记录左右括号可用数,思路很简单,只需要注意剪枝条件:左括号可用大于右括号(其后无法完成匹配)

单词搜索

给定二维数组,查找其内部是否包含目标单词

public boolean exist(char[][] board, String word) {
        boolean[][] visited = new boolean[board.length][board[0].length];
        int[][] dir = new int[][] {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};

        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if (backTrack(board, word, 0, visited, i, j, dir)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean backTrack(char[][] board, String word, int pos, boolean[][] visited, int row, int col, int[][] dir) {
        if (row < 0 || col < 0 || row >= board.length || col >= board[0].length || visited[row][col]) {
            return false;
        }

        if (board[row][col] != word.charAt(pos)) {
            return false;
        }

        if (pos == word.length() - 1) {
            return true;
        }
        visited[row][col] = true;

        for (int[] ints : dir) {
            if (backTrack(board, word, pos + 1, visited, row + ints[0], col + ints[1], dir)) {
                return true;
            }
        }
        visited[row][col] = false;

        return false;
    }

这个题就可以看出经典bfs思路,到新节点,四方向遍历查找。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魔幻音

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值