全排列问题_不含相同元素和含有相同元素_Leetcode46_Leetcode47_回溯解法+Java代码

全排列问题(不含相同元素和含有相同元素+Java回溯代码)


  全排列是一种非常经典的算法问题,它大体可以分为两类:第一种是不含相同元素的全排列问题;第二种是含有相同元素的全排列问题。后者因为序列中含有相同元素,所以会出现很多的重复答案,问题的关键就在于消除这种重复答案。一旦我们写出了两种全排列问题的代码,就可以一劳永逸,把它们当作模板使用。

1 不含相同元素的全排列问题
1.1 问题描述

  问题描述可以参见Leetcode46题

1.2 解题思路

  采用回溯的基本思想,通过设置一个visited数组标记原始序列中每个元素是否已被访问,递归地输出问题所有可能的排列方式。需要注意的是,每次递归完成后,要恢复visited数组的状态,否则会影响程序后面的判断。

1.3 Java代码
import java.util.*;

public class Question_46 {

    public List<List<Integer>> permute(int[] nums) {
    	//使用一个List存储所有可能的排列方式
        List<List<Integer>> res = new ArrayList<>();
        //visited数组,标记该位置的元素是否已被访问
        boolean[] visited = new boolean[nums.length];
        backtrack(res, nums, visited, new ArrayList<Integer>());
        return res;
    }

    private void backtrack(List<List<Integer>> res, int[] nums, boolean[] visited, ArrayList<Integer> tmp) {
    	//如果当前序列的元素个数等于nums数组的长度,则这是一种可能的解,加入res中
        if (tmp.size() == nums.length) {
        	//注意不要直接加入tmp
            res.add(new ArrayList<>(tmp));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
        	//如果已被访问,返回
            if (visited[i]) {
                continue;
            }
            visited[i] = true;
            tmp.add(nums[i]);
            backtrack(res, nums, visited, tmp);
            //使用完毕后,恢复到初始状态
            visited[i] = false;
            tmp.remove(tmp.size() - 1);
        }
    }

}
2 含有相同元素的全排列问题
2.1 问题描述

  问题描述可以参见Leetcode47题

2.2 解题思路

  依然采用回溯的基本思想,不同的是需要消除因为序列中含有相同元素而带来的重复答案。解题思路为:先将数组进行排序(把相同元素聚合到一起,让它们彼此靠近),然后在回溯的时候,只有当前面的相同元素被访问过,才可以访问下一个相同元素(当然也可以设置为只有当前面的相同元素没有被访问过,才可以访问下一个相同元素,这代表了两种排列方式,结果都是一样的)。

2.3 Java代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Question_47 {

    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> permutes = new ArrayList<>();
        List<Integer> permuteList = new ArrayList<>();
        //需要对数组事先进行排序,把相同的元素聚合到一起
        Arrays.sort(nums);
        boolean[] hasVisited = new boolean[nums.length];
        backtracking(permuteList, permutes, hasVisited, nums);
        return permutes;
    }

    private void backtracking(List<Integer> permuteList, List<List<Integer>> permutes, boolean[] visited, int[] nums) {
    	//如果当前序列的元素个数等于nums数组的长度,则这是一种可能的解,加入permutes中
        if (permuteList.size() == nums.length) {
        	//注意不要直接加入permuteList
            permutes.add(new ArrayList<>(permuteList));
            return;
        }

        for (int i = 0; i < visited.length; i++) {
        	//这里设置为只有当前面的相同元素被访问过,才可以访问该元素,当然也可以设置为只有当前面的相同元素没有被访问过,才可以访问当前元素,这代表了两种排列顺序,结果都是一样的
            if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {//也可以写成(i != 0 && nums[i] == nums[i - 1] && visited[i - 1])
                continue;//防止重复
            }
            if (visited[i]) {
                continue;
            }
            visited[i] = true;
            permuteList.add(nums[i]);
            backtracking(permuteList, permutes, visited, nums);
            //使用完毕后,恢复到初始状态
            permuteList.remove(permuteList.size() - 1);
            visited[i] = false;
        }
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值