力扣最热100题之回溯算法全排列问题

回溯问题是我们面试中会经常遇到的问题,它和贪心问题以及动态规划问题可以说是面试三位常客,本篇文章就会从根本上讲清楚回溯问题中排列相关问题

1、排列问题之不含重复元素的数组

题目:一个不含重复元素的数组,让你求出其不重复的所有全排列?
解析:不含重复元素,就意味着不需要去重,所以这是一类较为简单的问题。我们根据回溯法三步走:
1)先确定回溯函数参数
排列问题,我们是可以走回头路的,也就是可以从第四个元素开始,再次访问第一、二、三号元素。因此我们就不用传入index参数作为当前开始元素标志位(如果在组合中,是不可以走回头路的,那就需要index参数来说明你只能从index开始往下走,往后走你会重复组合)。然后就是数组参数。
2)回溯返回条件
排列问题,那回溯条件必然是当回溯收集的数据长度和原数组长度相等时,返回。
3)单层循环逻辑
在for循环中,从0开始,一直到数组长度结束。当当前元素在回溯收集列表里,就跳过该元素,进入下一次循环。如果不在,那就将该元素加入回溯列表。然后递归调用函数。最后回溯将该元素再从回溯列表中删除。

class Solution {
    List<List<Integer>> list = new ArrayList<>();
    List<Integer> li = new ArrayList<>();
    public List<List<Integer>> permute(int[] nums) {
        
        // 排列,组合,子集,分割,棋盘
        // list add get
        // set add  itertor  hasNext()  next()
        // map put get
        Test(nums);
        return list;
    }

    void Test(int[] nums){
        
        if(li.size() == nums.length){
            list.add(new ArrayList<>(li));
            return;
        }

        for(int i = 0; i < nums.length; i++){
            if(valid(li, nums[i])){
                continue;
            }
            li.add(nums[i]);
            Test(nums);
            li.remove(li.size()-1);
        }
    }

    boolean valid(List<Integer> a, int b){
        for(int i = 0; i < a.size(); i++){
            if(b == a.get(i)){
                return true;
            }
        }

        return false;
    }
}

2、含重复元素的数组,让你找出所有不重复的全排列。

本题中一个重点信息就是含重复元素。这就涉及到我们的去重操作了。普遍采用的去重是树层间去重。我们可以把数组想象成一颗树,不同的元素做树根会形成不同的树。在我们遍历整个数组成二叉树时,当到某一层相邻的元素是相同的时候,那么继续往下遍历会得到相同的结果。比如[1,2,2,3,4]。当我们遍历到1,2后,同层还有一个节点是1,2(这个2是后面的那个2).对于这两种情况,在继续组全排列的情况是相同的,都是(2,3,4)的全排列。因此需要去重。所以我们应该为每个节点设置一个标志位。如果上一个节点和我这个节点是相邻树层节点,并且还相等,就去重。否则不去重。

可是我们怎么知道是树枝上相等,还是树层上相等呢?
用一个标志位,如果你是向下递归调用的化,那就将标志位设置为true,代表是递归下来的,是一个树枝。如果是false,没有被修改为true,说明你不是递归进来的,那就是回溯到某一层,然后继续下一个该层节点进来的,就是树层。

知道了上面这些分析,那代码就好写了,大框架和上一题相同。

class Solution {

    List<List<Integer>> list = new ArrayList<>();
    List<Integer> li = new ArrayList<>();
    boolean[] flag;

//  组合才不让回退,所有有index,如果可以重复用,那就传入i,不可以就传入i+1
    public List<List<Integer>> permuteUnique(int[] nums) {
    
        if(nums.length == 0){
            return list;
        }

        flag = new boolean[nums.length];

        Arrays.sort(nums);

        Test(nums);
        

        return list;

    }

    void Test(int[] nums){

        if(nums.length == li.size()){
            list.add(new ArrayList(li));
            return;
        }

        for(int i = 0; i < nums.length; i++){

            if(i > 0 && nums[i-1] == nums[i] && flag[i-1] == false){
                continue;
            }

            if(flag[i] == false){
            li.add(nums[i]);
            flag[i] = true;
            Test(nums);

            li.remove(li.size()-1);
            flag[i] = false;
            }
        }
    }
}

至此,排列问题两大类基本结束,下一篇我们更新力扣最热100题之回溯组合问题!!!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
力扣是一个在线编程平台,提供了大量的算法目,可以帮助程序员提高算法能力。回溯算法是一种搜索算法,它通过不断地尝试所有可能的解来求解问题。在回溯算法中,我们首先定义一个解空间,然后从解空间中搜索所有可能的解,直到找到符合要求的解为止。回溯算法通常用于求解组合问题排列问题、子集问题等。 在 Java 中实现回溯算法,通常需要定义一个递归函数来搜索解空间。在递归函数中,我们首先判断当前状态是否符合要求,如果符合要求,则将当前状态加入到解集中;否则,我们继续搜索下一个状态。在搜索下一个状态时,我们需要对当前状态进行一些修改,然后递归调用自身来搜索下一个状态。当搜索完所有可能的状态后,我们需要回溯到上一个状态,继续搜索其他可能的状态。 以下是回溯算法的一般步骤: 1. 定义解空间:确定问题的解空间,并定义一个数据结构来表示解空间中的每个状态。 2. 确定约束条件:确定哪些状态是合法的,并定义一个函数来判断当前状态是否符合要求。 3. 确定搜索策略:确定搜索解空间的顺序,并定义一个函数来生成下一个状态。 4. 搜索解空间:使用递归函数搜索解空间,如果当前状态符合要求,则将其加入到解集中;否则,继续搜索下一个状态。 5. 回溯:当搜索完所有可能的状态后,回溯到上一个状态,继续搜索其他可能的状态。 以下是一个力扣目的回溯算法 Java 实现示例: ``` class Solution { List<List<Integer>> res = new ArrayList<>(); List<Integer> path = new ArrayList<>(); public List<List<Integer>> subsets(int[] nums) { dfs(nums, 0); return res; } private void dfs(int[] nums, int start) { res.add(new ArrayList<>(path)); for (int i = start; i < nums.length; i++) { path.add(nums[i]); dfs(nums, i + 1); path.remove(path.size() - 1); } } } ``` 该算法用于求解给定数组的所有子集。在递归函数中,我们首先将当前状态加入到解集中,然后从当前位置开始搜索下一个状态。在搜索下一个状态时,我们将当前元素加入到路径中,并递归调用自身来搜索下一个状态。当搜索完所有可能的状态后,我们需要回溯到上一个状态,继续搜索其他可能的状态。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值