Permutations II medium
Problem Statement
Given a collection of numbers, nums, that might contain duplicates, return all possible unique permutations.
Example 1
Input: nums = [1,1,2] Output: [[1,1,2],[1,2,1],[2,1,1]]
Example 2
Input: nums = [1,2,3] Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
Steps
-
Sort the input array: Sorting the input array helps to easily identify and skip duplicate permutations.
-
Backtracking Algorithm: We'll use a backtracking algorithm to generate all permutations. The algorithm explores all possible arrangements by recursively placing each number in each available position.
-
Handling Duplicates: To avoid generating duplicate permutations, we'll skip numbers that have already been used in the current permutation. We achieve this using a
used
array which tracks if a number in its original index is already in current permutation or not. -
Base Case: The base case of the recursion is when we've placed all numbers in the permutation. We add this permutation to the result list.
-
Recursive Step: For each number in the array, we check if it's used. If not, we mark it as used, add it to the current permutation, recursively call the function to explore further permutations, and then backtrack (remove the number and mark it as unused).
Explanation
The backtracking algorithm systematically explores all possible arrangements. Sorting the input ensures that we process numbers in a predictable order, making it easier to detect and skip duplicate permutations. The used
array efficiently tracks the usage of numbers. By checking used[i]
we avoid processing the same numbers multiple times during a single recursive call.
Code
using System;
using System.Collections.Generic;
using System.Linq;
public class Solution {
public IList<IList<int>> PermuteUnique(int[] nums) {
IList<IList<int>> result = new List<IList<int>>();
Array.Sort(nums); //Sort for efficient duplicate handling
List<int> currentPermutation = new List<int>();
bool[] used = new bool[nums.Length]; //used[i] indicates if nums[i] is used
Backtrack(nums, used, currentPermutation, result);
return result;
}
private void Backtrack(int[] nums, bool[] used, List<int> currentPermutation, IList<IList<int>> result) {
if (currentPermutation.Count == nums.Length) {
result.Add(new List<int>(currentPermutation)); //Add a copy to avoid modification
return;
}
for (int i = 0; i < nums.Length; i++) {
//Skip if already used or a duplicate of the previous used number
if (used[i] || (i > 0 && nums[i] == nums[i-1] && !used[i-1])) {
continue;
}
used[i] = true;
currentPermutation.Add(nums[i]);
Backtrack(nums, used, currentPermutation, result);
currentPermutation.RemoveAt(currentPermutation.Count - 1); //Backtrack
used[i] = false;
}
}
}
Complexity
-
Time Complexity: O(N * N!), where N is the length of the input array. Generating all permutations takes O(N!) time, and for each permutation, we perform O(N) work (copying to the result list).
-
Space Complexity: O(N) for the recursion stack and the
currentPermutation
list. The result list could also potentially use O(N * N!) space in the worst case (all unique permutations). Theused
array uses O(N) space.