diff --git a/DIRECTORY.md b/DIRECTORY.md index 1b3282f2..8861d9b2 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -72,10 +72,14 @@ * [Test Max Profit Three](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/buy_sell_stock/test_max_profit_three.py) * [Test Max Profit Two](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/buy_sell_stock/test_max_profit_two.py) * [Test Max Profit With Fee](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/buy_sell_stock/test_max_profit_with_fee.py) + * Cherry Pickup + * [Test Cherry Pickup](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/cherry_pickup/test_cherry_pickup.py) * Climb Stairs * [Test Climb Stairs](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/climb_stairs/test_climb_stairs.py) * Coin Change * [Test Coin Change](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/coin_change/test_coin_change.py) + * Count Number Of Good Subsequences + * [Test Count Number Of Good Subsequences](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/count_number_of_good_subsequences/test_count_number_of_good_subsequences.py) * Countingbits * [Test Counting Bits](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/countingbits/test_counting_bits.py) * Decodeways @@ -109,6 +113,8 @@ * [Test Min Distance](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/min_distance/test_min_distance.py) * Min Path Sum * [Test Min Path Sum](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/min_path_sum/test_min_path_sum.py) + * Number Of People Aware Of A Secret + * [Test Number Of People Aware Of A Secret](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/number_of_people_aware_of_a_secret/test_number_of_people_aware_of_a_secret.py) * Painthouse * [Test Min Cost To Paint Houses](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/painthouse/test_min_cost_to_paint_houses.py) * Palindromic Substring @@ -161,6 +167,8 @@ * [Test Nearest Exit From Entrance](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/nearest_exit_from_entrance_in_maze/test_nearest_exit_from_entrance.py) * Network Delay Time * [Test Network Delay Time](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/network_delay_time/test_network_delay_time.py) + * Number Of Connected Components + * [Test Number Of Connected Components](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/number_of_connected_components/test_number_of_connected_components.py) * Number Of Islands * [Test Number Of Distinct Islands](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/number_of_islands/test_number_of_distinct_islands.py) * [Test Number Of Islands](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/number_of_islands/test_number_of_islands.py) @@ -213,14 +221,22 @@ * Two City Scheduling * [Test Two City Scheduling](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/greedy/two_city_scheduling/test_two_city_scheduling.py) * Hash Table + * Custom Sort String + * [Test Custom Sort String](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/custom_sort_string/test_custom_sort_string.py) + * Duplicate File In System + * [Test Find Duplicate Files In System](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/duplicate_file_in_system/test_find_duplicate_files_in_system.py) * First Unique Character * [Test First Unique Character](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/first_unique_character/test_first_unique_character.py) * Jewels And Stones * [Test Jewels And Stones](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/jewels_and_stones/test_jewels_and_stones.py) + * Maxlen Contiguous Binary Subarray + * [Test Maxlen Contiguous Binary Subarray](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/maxlen_contiguous_binary_subarray/test_maxlen_contiguous_binary_subarray.py) * Powerful Integers * [Test Powerful Integers](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/powerful_integers/test_powerful_integers.py) * Ransom Note * [Test Ransom Note](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/ransom_note/test_ransom_note.py) + * Word Pattern + * [Test Word Pattern](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/word_pattern/test_word_pattern.py) * Heap * Construct Target With Sums * [Test Construct Target With Sums](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/construct_target_with_sums/test_construct_target_with_sums.py) @@ -294,8 +310,12 @@ * Matrix * Best Meeting Point * [Test Best Meeting Point](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/matrix/best_meeting_point/test_best_meeting_point.py) + * Game Of Life + * [Test Game Of Life](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/matrix/game_of_life/test_game_of_life.py) * Isvalidsudoku * [Test Is Valid Sudoku](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/matrix/isvalidsudoku/test_is_valid_sudoku.py) + * Rotting Oranges + * [Test Rotting Oranges](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/matrix/rotting_oranges/test_rotting_oranges.py) * Transpose * [Test Transpose](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/matrix/transpose/test_transpose.py) * [Test Transpose Matrix](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/matrix/transpose/test_transpose_matrix.py) @@ -364,6 +384,8 @@ * Search Suggestions * [Test Search Suggestions](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/search/trie/search_suggestions/test_search_suggestions.py) * Sliding Window + * Frequency Of The Most Frequent Element + * [Test Frequency Of Most Frequent Element](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/sliding_window/frequency_of_the_most_frequent_element/test_frequency_of_most_frequent_element.py) * Length Of Longest Substring * [Test Length Of Longest Substring](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/sliding_window/length_of_longest_substring/test_length_of_longest_substring.py) * [Test Longest Substring Without Repeating Characters](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/sliding_window/length_of_longest_substring/test_longest_substring_without_repeating_characters.py) @@ -383,6 +405,9 @@ * [Test Repeated Dna Sequences](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/sliding_window/repeated_dna_sequences/test_repeated_dna_sequences.py) * Substring Concatenation * [Test Substring With Concatenation Of Words](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/sliding_window/substring_concatenation/test_substring_with_concatenation_of_words.py) + * Sort And Search + * Russian Doll Envelopes + * [Test Russian Doll Envelopes](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/sort_and_search/russian_doll_envelopes/test_russian_doll_envelopes.py) * Sorting * Heapsort * [Test Heap Sort](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/sorting/heapsort/test_heap_sort.py) @@ -432,6 +457,8 @@ * [Test Index Pairs Of A String](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/trie/index_pairs_of_a_string/test_index_pairs_of_a_string.py) * Longest Word With Prefixes * [Test Longest Word With Prefixes](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/trie/longest_word_with_prefixes/test_longest_word_with_prefixes.py) + * Replace Words + * [Test Replace Words](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/trie/replace_words/test_replace_words.py) * Topkfreqwords * [Test Top K Frequent Words](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/trie/topkfreqwords/test_top_k_frequent_words.py) * Two Pointers @@ -445,6 +472,10 @@ * [Test Count Pairs](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/count_pairs/test_count_pairs.py) * Find Sum Of Three * [Test Find Sum Of Three](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/find_sum_of_three/test_find_sum_of_three.py) + * Issubsequence + * [Test Is Subsequence](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/issubsequence/test_is_subsequence.py) + * Max Score + * [Test Max Score](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/max_score/test_max_score.py) * Merge Sorted Arrays * [Test Merge Sorted Arrays](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/merge_sorted_arrays/test_merge_sorted_arrays.py) * Move Zeroes @@ -486,6 +517,9 @@ * [Test Two Sum](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/two_sum_less_k/test_two_sum.py) * Valid Word Abbreviation * [Test Valid Word Abbreviation](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/valid_word_abbreviation/test_valid_word_abbreviation.py) + * Union Find + * Acounts Merge + * [Test Accounts Merge](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/union_find/acounts_merge/test_accounts_merge.py) * Unique Bsts * [Unique Bsts](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/unique_bsts/unique_bsts.py) * Word Count @@ -502,6 +536,8 @@ * [Test Number Of One Bits](https://github.com/BrianLusina/PythonSnips/blob/master/bit_manipulation/number_of_1_bits/test_number_of_one_bits.py) * Single Number * [Test Single Number](https://github.com/BrianLusina/PythonSnips/blob/master/bit_manipulation/single_number/test_single_number.py) + * Sum Of Subset Xor + * [Test Sum Of All Subset Xor Totals](https://github.com/BrianLusina/PythonSnips/blob/master/bit_manipulation/sum_of_subset_xor/test_sum_of_all_subset_xor_totals.py) * Sum Two Integers * [Test Sum Of Two Integers](https://github.com/BrianLusina/PythonSnips/blob/master/bit_manipulation/sum_two_integers/test_sum_of_two_integers.py) @@ -636,6 +672,8 @@ * [With Ordered Dict](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/lrucache/with_ordered_dict.py) * Map Sum * [Test Map Sum Pairs](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/map_sum/test_map_sum_pairs.py) + * Nested Iterator + * [Test Nested Iterator](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/nested_iterator/test_nested_iterator.py) * Orderedstream * [Test Ordered Stream](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/orderedstream/test_ordered_stream.py) * Queues @@ -658,6 +696,8 @@ * [Test Smallest Infinite Set](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/smallest_infinite_set/test_smallest_infinite_set.py) * Snapshot Array * [Test Snapshot Array](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/snapshot_array/test_snapshot_array.py) + * Sparse Vector + * [Test Sparse Vector](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/sparse_vector/test_sparse_vector.py) * Stacks * Maxstack * [Test Max Stack](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/stacks/maxstack/test_max_stack.py) @@ -923,8 +963,6 @@ * [Test Max Number Of Ksum Pairs](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/arrays/max_number_of_ksum_pairs/test_max_number_of_ksum_pairs.py) * Maximum Average Subarray * [Test Max Average Subarray](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/arrays/maximum_average_subarray/test_max_average_subarray.py) - * Maxlen Contiguous Binary Subarray - * [Test Maxlen Contiguous Binary Subarray](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/arrays/maxlen_contiguous_binary_subarray/test_maxlen_contiguous_binary_subarray.py) * Rotation * Cyclic Rotation * [Test Cyclic Rotation](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/arrays/rotation/cyclic_rotation/test_cyclic_rotation.py) @@ -1001,6 +1039,8 @@ * [Wraps](https://github.com/BrianLusina/PythonSnips/blob/master/pyfuncs/decorators/wraps.py) ## Pymath + * Adding Two Negabinary Numbers + * [Test Add Two Negabinary Numbers](https://github.com/BrianLusina/PythonSnips/blob/master/pymath/adding_two_negabinary_numbers/test_add_two_negabinary_numbers.py) * Divisible By 7 Not 5 * [Test Divisible By7 Not 5](https://github.com/BrianLusina/PythonSnips/blob/master/pymath/divisible_by_7_not_5/test_divisible_by7_not_5.py) * Even Digits @@ -1072,8 +1112,6 @@ * [Test Is Prefix Of Word](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/is_prefix/test_is_prefix_of_word.py) * Is Unique * [Test Is Unique](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/is_unique/test_is_unique.py) - * Issubsequence - * [Test Is Subsequence](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/issubsequence/test_is_subsequence.py) * Lexicographically Largest String * [Test Lexicographically Largest String](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/lexicographically_largest_string/test_lexicographically_largest_string.py) * Longest Common Prefix @@ -1259,7 +1297,6 @@ * [Test Queen Attack](https://github.com/BrianLusina/PythonSnips/blob/master/tests/puzzles/test_queen_attack.py) * [Test Queue To Do](https://github.com/BrianLusina/PythonSnips/blob/master/tests/puzzles/test_queue_to_do.py) * [Test Rectangle](https://github.com/BrianLusina/PythonSnips/blob/master/tests/puzzles/test_rectangle.py) - * [Test Replace Words](https://github.com/BrianLusina/PythonSnips/blob/master/tests/puzzles/test_replace_words.py) * [Test Robot Name](https://github.com/BrianLusina/PythonSnips/blob/master/tests/puzzles/test_robot_name.py) * [Test Scale Generator](https://github.com/BrianLusina/PythonSnips/blob/master/tests/puzzles/test_scale_generator.py) * [Test Scrabble](https://github.com/BrianLusina/PythonSnips/blob/master/tests/puzzles/test_scrabble.py) @@ -1461,6 +1498,7 @@ * [Json Files](https://github.com/BrianLusina/PythonSnips/blob/master/utils/json_files.py) * Network * [Urllib Module](https://github.com/BrianLusina/PythonSnips/blob/master/utils/network/urllib_module.py) + * [Test Utils](https://github.com/BrianLusina/PythonSnips/blob/master/utils/test_utils.py) * [User File](https://github.com/BrianLusina/PythonSnips/blob/master/utils/user_file.py) * Video Optimizer * [Video](https://github.com/BrianLusina/PythonSnips/blob/master/utils/video_optimizer/video.py) diff --git a/algorithms/dynamic_programming/count_number_of_good_subsequences/README.md b/algorithms/dynamic_programming/count_number_of_good_subsequences/README.md new file mode 100644 index 00000000..c72cc067 --- /dev/null +++ b/algorithms/dynamic_programming/count_number_of_good_subsequences/README.md @@ -0,0 +1,86 @@ +# Count the Number of Good Subsequences + +Given a string s, you need to count the number of good subsequences that can be formed from it. + +A subsequence is good if: + +- It is not empty +- Every character in the subsequence appears the same number of times + +For example, if we have a subsequence "aabb", it's good because 'a' appears 2 times and 'b' appears 2 times (both have +frequency 2). + +A subsequence is formed by deleting some (possibly zero) characters from the original string without changing the relative order of the remaining characters. + +Since the total count can be very large, return the answer modulo 10^9 + 7. + +Key Points: + +- You need to find all possible subsequences where each distinct character appears exactly the same number of times +- The subsequence must be non-empty +- Characters maintain their relative order from the original string +- The answer should be computed modulo 10^9 + 7 + +Example Understanding: If s = "aab", the good subsequences would include: + +Single characters: "a", "a", "b" (each character appears once) +"aa" (character 'a' appears twice) +"ab" (both 'a' and 'b' appear once each) + +## Constraints + +- 1 <= `s.length` <= 10^4 +- `s` will only contain lowercase English characters + +## Examples + +![Example 1](./images/examples/count_the_number_of_good_subsequences_example_1.png) +![Example 2](./images/examples/count_the_number_of_good_subsequences_example_2.png) +![Example 3](./images/examples/count_the_number_of_good_subsequences_example_3.png) +![Example 4](./images/examples/count_the_number_of_good_subsequences_example_4.png) +![Example 5](./images/examples/count_the_number_of_good_subsequences_example_5.png) + +## Topics + +- Math +- Combinatorics +- Counting +- Hash Table +- Dynamic Programming + +## Solution + +The solution to this problem involves calculating the combinations. We will start by calculating the frequency of each +character and then iterate over the frequencies, from 1 to the highest frequency of any character. We will count +subsequences where each character appears for some or all of its frequency, i.e., calculating combinations. The count is +summed up in each iteration, and after the loop terminates, 1 is deducted from the final count. The reason for deducting +1 is that the empty subsequence will also be counted. Because empty subsequence does not qualify as a good subsequence, +we will remove its count from the final calculation. + +Computing the combinations is a typical _Binomial Coefficient problem_, known as `n choose k` problem, which is in +mathematics written as `C(n, k) = n! / (k!ā‹…(nāˆ’k)!)`. Counting the combinations would require us to calculate the +factorial and division by factorial, calculated by multiplying with the modular inverses. + +Because calculating the factorial[i] requires calculating the [factorial[i-1]..factorial[2]] again and again, therefore +we will use dynamic programming to calculate the factorial[i] by multiplying i only with factorial[i — 1]. That is, we +will reuse the existing results instead of calculating them again. + +### Time Complexity + +There are 3 loops in the code: + +- The loop that populates the factorial and inverses array runs for `n` times, where `n` is the length of the input + string `s`. Therefore, its time complexity will be `O(n)` +- In the nested loop, the outer loop runs for `max_frequency` times. Because the max_frequency can have the maximum value + as `n`, therefore the outer loop will also take `O(n)`. The inner loop will run through all unique characters bounded by + 26 lowercase letters, which is a constant. The total time complexity of the nested loop will be `O(n*26)`, i.e. `O(n)` +- The loop in the method `quick_modular_inverse` runs until the exponent becomes 0. In each iteration, the exponent is + divided by 2. As the value of exponent is constant, it will not be dependent on the input, which means this loop will + also run for a constant time, i.e., `O(1)`. + +Therefore, the overall time complexity is `O(n + n + 1)` = `O(n)` + +### Space Complexity + +In this program, we use two arrays `factorials` and `inverse_factorial`, each taking O(N). Therefore, the space +complexity is O(N) diff --git a/algorithms/dynamic_programming/count_number_of_good_subsequences/__init__.py b/algorithms/dynamic_programming/count_number_of_good_subsequences/__init__.py new file mode 100644 index 00000000..c4817b26 --- /dev/null +++ b/algorithms/dynamic_programming/count_number_of_good_subsequences/__init__.py @@ -0,0 +1,93 @@ +from collections import Counter + + +def count_good_subsequences_with_combinatorics(s: str) -> int: + """ + Count the number of good subsequences of s. + A good subsequence has equal frequency for all characters in it + + Args: + s (str): input string + Returns: + int: number of good subsequences + """ + n = len(s) + 1 + mod = 10**9 + 7 + + # factorial[i] = i! mod MOD + factorial = [1] * n + # inverse_factorial[i] = (i!)^(-1) mod MOD + inverse_factorial = [1] * n + + def quick_modular_inverse(base, exponent, modulus): + """ + Method to find the modular inverse of a number + """ + # Initialize the result to 1, as 1 is the identity element for multiplication modulo modulus + result = 1 + + while exponent != 0: + # If exponent is odd, multiply result by base and take modulo modulus + if (exponent & 1) == 1: + result = result * base % modulus + # Right shift exponent by 1 (equivalent to dividing exponent by 2) + exponent >>= 1 + # Square base and take modulo modulus to reduce base in terms of modulus + base = base * base % modulus + + return result + + def comb(number_of_items, number_of_items_to_choose): + """ + Calculate binomial coefficient C(n, k) mod MOD. n choose k + + Args: + number_of_items: Total number of items + number_of_items_to_choose: Number of items to choose + + Returns: + C(n, k) mod MOD + """ + return ( + factorial[number_of_items] + * inverse_factorial[number_of_items_to_choose] + * inverse_factorial[number_of_items - number_of_items_to_choose] + % mod + ) + + # Precompute factorials and their modular inverses. + # Calculating the factorial and inverse of all numbers from 1 to n + # instead of calculating factorial of a number again and again + # we will store the factorial of a number i + # and use to calculate the factorial of a number i+1, since + # the factorial of a number i+1 is factorial of i-1 * i + for i in range(1, n): + factorial[i] = factorial[i - 1] * i % mod + # Using Fermat's Little Theorem: a^(-1) ≔ a^(p-2) (mod p) where p is prime. Note that either pow built in can b + # used or the quick_modular_inverse method can be used. + # inverse_factorial[i] = pow(factorial[i], mod - 2, mod) + inverse_factorial[i] = quick_modular_inverse(factorial[i], mod - 2, mod) + + # Count the frequency of each character in the string + char_frequency = Counter(s) + + final_count = 0 + + # Try each possible frequency (each character appears exactly i times) + max_frequency = max(char_frequency.values()) + + for target_freq in range(1, max_frequency + 1): + # Count ways to form subsequences where each character appears exactly target_freq + ways = 1 + + for char_count in char_frequency.values(): + # For this character, we can either: + # 1. Include it (choose target_freq occurrences from char_count) + # 2. Exclude it (multiply by 1) + # Total ways = C(char_count, target_freq) + 1) + if char_count >= target_freq: + ways = ways * (comb(char_count, target_freq) + 1) % mod + + final_count = (final_count + ways - 1) % mod + + return final_count diff --git a/algorithms/dynamic_programming/count_number_of_good_subsequences/images/examples/count_the_number_of_good_subsequences_example_1.png b/algorithms/dynamic_programming/count_number_of_good_subsequences/images/examples/count_the_number_of_good_subsequences_example_1.png new file mode 100644 index 00000000..4b509b13 Binary files /dev/null and b/algorithms/dynamic_programming/count_number_of_good_subsequences/images/examples/count_the_number_of_good_subsequences_example_1.png differ diff --git a/algorithms/dynamic_programming/count_number_of_good_subsequences/images/examples/count_the_number_of_good_subsequences_example_2.png b/algorithms/dynamic_programming/count_number_of_good_subsequences/images/examples/count_the_number_of_good_subsequences_example_2.png new file mode 100644 index 00000000..641bbe62 Binary files /dev/null and b/algorithms/dynamic_programming/count_number_of_good_subsequences/images/examples/count_the_number_of_good_subsequences_example_2.png differ diff --git a/algorithms/dynamic_programming/count_number_of_good_subsequences/images/examples/count_the_number_of_good_subsequences_example_3.png b/algorithms/dynamic_programming/count_number_of_good_subsequences/images/examples/count_the_number_of_good_subsequences_example_3.png new file mode 100644 index 00000000..01f6a9e2 Binary files /dev/null and b/algorithms/dynamic_programming/count_number_of_good_subsequences/images/examples/count_the_number_of_good_subsequences_example_3.png differ diff --git a/algorithms/dynamic_programming/count_number_of_good_subsequences/images/examples/count_the_number_of_good_subsequences_example_4.png b/algorithms/dynamic_programming/count_number_of_good_subsequences/images/examples/count_the_number_of_good_subsequences_example_4.png new file mode 100644 index 00000000..6754ee33 Binary files /dev/null and b/algorithms/dynamic_programming/count_number_of_good_subsequences/images/examples/count_the_number_of_good_subsequences_example_4.png differ diff --git a/algorithms/dynamic_programming/count_number_of_good_subsequences/images/examples/count_the_number_of_good_subsequences_example_5.png b/algorithms/dynamic_programming/count_number_of_good_subsequences/images/examples/count_the_number_of_good_subsequences_example_5.png new file mode 100644 index 00000000..6b741256 Binary files /dev/null and b/algorithms/dynamic_programming/count_number_of_good_subsequences/images/examples/count_the_number_of_good_subsequences_example_5.png differ diff --git a/algorithms/dynamic_programming/count_number_of_good_subsequences/test_count_number_of_good_subsequences.py b/algorithms/dynamic_programming/count_number_of_good_subsequences/test_count_number_of_good_subsequences.py new file mode 100644 index 00000000..7af8693a --- /dev/null +++ b/algorithms/dynamic_programming/count_number_of_good_subsequences/test_count_number_of_good_subsequences.py @@ -0,0 +1,27 @@ +import unittest +from parameterized import parameterized +from utils.test_utils import custom_test_name_func +from algorithms.dynamic_programming.count_number_of_good_subsequences import ( + count_good_subsequences_with_combinatorics, +) + +COUNT_NUMBER_OF_GOOD_SUBSEQUENCES_TEST_CASES = [ + ("ab", 3), + ("a", 1), + ("aba", 6), + ("abbcc", 20), + ("aabbcc", 33), +] + + +class CountNumberOfGoodSubsequencesTestCase(unittest.TestCase): + @parameterized.expand( + COUNT_NUMBER_OF_GOOD_SUBSEQUENCES_TEST_CASES, name_func=custom_test_name_func + ) + def test_count_good_subsequences_with_combinatorics(self, s: str, expected: int): + actual = count_good_subsequences_with_combinatorics(s) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main() diff --git a/pymath/adding_two_negabinary_numbers/README.md b/pymath/adding_two_negabinary_numbers/README.md new file mode 100644 index 00000000..a1ab64e7 --- /dev/null +++ b/pymath/adding_two_negabinary_numbers/README.md @@ -0,0 +1,41 @@ +# Adding Two Negabinary Numbers + +Given two numbers arr1 and arr2 in base -2, return the result of adding them together. + +Each number is given in array format: as an array of 0s and 1s, from most significant bit to least significant bit. +For example, arr = [1,1,0,1] represents the number (-2)^3 + (-2)^2 + (-2)^0 = -3. A number arr in array, format is also +guaranteed to have no leading zeros: either arr == [0] or arr[0] == 1. + +Return the result of adding arr1 and arr2 in the same format: as an array of 0s and 1s with no leading zeros. + +## Examples + +Example 1: +```text +Input: arr1 = [1,1,1,1,1], arr2 = [1,0,1] +Output: [1,0,0,0,0] +Explanation: arr1 represents 11, arr2 represents 5, the output represents 16. +``` + +Example 2: +```text +Input: arr1 = [0], arr2 = [0] +Output: [0] +``` + +Example 3: +```text +Input: arr1 = [0], arr2 = [1] +Output: [1] +``` + +## Constraints + +- 1 <= arr1.length, arr2.length <= 1000 +- arr1[i] and arr2[i] are 0 or 1 +- arr1 and arr2 have no leading zeros + +## Topics + +- Array +- Math diff --git a/pymath/adding_two_negabinary_numbers/__init__.py b/pymath/adding_two_negabinary_numbers/__init__.py new file mode 100644 index 00000000..a5aed2ee --- /dev/null +++ b/pymath/adding_two_negabinary_numbers/__init__.py @@ -0,0 +1,81 @@ +from typing import List + + +def add_negabinary(arr1: List[int], arr2: List[int]) -> List[int]: + # Initialize pointers to the least significant bits(rightmost elements) + index_1, index_2 = len(arr1) - 1, len(arr2) - 1 + + # Initialize carry value for addition + carry = 0 + + # Result to store the sum digits + result = [] + + # Process digits from right to left, including any remaining carry + while index_1 >= 0 or index_2 >= 0 or carry != 0: + # Get current digit from arr1, or 0 if we've exhausted arr1 + digit_1 = 0 if index_1 < 0 else arr1[index_1] + + # Get current digit from arr2, or 0 if we've exhausted arr2 + digit_2 = 0 if index_2 < 0 else arr2[index_2] + + # Calculate sum of current position including carry + current_sum = digit_1 + digit_2 + carry + + # Reset carry for next iteration + carry = 0 + + # Handle negabinary addition rules + if current_sum >= 2: + # If sum is 2 or more, subtract 2 and set negative carry + current_sum -= 2 + carry = -1 + elif current_sum == -1: + # If sum is -1, set digit to 1 and positive carry + current_sum = 1 + carry = 1 + + # Append the computed digit to result + result.append(current_sum) + + # Move pointest to the next more significant bits + index_1 -= 1 + index_2 -= 1 + + # Remove leading zeros from the result (except if result is just [0] + while len(result) > 1 and result[-1] == 0: + result.pop() + + # Reverse the result since we built it from least to most significant + return result[::-1] + + +def add_negabinary_2(arr1: List[int], arr2: List[int]) -> List[int]: + arr1 = arr1[::-1] + arr2 = arr2[::-1] + + max_len = max(len(arr1), len(arr2)) + + result = [] + carry = 0 + + i = 0 + while i < max_len or carry != 0: + bit1 = arr1[i] if i < len(arr1) else 0 + bit2 = arr2[i] if i < len(arr2) else 0 + + total = bit1 + bit2 + carry + + if total >= 0: + result.append(total % 2) + carry = -(total // 2) + else: + result.append(1) + carry = 1 + + i += 1 + + while len(result) > 1 and result[-1] == 0: + result.pop() + + return result[::-1] diff --git a/pymath/adding_two_negabinary_numbers/test_add_two_negabinary_numbers.py b/pymath/adding_two_negabinary_numbers/test_add_two_negabinary_numbers.py new file mode 100644 index 00000000..1c33317c --- /dev/null +++ b/pymath/adding_two_negabinary_numbers/test_add_two_negabinary_numbers.py @@ -0,0 +1,40 @@ +import unittest +from typing import List +from parameterized import parameterized +from utils.test_utils import custom_test_name_func +from pymath.adding_two_negabinary_numbers import add_negabinary, add_negabinary_2 + +ADD_TWO_NEGABINARY_NUMBERS_TEST_CASES = [ + ([0], [0], [0]), + ([0], [1], [1]), + ([1], [1], [1, 1, 0]), + ([1, 0], [1], [1, 1]), + ([1, 1, 1, 1, 1], [1, 0, 1], [1, 0, 0, 0, 0]), + ([1, 0, 0], [0], [1, 0, 0]), + ([1, 1, 0], [1, 0, 1], [1, 1, 0, 1, 1]), + ([1, 1], [1], [0]), +] + + +class AddTwoNegabinaryNumbersTestCase(unittest.TestCase): + @parameterized.expand( + ADD_TWO_NEGABINARY_NUMBERS_TEST_CASES, name_func=custom_test_name_func + ) + def test_add_two_negabinary_numbers( + self, arr1: List[int], arr2: List[int], expected: List[int] + ): + actual = add_negabinary(arr1, arr2) + self.assertEqual(expected, actual) + + @parameterized.expand( + ADD_TWO_NEGABINARY_NUMBERS_TEST_CASES, name_func=custom_test_name_func + ) + def test_add_two_negabinary_numbers_2( + self, arr1: List[int], arr2: List[int], expected: List[int] + ): + actual = add_negabinary_2(arr1, arr2) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main()