
Problem Statement
The task is to generate all unique permutations of a given list of numbers, potentially including duplicates. A permutation refers to an arrangement of all the elements of the list in a particular order. For instance, given the list [1, 1, 2], besides the obvious arrangement, it can be rearranged in different orders such as [2, 1, 1] or [1, 2, 1]. It becomes slightly more complex with the presence of duplicates as it requires ensuring that each permutation is unique, eliminating any redundant combinations.
Examples
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]]
Constraints
1 <= nums.length <= 8
-10 <= nums[i] <= 10
Approach and Intuition
The challenge posed by the problem involves dealing with duplicates effectively, to ensure that the permutations generated are unique. Here’s a step-by-step breakdown of approaching this problem:
1. Sorting
- Begin by sorting the list. This initial step simplifies the process of avoiding duplicates, as it ensures that identical items are next to each other, making it easy to skip over successive duplicates during the permutation process.
2. Backtracking Technique
- Utilize the backtracking algorithm to generate permutations. This involves recursively choosing an item, fixing it in the current permutation, and then generating permutations of the remaining items.
3. Handling Duplicates
- When choosing items for the current position of the permutation, if the item is the same as the one before it and if the previous was not picked, skip it. This step is crucial and is successful due to the initial sorting. This ensures that each unique permutation is only generated once.
These logical steps ensure that all unique permutations of the input list are generated, concerning the constraint limits which maintain the problem at a computable scale, given the factorial nature of permutations. This factorial nature is the reason why the problem limits the size of the number list to a maximum of 8, as the number of computations grows extremely fast with each additional element. The complexity of the approach lies in effectively dodging the repetition caused by identical numbers while covering all possible unique arrangements.
Solutions
- C++
- Java
- C
- JavaScript
- Python
// C++ Implementation
class Solution {
public:
vector<vector<int>> generatePermutations(vector<int>& elements) {
vector<vector<int>> solutions;
unordered_map<int, int> frequency;
for (int element : elements) frequency[element]++;
vector<int> currentCombination;
permuteRecursively(frequency, currentCombination, elements.size(), solutions);
return solutions;
}
void permuteRecursively(unordered_map<int, int>& frequency, vector<int>& combination, int length,
vector<vector<int>>& solutions) {
if (combination.size() == length) {
solutions.push_back(combination);
return;
}
for (auto& pair : frequency) {
int value = pair.first;
int count = pair.second;
if (count == 0) continue;
combination.push_back(value);
frequency[value]--;
permuteRecursively(frequency, combination, length, solutions);
combination.pop_back();
frequency[value]++;
}
}
};
The provided C++ code outlines a solution for generating all unique permutations of a list of numbers, potentially including duplicates. This task is effectively executed using backtracking and an unordered map to manage the frequency of each element, ensuring an efficient manner of handling repetitions. Below is a breakdown of how the code operates:
Initialization of Data Structures:
- A
vector<vector<int>>
namedsolutions
to store all the unique permutations. - An
unordered_map<int, int>
calledfrequency
to count occurrences of each number in the inputelements
.
- A
Building Frequency Map:
- Loop through each number in the
elements
vector and update its count in thefrequency
map.
- Loop through each number in the
Recursive Function –
permuteRecursively
:- Checks if the size of the current permutation
combination
matches the original list's length. If true, the permutation is complete and added tosolutions
. - Iterates through each element in the frequency map:
- If an element's count is zero, it continues to the next element (as none are left to place).
- Otherwise, the element is added to the current
combination
, its count is decreased, and the function recurses to fill the next position in the combination. - After recursion, the last added element is removed (backtracking) and its count restored, allowing the next potential element to be considered at the current position in the permutation.
- Checks if the size of the current permutation
Generation and Return of Results:
- The
generatePermutations
function returns thesolutions
vector, containing all unique permutations generated.
- The
The method efficiently handles the generation of permutations by advancing only with valid sequences using the counts in the frequency map, thereby avoiding duplicates without requiring complex logic or additional data structures. Make sure to include preprocessing if required, depending on how the initial input is provided or could vary.
class PermutationGenerator {
public List<List<Integer>> generatePermutations(int[] elements) {
List<List<Integer>> allPermutations = new ArrayList<>();
// Map for counting occurrences
HashMap<Integer, Integer> frequencyMap = new HashMap<>();
for (int element : elements) {
frequencyMap.put(element, frequencyMap.getOrDefault(element, 0) + 1);
}
LinkedList<Integer> currentCombination = new LinkedList<>();
backtrackPermutations(currentCombination, elements.length, frequencyMap, allPermutations);
return allPermutations;
}
private void backtrackPermutations(
LinkedList<Integer> currentComb,
Integer totalLength,
HashMap<Integer, Integer> frequencyMap,
List<List<Integer>> allPermutations
) {
if (currentComb.size() == totalLength) {
allPermutations.add(new ArrayList<>(currentComb));
return;
}
for (Map.Entry<Integer, Integer> entry : frequencyMap.entrySet()) {
Integer currentElement = entry.getKey();
Integer currentCount = entry.getValue();
if (currentCount == 0) continue;
currentComb.addLast(currentElement);
frequencyMap.put(currentElement, currentCount - 1);
backtrackPermutations(currentComb, totalLength, frequencyMap, allPermutations);
currentComb.removeLast();
frequencyMap.put(currentElement, currentCount);
}
}
}
The Java solution provided generates all unique permutations of a given array where the elements might have duplicates. This is achieved using backtracking, a common technique for exploring all possible configurations of a problem space. Here is a detailed breakdown of the approach taken:
A
HashMap
calledfrequencyMap
is utilized to store the frequency of each element in the input array. This helps in handling duplicates by ensuring that each permutation includes the correct count of elements.The
generatePermutations
function initiates the process by preparing the data structure for the permutations (allPermutations
), and invoking thebacktrackPermutations
helper method with the initial state.In
backtrackPermutations
, aLinkedList
calledcurrentComb
is used to build permutations incrementally:If the length of
currentComb
equals the length of the input array, a complete permutation is achieved. This permutation is then added toallPermutations
.The function then iterates through each entry in
frequencyMap
. If the count of an element is not zero, the element is added to the current combination, and its count is decremented in the map. This ensures not to use the element more times than it appears in the original array.A recursive call is made with the updated state (current combination and frequency map).
After the recursive call, the last element added to the combination is removed (backtrack), and its count in the frequency map is restored.
The advantage of this method lies in efficiently handling duplicates by managing counts of elements, thus avoiding generating non-unique permutations only to filter them out later. This implementation helps in optimizing time complexity relative to approaches that generate all permutations and then remove duplicates.
struct hash_table_entry {
int key;
int count;
UT_hash_handle hh;
};
void recursiveBacktrack(int* arr, int arrSize, int* sequence, int seqLength, int** results,
int* resultCount, int** resultColSizes,
struct hash_table_entry* elementsCount) {
if (seqLength == arrSize) {
results[*resultCount] = malloc(arrSize * sizeof(int));
memcpy(results[*resultCount], sequence, arrSize * sizeof(int));
resultColSizes[0][*resultCount] = arrSize;
(*resultCount)++;
return;
}
struct hash_table_entry *element, *tempElement;
HASH_ITER(hh, elementsCount, element, tempElement) {
if (element->count > 0) {
sequence[seqLength] = element->key;
element->count--;
recursiveBacktrack(arr, arrSize, sequence, seqLength + 1, results, resultCount,
resultColSizes, elementsCount);
element->count++;
}
}
}
int** generateUniquePermutations(int* elements, int elementsSize, int* outSize,
int** outColumnSizes) {
struct hash_table_entry *elementsCount = NULL, *entry;
for (int i = 0; i < elementsSize; i++) {
HASH_FIND_INT(elementsCount, &elements[i], entry);
if (entry == NULL) {
entry = malloc(sizeof(struct hash_table_entry));
entry->key = elements[i];
entry->count = 1;
HASH_ADD_INT(elementsCount, key, entry);
} else {
entry->count++;
}
}
int** output = malloc(10000 * sizeof(int*));
*outColumnSizes = malloc(10000 * sizeof(int));
*outSize = 0;
int* sequence = malloc(elementsSize * sizeof(int));
recursiveBacktrack(elements, elementsSize, sequence, 0, output, outSize, outColumnSizes,
elementsCount);
return output;
}
This C program allows the generation of unique permutations of a given set of integers, which can include duplicates, and outputs them in a 2D integer array. The solution leverages a combination of recursive backtracking and a hash table to efficiently track and manage the count of each element during the permutation generation process.
The generateUniquePermutations
function initializes the hash table for element counts, checks and increments counts while iterating through the input array. It also prepares the output structure and triggers the recursive permutation generation via the recursiveBacktrack
function.
- In the
recursiveBacktrack
function:- If the current sequence length matches the array size, the sequence is stored as a permutation.
- Iterates over the hash table to explore permutations from elements with remaining counts, manipulating counts recursively to generate permutations without duplicates.
The use of hash tables (UT_hash_handle
) from uthash.h
library facilitates quick lookup, addition, and deletion to manage element frequencies effectively during permutation generation. The primary function allocates memory for the necessary structures and initializes them appropriately to handle up to 10,000 permutations initially, which implies that adjustment may be needed based on input size or unique permutations possible to prevent memory overflow or under-allocation.
Ensure proper freeing of allocated memory in actual implementation to avoid memory leaks. This C program is particularly adept at dealing with permutations of arrays where elements might repeat, directly addressing challenges related to duplicate values. Make sure to include uthash.h in your environment when compiling and executing this solution.
// JavaScript Code
var findUniquePermutations = function (elements) {
let output = [];
let freqMap = {};
elements.forEach(element => {
if (!freqMap[element]) freqMap[element] = 0;
freqMap[element]++;
});
function explore(path, length) {
if (path.length === length) {
output.push([...path]);
return;
}
for (let element in freqMap) {
if (freqMap[element] === 0) continue;
path.push(parseInt(element));
freqMap[element]--;
explore(path, length);
freqMap[element]++;
path.pop();
}
}
explore([], elements.length);
return output;
};
This tutorial covers how to solve the problem of generating all unique permutations of a list of numbers that may include duplicates, using JavaScript. The approach used involves backtracking to ensure each permutation is unique by using a frequency map to control element inclusion.
Initialization:
- Start by declaring a function
findUniquePermutations
which takeselements
as its input. - Inside this function, declare an empty array
output
to store the unique permutations and an objectfreqMap
to count the frequency of each element in theelements
array.
- Start by declaring a function
Frequency Map Construction:
- Iterate over the
elements
array and populatefreqMap
. For each element, check if it already exists in the map. If it exists, increment its count; otherwise, add it with a count of 1.
- Iterate over the
Backtracking Function (explore):
- Define a function
explore
which will be used recursively. This function takespath
(to store the current permutation) andlength
(the desired length of the permutation, which is the same as the length ofelements
). - Check if the current
path.length
is equal tolength
. If true, it means a complete permutation has been formed:- Push a copy of
path
tooutput
. - Return to exit the current recursive call.
- Push a copy of
- Iterate over each element in
freqMap
:- If the current element’s frequency is zero, continue to the next element (it means this element has been fully used in this branch of recursion).
- Add the element to the
path
and decrement its frequency infreqMap
. - Recursively call
explore
. - Backtrack by incrementing the frequency of the current element and removing it from
path
.
- Define a function
Execution and Return:
- Call the
explore
function initially with an empty path and the length ofelements
. - Return the
output
array, which now contains all unique permutations.
- Call the
By the end of this guide, you efficiently solve the problem using the concepts of backtracking combined with a frequency map, ensuring that you can handle permutations of arrays with duplicate elements effectively in JavaScript.
class Solution:
def uniquePermutations(self, numbers: List[int]) -> List[List[int]]:
result_set = []
def dfs(current_state, count_map):
if len(current_state) == len(numbers):
result_set.append(list(current_state))
return
for value in count_map:
if count_map[value] > 0:
current_state.append(value)
count_map[value] -= 1
dfs(current_state, count_map)
current_state.pop()
count_map[value] += 1
dfs([], Counter(numbers))
return result_set
The provided Python solution focuses on generating all unique permutations of a given list of numbers. Here is the approach detailed in the solution:
The
uniquePermutations
method takes a listnumbers
as input and initializes an empty listresult_set
to hold the resultant permutations.A recursive function
dfs
(Depth-First Search) is defined withinuniquePermutations
. It operates by building permutations one element at a time:- It checks if the length of the current permutation under construction (
current_state
) is equal to the length ofnumbers
. If true, it adds this permutation toresult_set
and returns to explore other possibilities. - The function iterates over a frequency count (
count_map
) of the numbers. If a number's count is above zero, the number is added tocurrent_state
, and its count is decreased by one. After the recursive call todfs
, the number is removed fromcurrent_state
(backtracking), and its count is restored.
- It checks if the length of the current permutation under construction (
The recursive process starts by calling
dfs
with an empty list and a counter of the numbers (generated usingCounter(numbers)
from Python'scollections
module), which keeps track of the frequency of each number in the input list.The completed
result_set
, containing all unique permutations, is then returned.
Overall, this approach ensures that all permutations of the numbers are considered, while the use of backtracking prevents duplicates and ensures all permutations are unique. The frequency map (counter) aids in efficiently tracking and managing the elements during permutation generation.
No comments yet.