diff --git a/DIRECTORY.md b/DIRECTORY.md index a15d17c3..410c8120 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -38,6 +38,11 @@ * [Test Generate Permutations](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/backtracking/permutations/generate_permutations/test_generate_permutations.py) * Restore Ip Addresses * [Test Restore Ip Addresses](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/backtracking/restore_ip_addresses/test_restore_ip_addresses.py) + * Subsets + * Cascading Subsets + * [Test Cascading Subsets](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/backtracking/subsets/cascading_subsets/test_cascading_subsets.py) + * Find All Subsets + * [Test Find All Subsets](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/backtracking/subsets/find_all_subsets/test_find_all_subsets.py) * Word Search * [Constants](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/backtracking/word_search/constants.py) * [Point](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/backtracking/word_search/point.py) @@ -307,11 +312,6 @@ * Stack * Daily Temperatures * [Test Daily Temperatures](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/stack/daily_temperatures/test_daily_temperatures.py) - * Subsets - * Cascading Subsets - * [Test Cascading Subsets](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/subsets/cascading_subsets/test_cascading_subsets.py) - * Find All Subsets - * [Test Find All Subsets](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/subsets/find_all_subsets/test_find_all_subsets.py) * Taxi Numbers * [Taxi Numbers](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/taxi_numbers/taxi_numbers.py) * Top K Elements diff --git a/algorithms/subsets/__init__.py b/algorithms/backtracking/subsets/__init__.py similarity index 100% rename from algorithms/subsets/__init__.py rename to algorithms/backtracking/subsets/__init__.py diff --git a/algorithms/subsets/cascading_subsets/README.md b/algorithms/backtracking/subsets/cascading_subsets/README.md similarity index 100% rename from algorithms/subsets/cascading_subsets/README.md rename to algorithms/backtracking/subsets/cascading_subsets/README.md diff --git a/algorithms/subsets/cascading_subsets/__init__.py b/algorithms/backtracking/subsets/cascading_subsets/__init__.py similarity index 100% rename from algorithms/subsets/cascading_subsets/__init__.py rename to algorithms/backtracking/subsets/cascading_subsets/__init__.py diff --git a/algorithms/subsets/cascading_subsets/test_cascading_subsets.py b/algorithms/backtracking/subsets/cascading_subsets/test_cascading_subsets.py similarity index 93% rename from algorithms/subsets/cascading_subsets/test_cascading_subsets.py rename to algorithms/backtracking/subsets/cascading_subsets/test_cascading_subsets.py index d538abe6..b955e2da 100644 --- a/algorithms/subsets/cascading_subsets/test_cascading_subsets.py +++ b/algorithms/backtracking/subsets/cascading_subsets/test_cascading_subsets.py @@ -1,6 +1,6 @@ import unittest -from algorithms.subsets.cascading_subsets import each_cons +from algorithms.backtracking.subsets.cascading_subsets import each_cons class CascadingSubsetsTestCase(unittest.TestCase): diff --git a/algorithms/subsets/find_all_subsets/README.md b/algorithms/backtracking/subsets/find_all_subsets/README.md similarity index 55% rename from algorithms/subsets/find_all_subsets/README.md rename to algorithms/backtracking/subsets/find_all_subsets/README.md index 0fa1064c..a9e11c0a 100644 --- a/algorithms/subsets/find_all_subsets/README.md +++ b/algorithms/backtracking/subsets/find_all_subsets/README.md @@ -22,3 +22,20 @@ Given an array of integers, nums, find all possible subsets of nums, including t - Bit Manipulation - Backtracking - Arrays + +--- +# Subsets II + +Given an integer array nums, that can contain duplicate elements, return all possible subsets while ensuring that each +subset is unique. The output must include unique subsets, and you may return them in any order. + +## Constraints + +- 1 <= `nums.length` <= 10 +- -10 <= `nums[i]` <= 10 + +## Examples + +![Example 1](images/examples/find_all_subsets_ii_example_1.png) +![Example 2](images/examples/find_all_subsets_ii_example_2.png) +![Example 3](images/examples/find_all_subsets_ii_example_3.png) diff --git a/algorithms/backtracking/subsets/find_all_subsets/__init__.py b/algorithms/backtracking/subsets/find_all_subsets/__init__.py new file mode 100644 index 00000000..25c19797 --- /dev/null +++ b/algorithms/backtracking/subsets/find_all_subsets/__init__.py @@ -0,0 +1,65 @@ +from typing import List + + +def find_all_subsets(nums: List[int]) -> List[List[int]]: + n = len(nums) + + if n == 0: + return [] + + subsets = [] + + def backtrack(first, curr): + # Add the current subset to the output + subsets.append(curr[:]) + # Generate subsets starting from the current index + for i in range(first, n): + curr.append(nums[i]) + backtrack(i + 1, curr) + curr.pop() + + backtrack(0, []) + return subsets + + +def find_all_subsets_with_duplicates(nums: List[int]) -> List[List[int]]: + n = len(nums) + + if n == 0: + return [] + + # Sort in place to ensure that duplicate elements are next to each other, which will be used to skip over the duplicate + # elements in the iteration. + # Time complexity here is O(n log(n)) and space complexity is O(n) due to Timsort requiring space to handle sorting + # in place + nums.sort() + + subsets: List[int] = [] + result: List[List[int]] = [] + + def backtrack(idx: int) -> None: + """ + Backtrack to create subsets from the given current index of the element being considered + Args: + idx(int): index of the element being considered. + """ + # Base case, if we reach the end of the list, we include the current subset to the result + if idx == n: + result.append(subsets[:]) + return + + # Add the current subset to the output + # Include the current element to the subset and move to the next element + subsets.append(nums[idx]) + backtrack(idx + 1) + # After recursion, remove it to backtrack and explore the excluded path + subsets.pop() + + # Skip the current element(exclude path), but skip duplicates too. If there are duplicate elements, skip them all + # when choosing exclude path. This ensures that we only take one unique subset for each duplicate group + while idx + 1 < n and nums[idx] == nums[idx + 1]: + idx += 1 + backtrack(idx + 1) + + backtrack(0) + return result diff --git a/algorithms/subsets/find_all_subsets/images/examples/find_all_subsets_example_1.png b/algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_example_1.png similarity index 100% rename from algorithms/subsets/find_all_subsets/images/examples/find_all_subsets_example_1.png rename to algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_example_1.png diff --git a/algorithms/subsets/find_all_subsets/images/examples/find_all_subsets_example_2.png b/algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_example_2.png similarity index 100% rename from algorithms/subsets/find_all_subsets/images/examples/find_all_subsets_example_2.png rename to algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_example_2.png diff --git a/algorithms/subsets/find_all_subsets/images/examples/find_all_subsets_example_3.png b/algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_example_3.png similarity index 100% rename from algorithms/subsets/find_all_subsets/images/examples/find_all_subsets_example_3.png rename to algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_example_3.png diff --git a/algorithms/subsets/find_all_subsets/images/examples/find_all_subsets_example_4.png b/algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_example_4.png similarity index 100% rename from algorithms/subsets/find_all_subsets/images/examples/find_all_subsets_example_4.png rename to algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_example_4.png diff --git a/algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_ii_example_1.png b/algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_ii_example_1.png new file mode 100644 index 00000000..59fb031d Binary files /dev/null and b/algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_ii_example_1.png differ diff --git a/algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_ii_example_2.png b/algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_ii_example_2.png new file mode 100644 index 00000000..3b873590 Binary files /dev/null and b/algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_ii_example_2.png differ diff --git a/algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_ii_example_3.png b/algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_ii_example_3.png new file mode 100644 index 00000000..39e7cfe6 Binary files /dev/null and b/algorithms/backtracking/subsets/find_all_subsets/images/examples/find_all_subsets_ii_example_3.png differ diff --git a/algorithms/backtracking/subsets/find_all_subsets/test_find_all_subsets.py b/algorithms/backtracking/subsets/find_all_subsets/test_find_all_subsets.py new file mode 100644 index 00000000..368374ff --- /dev/null +++ b/algorithms/backtracking/subsets/find_all_subsets/test_find_all_subsets.py @@ -0,0 +1,87 @@ +import unittest +from typing import List +from parameterized import parameterized +from algorithms.backtracking.subsets.find_all_subsets import ( + find_all_subsets, + find_all_subsets_with_duplicates, +) + +FIND_ALL_SUBSETS_TEST_CASES = [ + ([], []), + ([9], [[], [9]]), + ([1], [[], [1]]), + ([0], [[], [0]]), + ([2, 4], [[], [2], [4], [2, 4]]), + ([1, 2], [[], [1], [2], [1, 2]]), + ([2, 5, 7], [[], [2], [5], [2, 5], [7], [2, 7], [5, 7], [2, 5, 7]]), + ( + [1, 2, 3, 4], + [ + [], + [1], + [2], + [1, 2], + [3], + [1, 3], + [2, 3], + [1, 2, 3], + [4], + [1, 4], + [2, 4], + [1, 2, 4], + [3, 4], + [1, 3, 4], + [2, 3, 4], + [1, 2, 3, 4], + ], + ), + ([3, 6, 9], [[], [3], [3, 6], [3, 6, 9], [3, 9], [6], [6, 9], [9]]), + ([1, 2, 3], [[], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]]), +] + +FIND_ALL_SUBSETS_WITH_DUPLICATES_TEST_CASES = [ + ([0], [[], [0]]), + ([-1, 1], [[], [-1], [-1, 1], [1]]), + ([1, 2, 2], [[], [1], [1, 2], [1, 2, 2], [2], [2, 2]]), + ([0, 0], [[], [0], [0, 0]]), + ([-2, -2, 1], [[], [-2], [-2, -2], [-2, -2, 1], [-2, 1], [1]]), + ([1, 1], [[], [1], [1, 1]]), + ( + [4, 4, 4, 1, 4], + [ + [], + [1], + [1, 4], + [1, 4, 4], + [1, 4, 4, 4], + [1, 4, 4, 4, 4], + [4], + [4, 4], + [4, 4, 4], + [4, 4, 4, 4], + ], + ), +] +FIND_ALL_SUBSETS_WITH_DUPLICATES_TEST_CASES.extend(FIND_ALL_SUBSETS_TEST_CASES) + + +class FindAllSubsetsTestCase(unittest.TestCase): + @parameterized.expand(FIND_ALL_SUBSETS_TEST_CASES) + def test_find_all_subsets(self, nums: List[int], expected: List[List[int]]): + actual = find_all_subsets(nums) + expected_sorted = sorted(expected) + actual_sorted = sorted(actual) + self.assertEqual(expected_sorted, actual_sorted) + + @parameterized.expand(FIND_ALL_SUBSETS_WITH_DUPLICATES_TEST_CASES) + def test_find_all_subsets_with_duplicates( + self, nums: List[int], expected: List[List[int]] + ): + actual = find_all_subsets_with_duplicates(nums) + expected_sorted = sorted(expected) + actual_sorted = sorted(actual) + self.assertEqual(expected_sorted, actual_sorted) + + +if __name__ == "__main__": + unittest.main() diff --git a/algorithms/subsets/find_all_subsets/__init__.py b/algorithms/subsets/find_all_subsets/__init__.py deleted file mode 100644 index 016f6af7..00000000 --- a/algorithms/subsets/find_all_subsets/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import List - - -def find_all_subsets(nums: List[int]) -> List[List[int]]: - n = len(nums) - - if n == 0: - return [] - - subsets = [] - - def backtrack(first, curr): - # Add the current subset to the output - subsets.append(curr[:]) - # Generate subsets starting from the current index - for i in range(first, n): - curr.append(nums[i]) - backtrack(i + 1, curr) - curr.pop() - - backtrack(0, []) - return subsets diff --git a/algorithms/subsets/find_all_subsets/test_find_all_subsets.py b/algorithms/subsets/find_all_subsets/test_find_all_subsets.py deleted file mode 100644 index c237417b..00000000 --- a/algorithms/subsets/find_all_subsets/test_find_all_subsets.py +++ /dev/null @@ -1,47 +0,0 @@ -import unittest -from typing import List -from parameterized import parameterized -from algorithms.subsets.find_all_subsets import find_all_subsets - -FIND_ALL_SUBSETS_TEST_CASES = [ - ([], []), - ([9], [[], [9]]), - ([1], [[], [1]]), - ([0], [[], [0]]), - ([2, 4], [[], [2], [4], [2, 4]]), - ([1, 2], [[], [1], [2], [1, 2]]), - ([2, 5, 7], [[], [2], [5], [2, 5], [7], [2, 7], [5, 7], [2, 5, 7]]), - ( - [1, 2, 3, 4], - [ - [], - [1], - [2], - [1, 2], - [3], - [1, 3], - [2, 3], - [1, 2, 3], - [4], - [1, 4], - [2, 4], - [1, 2, 4], - [3, 4], - [1, 3, 4], - [2, 3, 4], - [1, 2, 3, 4], - ], - ), - ([3, 6, 9], [[], [3], [3, 6], [3, 6, 9], [3, 9], [6], [6, 9], [9]]), -] - - -class FindAllSubsetsTestCase(unittest.TestCase): - @parameterized.expand(FIND_ALL_SUBSETS_TEST_CASES) - def test_find_all_subsets(self, nums: List[int], expected: List[List[int]]): - actual = find_all_subsets(nums) - self.assertEqual(expected, actual) - - -if __name__ == "__main__": - unittest.main()