Permutations II medium
Problem Statement
Given a collection of numbers, nums
, that might contain duplicates, return all unique permutations of the numbers. You can return the answer in any order.
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 allows us to easily identify and skip duplicate permutations.
-
Backtracking: Use a backtracking algorithm to generate all possible permutations. The core idea is to recursively explore all possible positions for each number.
-
Skip Duplicates: When choosing a number for a position, skip numbers that are the same as the previously chosen number at that level of recursion. This prevents generating duplicate permutations.
-
Base Case: The base case for recursion is when we have placed all numbers in the permutation. Add this permutation to the result.
-
Recursive Step: For each available number, place it in the current position, recursively generate the rest of the permutation, and then backtrack (remove the number from the current position).
Explanation
The backtracking approach systematically explores all possible arrangements of the numbers. Sorting the input array significantly improves efficiency by allowing us to quickly identify and avoid duplicates. The condition i > 0 and nums[i] == nums[i - 1] and i - 1 not in used
ensures we only consider unique permutations.
Code
from typing import List
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort() # Sort to easily handle duplicates
n = len(nums)
used = set() # Keep track of used indices in the current permutation
def backtrack(current_permutation):
if len(current_permutation) == n:
result.append(current_permutation[:]) # Add a copy to avoid modification
return
for i in range(n):
if i not in used:
# Skip duplicates at the same recursion level
if i > 0 and nums[i] == nums[i - 1] and i - 1 not in used:
continue
used.add(i)
current_permutation.append(nums[i])
backtrack(current_permutation)
current_permutation.pop() # Backtrack: remove the last element
used.remove(i)
backtrack([])
return result
Complexity
-
Time Complexity: O(N! * N), where N is the length of the input array. Generating all permutations takes O(N!) time, and adding each permutation to the result list takes O(N) time.
-
Space Complexity: O(N) due to the recursive call stack and the
used
set. The result list can also take O(N!) space in the worst case (no duplicates), but this is not part of the recursive function's space usage.