回溯算法大探秘 --java版

回溯算法学习:
找了几个相似的题目,统一学习了一下:
第一道题:大小字母的全排列

题目描述:
在这里插入图片描述

一开始想用回溯法,对回溯法了解的并不那么深入,去百度了一下,觉得这篇文章写得很好,
回溯法

现在理解到,回溯法就是需要有一个结束条件,满足结束条件,就添加或者返回元素,不满足时,去寻找满足的条件,然后按照题目的要求,去寻找满足条件的情况。
上面是形式上的,从具体的遍历方式上,类似于深度搜索,先一步一步遍历,知道找到满足条件的,然后开始往前倒推,倒到上一步,找到满足条件的,这样一直倒推到最后,所有的情况都被遍历完成,回溯完成。

这一题先自己思考,后面参考题解,最终大概有两种解法,思想都是一样的:
第一种:

    public void letterSolvedTheEnd(String s, List<String> result, int index, String preString) {
        if(index == s.length()) {
            result.add(preString);
        } else {
            char temp = s.charAt(index);
            if(Character.isLetter(s.charAt(index))) {
                temp = Character.toUpperCase(s.charAt(index));
                System.out.println("temp = " + temp + ", priStringUp = " + preString + ", index = " + index);
                letterSolvedTheEnd(s, result, index + 1, preString + temp);
                temp = Character.toLowerCase(s.charAt(index));
                System.out.println("temp = " + temp + ", priStringDown = " + preString + ", index = " + index);
                letterSolvedTheEnd(s, result, index + 1, preString + temp);
            } else {
                preString += s.charAt(index);
                System.out.println("temp = " + temp + ", 数字 = " + preString + ", index = " + index);
                letterSolvedTheEnd(s, result, index + 1, preString);
            }
        }
    }

第二种:

public void letterSolvedAnother(char[] temp, List<String> result, int index) {
        if(index == temp.length) {
            result.add(String.valueOf(temp));
        } else {
            if(Character.isLetter(temp[index])) {
                temp[index] = Character.toUpperCase(temp[index]);
                
                letterSolvedAnother(temp, result, index+1);
                
                temp[index] = Character.toLowerCase(temp[index]);
                letterSolvedAnother(temp, result, index+1);
            } else {
                letterSolvedAnother(temp, result, index+1);
            }
        }
    }

这两种本质都是一样的,上面给出的是用来递归的函数。
主函数就比较简单:

    public List<String> letterCase(String s) {
        List<String> result = new ArrayList<>();
        String preString = "";
        // letterSolvedTheEnd(s, result, 0, preString);
        char[] temp = s.toCharArray();
        letterSolvedAnother(temp, result, 0);
        return result;
    }

可以通过:
在这里插入图片描述
第二道题:
在这里插入图片描述
这个还要考虑包含了重复数字,返回所有不重复的全排列,这个就需要设置一个数据,标记那个数字有没有被使用过:

    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> ans = new ArrayList<List<Integer>>();
        List<Integer> perm = new ArrayList<Integer>();
        vis = new boolean[nums.length];
        Arrays.sort(nums);
        backtrack(nums, ans, 0, perm);
        return ans;
    }
    public void backtrack(int[] nums, List<List<Integer>> ans, int idx, List<Integer> perm) {
        if (idx == nums.length) {
            ans.add(new ArrayList<Integer>(perm));
            return;
        }
        for (int i = 0; i < nums.length; ++i) {
            if (vis[i] || (i > 0 && nums[i] == nums[i - 1] && !vis[i - 1])) {
                continue;
            }
            perm.add(nums[i]);
            vis[i] = true;
            backtrack(nums, ans, idx + 1, perm);
            vis[i] = false;
            perm.remove(idx);
        }
    }

这个题目还踩了坑:
第一个:

if (idx == nums.length) {
    ans.add(new ArrayList<Integer>(perm));
    return;
}

这个要这样处理,不然返回的是空
是因为回溯法回到上一步的时候,有remove操作,如果直接是不这样保存一份perm的复制值,就会把remove后的 [ ] 加载进去。

第二个:
这个判断条件

if (vis[i] || (i > 0 && nums[i] == nums[i - 1] && !vis[i - 1])) {
   continue;
}

当时对为什么是:!vis[i - 1] 纠结了很久,后面看了一个视频,讲解的很好,这样处理时因为,如果之前这个值已经出现过了,那么它一定是 回置回false。

在这里插入图片描述
第三道题:
做完这个需要增加一个数组来标记数字有没有使用的题目,再去做他的上一个题目,就很简单了
在这里插入图片描述
同样的思路,不用增加额外的判断:

    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> result = new ArrayList<>(new ArrayList<>());
        List<Integer> temp = new ArrayList<>();
        permuteHelp(nums, result, temp, 0);
        return result;
    }
    public void permuteHelp(int[] nums, List<List<Integer>> result, List<Integer> temp, int index) {
        if(index == nums.length) {
            result.add(new ArrayList<>(temp));
        }
        for (int i = 0; i < nums.length; i++) {
            if(temp.contains(nums[i])) {
                continue;
            }
            temp.add(nums[i]);
            permuteHelp(nums, result, temp, index+1);
            temp.remove(temp.size()-1);
        }
    }

在这里插入图片描述
第四道题:
后面又做了这道题用来练习,还是有一丢丢难的(对我来说),对大佬来说很easy。
在这里插入图片描述
这个一开始想到了用回溯,使用各种判断条件,最后还是在看了讲解之后,写出来的:

    public boolean exist(char[][] board, String word) {
        if (board.length == 0 || word.length() == 0) {
            return false;
        }
        int l1 = board.length;
        int l2 = board[0].length;
        boolean temp[][] = new boolean[l1][l2];
        for (int i = 0; i < l1; i++) {
            for (int j = 0; j < l2; j++) {
                if(existHelper(board, word, temp, i, j, 0)) {
                    return true;
                }
            }
        }
        return false;
    }
    public boolean existHelper(char[][] board, String word, boolean[][] temp, int i, int j, int index) {
        //退出递归的边界条件
        if(index == word.length()) {
            return true;
        }
        //剪枝操作,减少无效的递归
        if(i < 0 || i >= board.length || j < 0 || j >= board[0].length || temp[i][j] || board[i][j] != word.charAt(index)) {
            return false;
        }
        temp[i][j] = true;
        //真正的递归函数
        boolean result = existHelper(board, word, temp, i-1, j, index+1)
                || existHelper(board, word, temp, i, j-1, index+1)
                || existHelper(board, word, temp, i+1, j, index+1)
                || existHelper(board, word, temp, i, j+1, index+1);
        temp[i][j] = false;
        return result;
    }

讲两个我遇到的坑吧:
第一个:

        for (int i = 0; i < l1; i++) {
            for (int j = 0; j < l2; j++) {
                if(existHelper(board, word, temp, i, j, 0)) {
                    return true;
                }
            }
        }

这个双层for循环要放在主函数中,每一个值都要进行回溯,这个当时没有想到,导致写的很艰难。

第二个:

        if(index == word.length()) {
            return true;
        }

当时一开始写时候,懵圈了,导致判断回溯退出的条件都不知道咋找了

在这里插入图片描述
总结一下,通过回溯法解题的要领就是:
第一:一定要找到回溯结束的条件
第二:遇到给定条件中有重复元素,但是结果集中不包含重复元素的情况,需要增加一个数组用来标记,方便后面剪枝处理。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值