leetcode
leetcode 1 ~ 50
全排列 II

全排列 II

难度:

标签:

题目描述

给定一个可包含重复数字的序列 nums按任意顺序 返回所有不重复的全排列。

 

示例 1:

输入:nums = [1,1,2]
输出:
[[1,1,2],
 [1,2,1],
 [2,1,1]]

示例 2:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

 

提示:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

代码结果

运行时间: 52 ms, 内存: 15.3 MB


/* 
 * 思路:
 * 1. 利用Java Stream API来生成排列。
 * 2. 使用Map来统计每个数字的出现次数,避免重复排列。
 * 3. 使用递归和流的扁平映射操作来生成排列。
 */
 
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
 
public class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        Map<Integer, Long> counter = Arrays.stream(nums).boxed()
                .collect(Collectors.groupingBy(n -> n, Collectors.counting()));
        return permute(new LinkedList<>(), nums.length, counter).collect(Collectors.toList());
    }
 
    private Stream<List<Integer>> permute(LinkedList<Integer> current, int n, Map<Integer, Long> counter) {
        if (current.size() == n) {
            return Stream.of(new ArrayList<>(current));
        }
        return counter.entrySet().stream()
                .filter(entry -> entry.getValue() > 0)
                .flatMap(entry -> {
                    current.add(entry.getKey());
                    counter.put(entry.getKey(), entry.getValue() - 1);
                    Stream<List<Integer>> result = permute(current, n, counter);
                    current.removeLast();
                    counter.put(entry.getKey(), entry.getValue());
                    return result;
                });
    }
}
 

解释

方法:

这个题解使用了回溯算法。首先对数组进行排序,然后使用递归的方式生成所有排列。为了避免生成重复的排列,在每一层递归中,如果当前元素和前一个元素相同,则跳过该元素。另外,为了避免在递归过程中重复使用同一个元素,使用 None 标记已经使用过的元素。

时间复杂度:

O(n * n!)

空间复杂度:

O(n * n!)

代码细节讲解

🦆
为什么在回溯算法中首先需要对数组进行排序?排序对于避免重复排列有什么帮助?
在回溯算法中对数组进行排序是为了更方便地处理重复元素。当数组排序后,相同的元素会被排列在一起。这样,在生成排列的过程中,可以通过简单的比较相邻元素来检查是否存在重复的元素。如果当前元素与前一个元素相同,可以直接跳过这个元素,从而避免生成重复的排列。这种方法简化了重复检测的逻辑,使得代码更简洁且有效率。
🦆
在回溯过程中,使用`None`来标记已经使用过的元素是否是最优的方法?是否有其他方式可以追踪元素的使用状态?
使用`None`来标记已经使用过的元素是一种有效的方法,但不一定是最优的方法,因为它修改了原数组的值,可能在某些情况下引起混淆或错误。其他常见的方法包括使用一个布尔数组(或位图)来追踪每个元素的使用状态。这种方法不需要修改原数组,而是通过额外的空间来记录哪些元素已被使用过,这样可以保持原数组不变,逻辑上可能更清晰。
🦆
题解中提到如遇到当前元素与前一个元素相同就跳过,这种方法是否确保了所有不重复的全排列都被正确生成,还是可能漏掉某些排列?
此方法确保了所有不重复的全排列都被正确生成,并没有漏掉任何排列。通过先排序再检查相邻元素是否相同,这种策略确保只有当相同的元素第一次出现时才会被考虑进排列中,而在相同元素的连续序列中,只有第一个元素会被用来生成新的排列。这样做可以避免重复而不会错过任何合法的排列组合。
🦆
在实现中,为什么在每次递归调用`backtrack`之后需要执行`nums[i] = path.pop()`这一步?这样做有什么特别的目的或好处?
这一步是回溯算法的核心部分,称为回溯。在递归调用`backtrack`之后执行`nums[i] = path.pop()`是为了撤销之前做出的选择,即将当前元素从路径中移除并恢复其在数组中的值,使得这个元素在后续的迭代中可以重新被使用。这样做确保了在每一层递归中,路径和数组的状态都被正确地管理和恢复,使算法能够正确地探索所有可能的排列组合。

相关问题

下一个排列

整数数组的一个 排列  就是将其所有成员以序列或线性顺序排列。

  • 例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3][1,3,2][3,1,2][2,3,1]

整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

  • 例如,arr = [1,2,3] 的下一个排列是 [1,3,2]
  • 类似地,arr = [2,3,1] 的下一个排列是 [3,1,2]
  • arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。

给你一个整数数组 nums ,找出 nums 的下一个排列。

必须 原地 修改,只允许使用额外常数空间。

 

示例 1:

输入:nums = [1,2,3]
输出:[1,3,2]

示例 2:

输入:nums = [3,2,1]
输出:[1,2,3]

示例 3:

输入:nums = [1,1,5]
输出:[1,5,1]

 

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 100

全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

 

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]

 

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

回文排列 II

回文排列 II

正方形数组的数目

给定一个非负整数数组 A,如果该数组每对相邻元素之和是一个完全平方数,则称这一数组为正方形数组。

返回 A 的正方形排列的数目。两个排列 A1A2 不同的充要条件是存在某个索引 i,使得 A1[i] != A2[i]。

 

示例 1:

输入:[1,17,8]
输出:2
解释:
[1,8,17] 和 [17,8,1] 都是有效的排列。

示例 2:

输入:[2,2,2]
输出:1

 

提示:

  1. 1 <= A.length <= 12
  2. 0 <= A[i] <= 1e9