diff --git a/DIRECTORY.md b/DIRECTORY.md index 36d6b342..6eb9887f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -88,6 +88,9 @@ * Huffman * [Decoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/decoding.py) * [Encoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/encoding.py) + * Intervals + * Task Scheduler + * [Test Task Scheduler](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/task_scheduler/test_task_scheduler.py) * Josephus Circle * [Test Josephus Circle](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/josephus_circle/test_josephus_circle.py) * Memoization @@ -266,6 +269,10 @@ * [Models](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/queues/priority/models.py) * Randomized Set * [Test Randomized Set](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/randomized_set/test_randomized_set.py) + * Sets + * Union Find + * [Disjoint Set Union](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/sets/union_find/disjoint_set_union.py) + * [Union Find](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/sets/union_find/union_find.py) * Smallest Infinite Set * [Test Smallest Infinite Set](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/smallest_infinite_set/test_smallest_infinite_set.py) * Snapshot Array @@ -289,6 +296,7 @@ * [Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/search_tree/avl/node.py) * [Bst Iterator](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/search_tree/bst_iterator.py) * [Test Binary Search Tree Delete Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/search_tree/test_binary_search_tree_delete_node.py) + * [Test Binary Search Tree Inorder Successor](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/search_tree/test_binary_search_tree_inorder_successor.py) * [Test Binary Search Tree Insert](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/search_tree/test_binary_search_tree_insert.py) * [Test Binary Search Tree Search](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/search_tree/test_binary_search_tree_search.py) * [Test Utils](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/test_utils.py) @@ -528,6 +536,7 @@ * [Test Nearest Exit From Entrance](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/nearest_exit_from_entrance_in_maze/test_nearest_exit_from_entrance.py) * Number Of Islands * [Test Number Of Islands](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/number_of_islands/test_number_of_islands.py) + * [Union Find](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/number_of_islands/union_find.py) * Number Of Provinces * [Test Number Of Provinces](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/number_of_provinces/test_number_of_provinces.py) * Reorder Routes @@ -718,8 +727,6 @@ * Group Anagrams * [Test Group Anagrams](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/anagram/group_anagrams/test_group_anagrams.py) * [Test Anagram](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/anagram/test_anagram.py) - * Balanced Paren - * [Test Balanced Paren](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/balanced_paren/test_balanced_paren.py) * Count Consonants * [Test Count Consonants](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/count_consonants/test_count_consonants.py) * Domain Name @@ -736,8 +743,14 @@ * [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 + * [Test Longest Common Prefix](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/longest_common_prefix/test_longest_common_prefix.py) * Longest Common Suffix Queries * [Test Longest Common Suffix Queries](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/longest_common_suffix_queries/test_longest_common_suffix_queries.py) + * Longest Happy String + * [Test Longest Happy String](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/longest_happy_string/test_longest_happy_string.py) * Longest Self Contained Substring * [Test Longest Self Contained Substring](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/longest_self_contained_substring/test_longest_self_contained_substring.py) * Look And Say Sequence @@ -760,6 +773,11 @@ * [Test Palindrome Pairs](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/palindrome/test_palindrome_pairs.py) * Pangram * [Test Pangram Checker](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/pangram/test_pangram_checker.py) + * Parenthesis + * Balanced Paren + * [Test Balanced Paren](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/parenthesis/balanced_paren/test_balanced_paren.py) + * Remove Invalid Parenthesis + * [Test Remove Invalid Parenthesis](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/parenthesis/remove_invalid_parenthesis/test_remove_invalid_parenthesis.py) * Permutation * [Test Check Permutation](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/permutation/test_check_permutation.py) * Reverse Vowels diff --git a/pystrings/longest_happy_string/README.md b/pystrings/longest_happy_string/README.md new file mode 100644 index 00000000..49cd3b83 --- /dev/null +++ b/pystrings/longest_happy_string/README.md @@ -0,0 +1,88 @@ +# Longest Happy String + +A string is considered happy if it meets the following conditions: + +1. It comprises only the characters 'a', 'b', and 'c'. +2. It does not contain the substrings "aaa", "bbb", or "ccc". +3. The total occurrences of: + - The character 'a' does not exceed a. + - The character 'b' does not exceed b. + - The character 'c' does not exceed c. + +You are given three integers, a, b, and c, representing the maximum allowable occurrences of 'a', 'b', and 'c', +respectively. Your task is to return the longest possible happy string. If there are multiple valid longest happy +strings, return any one of them. If no such string can be formed, return an empty string "". + +> Note: A substring is a contiguous sequence of characters within a string. + +**Constraints** + +- 0 ≤ `a`, `b`, `c` ≤ 100 +- a + b + c > 0 + +## Examples + +![Example 1](./images/examples/longest_happy_string_example_1.png) +![Example 2](./images/examples/longest_happy_string_example_2.png) +![Example 3](./images/examples/longest_happy_string_example_3.png) + + +## Solution + +The essence of this solution lies in constructing the longest happy string while adhering to the constraints of avoiding three consecutive identical characters. The approach uses the heaps pattern and prioritizes characters with the highest remaining frequency to maximize the length of the resulting string. A max heap keeps track of the most frequent characters to achieve this. The character with the highest frequency is added to the resulting string at each step, provided it does not violate the constraints. If adding the character would result in three consecutive identical characters, the next most frequent character is selected and added to the resulting string. By carefully managing character counts and maintaining priority with the heap, the solution ensures that valid characters are used to their maximum potential. This process continues until no valid characters can be added, resulting in the longest possible happy string or an empty string if no such string can be formed. + +Now, let’s look at the solution steps below: + +We initialize a max heap, pq, to store the input frequencies of 'a', 'b', and 'c'. We push the frequencies into pq, where each heap element is a tuple of the frequency and the corresponding character. + +We initialize an empty list result to construct the happy string step by step. + +We iterate until the heap, pq, is empty and perform the following: + +We pop the character with the highest frequency from pq. + +We handle repetition constraints by checking if the last two characters in the result are the same as the current character because adding it would violate the rule of avoiding three consecutive identical characters: + +In this case, check if another character is available in pq. If yes, pop it, append it to the result, and push it back into pq after decrementing its count. + +Push the original character back into the heap to try adding it later. + +Otherwise, if adding the current character does not violate the constraints, append it to the result, decrease its count by 1, and push it back into pq if there are remaining occurrences. + +Finally, we return the result as the longest happy string. + +Note: In Python, strings are immutable, so we construct the result as a list and convert it to a string before returning. + +Let’s look at the following illustration to get a better understanding of the solution: + +![Longest Happy String Solution 1](./images/solutions/longest_happy_string_solution_1.png) +![Longest Happy String Solution 2](./images/solutions/longest_happy_string_solution_2.png) +![Longest Happy String Solution 3](./images/solutions/longest_happy_string_solution_3.png) +![Longest Happy String Solution 4](./images/solutions/longest_happy_string_solution_4.png) +![Longest Happy String Solution 5](./images/solutions/longest_happy_string_solution_5.png) +![Longest Happy String Solution 6](./images/solutions/longest_happy_string_solution_6.png) +![Longest Happy String Solution 7](./images/solutions/longest_happy_string_solution_7.png) +![Longest Happy String Solution 8](./images/solutions/longest_happy_string_solution_8.png) +![Longest Happy String Solution 9](./images/solutions/longest_happy_string_solution_9.png) +![Longest Happy String Solution 10](./images/solutions/longest_happy_string_solution_10.png) +![Longest Happy String Solution 11](./images/solutions/longest_happy_string_solution_11.png) +![Longest Happy String Solution 12](./images/solutions/longest_happy_string_solution_12.png) +![Longest Happy String Solution 13](./images/solutions/longest_happy_string_solution_13.png) +![Longest Happy String Solution 14](./images/solutions/longest_happy_string_solution_14.png) +![Longest Happy String Solution 15](./images/solutions/longest_happy_string_solution_15.png) + +### Time complexity + +The solution uses a max heap to select characters based on their frequency. Let’s break it down: + +1. Adding up to 3 elements ('a', 'b', 'c') to the heap takes O(log3), which simplifies to O(1). +2. A character is popped from the heap at each step, processed, and potentially pushed back. For a total of k = a + b + c characters, each heap operation takes O(log3) simplifying to O(1), as the heap size is always at most 3. Therefore, the total time for heap operations is O(k⋅1)=O(k). +3. Building the result string by appending characters takes O(k). + +Combining these steps, the overall time complexity of the solution is O(k), where k=a+b+c. + +### Space complexity + +The heap stores at most three elements ('a', 'b', 'c'), requiring O(1) space, and the result string for the output requires O(k) space, where k=a+b+c. + +The space required by the result string for the output is not counted in the solution space. Therefore, the overall space complexity is O(1). diff --git a/pystrings/longest_happy_string/__init__.py b/pystrings/longest_happy_string/__init__.py new file mode 100644 index 00000000..7da0fde4 --- /dev/null +++ b/pystrings/longest_happy_string/__init__.py @@ -0,0 +1,52 @@ +from typing import List, Tuple +from heapq import heappop, heappush + + +def longest_diverse_string(a: int, b: int, c: int) -> str: + # Priority queue (max heap) to store the counts of 'a', 'b', and 'c' as negative values + max_heap: List[Tuple[int, str]] = [] + + # Push the counts of each character into the heap if they are greater than 0 + if a > 0: + heappush(max_heap, (-a, "a")) # Push 'a' with its count + if b > 0: + heappush(max_heap, (-b, "b")) # Push 'b' with its count + if c > 0: + heappush(max_heap, (-c, "c")) # Push 'c' with its count + + # List to store the characters of the resulting happy string + result = [] + + # Process the heap until it's empty or no valid character can be added + while max_heap: + # Pop the character with the highest remaining frequency + count, character = heappop(max_heap) + count = -count # Convert back to positive + + # Check if adding this character violates the "no three consecutive" rule + if len(result) >= 2 and result[-1] == character and result[-2] == character: + # If the rule is violated and no alternative character exists, break the loop + if not max_heap: + break + + # Use the next most frequent character temporarily + temp_cnt, temp_char = heappop(max_heap) + result.append(temp_char) # Add the alternative character to the result + + # Push the alternative character back with its updated count + if (temp_cnt + 1) < 0: + heappush(max_heap, (temp_cnt + 1, temp_char)) + + # Push the original character back to the heap to try adding it later + heappush(max_heap, (-count, character)) + else: + # If no violation, add the current character to the result + count -= 1 # Decrease its count + result.append(character) + + # Push the character back into the heap if it still has remaining occurrences + if count > 0: + heappush(max_heap, (-count, character)) + + # Join the list into a string and return as the final result + return "".join(result) diff --git a/pystrings/longest_happy_string/images/examples/longest_happy_string_example_1.png b/pystrings/longest_happy_string/images/examples/longest_happy_string_example_1.png new file mode 100644 index 00000000..4313cd15 Binary files /dev/null and b/pystrings/longest_happy_string/images/examples/longest_happy_string_example_1.png differ diff --git a/pystrings/longest_happy_string/images/examples/longest_happy_string_example_2.png b/pystrings/longest_happy_string/images/examples/longest_happy_string_example_2.png new file mode 100644 index 00000000..bce2a07c Binary files /dev/null and b/pystrings/longest_happy_string/images/examples/longest_happy_string_example_2.png differ diff --git a/pystrings/longest_happy_string/images/examples/longest_happy_string_example_3.png b/pystrings/longest_happy_string/images/examples/longest_happy_string_example_3.png new file mode 100644 index 00000000..c7bbbc38 Binary files /dev/null and b/pystrings/longest_happy_string/images/examples/longest_happy_string_example_3.png differ diff --git a/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_1.png b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_1.png new file mode 100644 index 00000000..3d906b06 Binary files /dev/null and b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_1.png differ diff --git a/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_10.png b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_10.png new file mode 100644 index 00000000..75e1b01c Binary files /dev/null and b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_10.png differ diff --git a/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_11.png b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_11.png new file mode 100644 index 00000000..c4b0ff54 Binary files /dev/null and b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_11.png differ diff --git a/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_12.png b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_12.png new file mode 100644 index 00000000..2b192160 Binary files /dev/null and b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_12.png differ diff --git a/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_13.png b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_13.png new file mode 100644 index 00000000..07b0c26c Binary files /dev/null and b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_13.png differ diff --git a/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_14.png b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_14.png new file mode 100644 index 00000000..b29fa285 Binary files /dev/null and b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_14.png differ diff --git a/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_15.png b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_15.png new file mode 100644 index 00000000..c15bbb6f Binary files /dev/null and b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_15.png differ diff --git a/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_2.png b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_2.png new file mode 100644 index 00000000..80abfec5 Binary files /dev/null and b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_2.png differ diff --git a/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_3.png b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_3.png new file mode 100644 index 00000000..999eb40f Binary files /dev/null and b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_3.png differ diff --git a/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_4.png b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_4.png new file mode 100644 index 00000000..90d9cba6 Binary files /dev/null and b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_4.png differ diff --git a/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_5.png b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_5.png new file mode 100644 index 00000000..5921c559 Binary files /dev/null and b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_5.png differ diff --git a/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_6.png b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_6.png new file mode 100644 index 00000000..12b92eb0 Binary files /dev/null and b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_6.png differ diff --git a/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_7.png b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_7.png new file mode 100644 index 00000000..36099c6b Binary files /dev/null and b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_7.png differ diff --git a/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_8.png b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_8.png new file mode 100644 index 00000000..e029fd4c Binary files /dev/null and b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_8.png differ diff --git a/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_9.png b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_9.png new file mode 100644 index 00000000..2e3a86db Binary files /dev/null and b/pystrings/longest_happy_string/images/solutions/longest_happy_string_solution_9.png differ diff --git a/pystrings/longest_happy_string/test_longest_happy_string.py b/pystrings/longest_happy_string/test_longest_happy_string.py new file mode 100644 index 00000000..17975251 --- /dev/null +++ b/pystrings/longest_happy_string/test_longest_happy_string.py @@ -0,0 +1,25 @@ +import unittest +from parameterized import parameterized +from pystrings.longest_happy_string import longest_diverse_string + + +class LongestHappyStringTestCase(unittest.TestCase): + @parameterized.expand( + [ + (2, 2, 2, "abcabc"), + (0, 5, 5, "bcbcbcbcbc"), + (6, 3, 0, "aabaabaab"), + (3, 3, 1, "abababc"), + (2, 2, 1, "ababc"), + (5, 1, 0, "aabaa"), + (7, 2, 0, "aabaabaa"), + (1, 1, 7, "ccaccbcc"), + ] + ) + def test_longest_diverse_string(self, a: int, b: int, c: int, expected): + actual = longest_diverse_string(a, b, c) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main() diff --git a/pystrings/similar_string_groups/__init__.py b/pystrings/similar_string_groups/__init__.py index 0a4bdb91..6d41e602 100644 --- a/pystrings/similar_string_groups/__init__.py +++ b/pystrings/similar_string_groups/__init__.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Tuple from datastructures import DisjointSetUnion, UnionFind @@ -54,8 +54,8 @@ def is_similar(s1: str, s2: str) -> bool: # Helper: Decide if two strings are similar -def are_similar(s1, s2): - diff = [] +def are_similar(s1: str, s2: str): + diff: List[Tuple[str, str]] = [] for a, b in zip(s1, s2): if a != b: diff.append((a, b))