Skip to content

Commit 6b8bcc5

Browse files
authored
Merge pull request #116 from BrianLusina/feat/longest-happy-string
feat(strings, heaps): longest happy string
2 parents 7cec5b1 + 68b4fd4 commit 6b8bcc5

23 files changed

+188
-5
lines changed

DIRECTORY.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@
8888
* Huffman
8989
* [Decoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/decoding.py)
9090
* [Encoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/encoding.py)
91+
* Intervals
92+
* Task Scheduler
93+
* [Test Task Scheduler](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/task_scheduler/test_task_scheduler.py)
9194
* Josephus Circle
9295
* [Test Josephus Circle](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/josephus_circle/test_josephus_circle.py)
9396
* Memoization
@@ -266,6 +269,10 @@
266269
* [Models](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/queues/priority/models.py)
267270
* Randomized Set
268271
* [Test Randomized Set](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/randomized_set/test_randomized_set.py)
272+
* Sets
273+
* Union Find
274+
* [Disjoint Set Union](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/sets/union_find/disjoint_set_union.py)
275+
* [Union Find](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/sets/union_find/union_find.py)
269276
* Smallest Infinite Set
270277
* [Test Smallest Infinite Set](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/smallest_infinite_set/test_smallest_infinite_set.py)
271278
* Snapshot Array
@@ -289,6 +296,7 @@
289296
* [Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/search_tree/avl/node.py)
290297
* [Bst Iterator](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/search_tree/bst_iterator.py)
291298
* [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)
299+
* [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)
292300
* [Test Binary Search Tree Insert](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/search_tree/test_binary_search_tree_insert.py)
293301
* [Test Binary Search Tree Search](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/search_tree/test_binary_search_tree_search.py)
294302
* [Test Utils](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/test_utils.py)
@@ -528,6 +536,7 @@
528536
* [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)
529537
* Number Of Islands
530538
* [Test Number Of Islands](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/number_of_islands/test_number_of_islands.py)
539+
* [Union Find](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/number_of_islands/union_find.py)
531540
* Number Of Provinces
532541
* [Test Number Of Provinces](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/number_of_provinces/test_number_of_provinces.py)
533542
* Reorder Routes
@@ -718,8 +727,6 @@
718727
* Group Anagrams
719728
* [Test Group Anagrams](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/anagram/group_anagrams/test_group_anagrams.py)
720729
* [Test Anagram](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/anagram/test_anagram.py)
721-
* Balanced Paren
722-
* [Test Balanced Paren](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/balanced_paren/test_balanced_paren.py)
723730
* Count Consonants
724731
* [Test Count Consonants](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/count_consonants/test_count_consonants.py)
725732
* Domain Name
@@ -736,8 +743,14 @@
736743
* [Test Is Unique](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/is_unique/test_is_unique.py)
737744
* Issubsequence
738745
* [Test Is Subsequence](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/issubsequence/test_is_subsequence.py)
746+
* Lexicographically Largest String
747+
* [Test Lexicographically Largest String](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/lexicographically_largest_string/test_lexicographically_largest_string.py)
748+
* Longest Common Prefix
749+
* [Test Longest Common Prefix](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/longest_common_prefix/test_longest_common_prefix.py)
739750
* Longest Common Suffix Queries
740751
* [Test Longest Common Suffix Queries](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/longest_common_suffix_queries/test_longest_common_suffix_queries.py)
752+
* Longest Happy String
753+
* [Test Longest Happy String](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/longest_happy_string/test_longest_happy_string.py)
741754
* Longest Self Contained Substring
742755
* [Test Longest Self Contained Substring](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/longest_self_contained_substring/test_longest_self_contained_substring.py)
743756
* Look And Say Sequence
@@ -760,6 +773,11 @@
760773
* [Test Palindrome Pairs](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/palindrome/test_palindrome_pairs.py)
761774
* Pangram
762775
* [Test Pangram Checker](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/pangram/test_pangram_checker.py)
776+
* Parenthesis
777+
* Balanced Paren
778+
* [Test Balanced Paren](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/parenthesis/balanced_paren/test_balanced_paren.py)
779+
* Remove Invalid Parenthesis
780+
* [Test Remove Invalid Parenthesis](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/parenthesis/remove_invalid_parenthesis/test_remove_invalid_parenthesis.py)
763781
* Permutation
764782
* [Test Check Permutation](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/permutation/test_check_permutation.py)
765783
* Reverse Vowels
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Longest Happy String
2+
3+
A string is considered happy if it meets the following conditions:
4+
5+
1. It comprises only the characters 'a', 'b', and 'c'.
6+
2. It does not contain the substrings "aaa", "bbb", or "ccc".
7+
3. The total occurrences of:
8+
- The character 'a' does not exceed a.
9+
- The character 'b' does not exceed b.
10+
- The character 'c' does not exceed c.
11+
12+
You are given three integers, a, b, and c, representing the maximum allowable occurrences of 'a', 'b', and 'c',
13+
respectively. Your task is to return the longest possible happy string. If there are multiple valid longest happy
14+
strings, return any one of them. If no such string can be formed, return an empty string "".
15+
16+
> Note: A substring is a contiguous sequence of characters within a string.
17+
18+
**Constraints**
19+
20+
- 0 ≤ `a`, `b`, `c` ≤ 100
21+
- a + b + c > 0
22+
23+
## Examples
24+
25+
![Example 1](./images/examples/longest_happy_string_example_1.png)
26+
![Example 2](./images/examples/longest_happy_string_example_2.png)
27+
![Example 3](./images/examples/longest_happy_string_example_3.png)
28+
29+
30+
## Solution
31+
32+
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.
33+
34+
Now, let’s look at the solution steps below:
35+
36+
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.
37+
38+
We initialize an empty list result to construct the happy string step by step.
39+
40+
We iterate until the heap, pq, is empty and perform the following:
41+
42+
We pop the character with the highest frequency from pq.
43+
44+
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:
45+
46+
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.
47+
48+
Push the original character back into the heap to try adding it later.
49+
50+
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.
51+
52+
Finally, we return the result as the longest happy string.
53+
54+
Note: In Python, strings are immutable, so we construct the result as a list and convert it to a string before returning.
55+
56+
Let’s look at the following illustration to get a better understanding of the solution:
57+
58+
![Longest Happy String Solution 1](./images/solutions/longest_happy_string_solution_1.png)
59+
![Longest Happy String Solution 2](./images/solutions/longest_happy_string_solution_2.png)
60+
![Longest Happy String Solution 3](./images/solutions/longest_happy_string_solution_3.png)
61+
![Longest Happy String Solution 4](./images/solutions/longest_happy_string_solution_4.png)
62+
![Longest Happy String Solution 5](./images/solutions/longest_happy_string_solution_5.png)
63+
![Longest Happy String Solution 6](./images/solutions/longest_happy_string_solution_6.png)
64+
![Longest Happy String Solution 7](./images/solutions/longest_happy_string_solution_7.png)
65+
![Longest Happy String Solution 8](./images/solutions/longest_happy_string_solution_8.png)
66+
![Longest Happy String Solution 9](./images/solutions/longest_happy_string_solution_9.png)
67+
![Longest Happy String Solution 10](./images/solutions/longest_happy_string_solution_10.png)
68+
![Longest Happy String Solution 11](./images/solutions/longest_happy_string_solution_11.png)
69+
![Longest Happy String Solution 12](./images/solutions/longest_happy_string_solution_12.png)
70+
![Longest Happy String Solution 13](./images/solutions/longest_happy_string_solution_13.png)
71+
![Longest Happy String Solution 14](./images/solutions/longest_happy_string_solution_14.png)
72+
![Longest Happy String Solution 15](./images/solutions/longest_happy_string_solution_15.png)
73+
74+
### Time complexity
75+
76+
The solution uses a max heap to select characters based on their frequency. Let’s break it down:
77+
78+
1. Adding up to 3 elements ('a', 'b', 'c') to the heap takes O(log3), which simplifies to O(1).
79+
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).
80+
3. Building the result string by appending characters takes O(k).
81+
82+
Combining these steps, the overall time complexity of the solution is O(k), where k=a+b+c.
83+
84+
### Space complexity
85+
86+
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.
87+
88+
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).
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from typing import List, Tuple
2+
from heapq import heappop, heappush
3+
4+
5+
def longest_diverse_string(a: int, b: int, c: int) -> str:
6+
# Priority queue (max heap) to store the counts of 'a', 'b', and 'c' as negative values
7+
max_heap: List[Tuple[int, str]] = []
8+
9+
# Push the counts of each character into the heap if they are greater than 0
10+
if a > 0:
11+
heappush(max_heap, (-a, "a")) # Push 'a' with its count
12+
if b > 0:
13+
heappush(max_heap, (-b, "b")) # Push 'b' with its count
14+
if c > 0:
15+
heappush(max_heap, (-c, "c")) # Push 'c' with its count
16+
17+
# List to store the characters of the resulting happy string
18+
result = []
19+
20+
# Process the heap until it's empty or no valid character can be added
21+
while max_heap:
22+
# Pop the character with the highest remaining frequency
23+
count, character = heappop(max_heap)
24+
count = -count # Convert back to positive
25+
26+
# Check if adding this character violates the "no three consecutive" rule
27+
if len(result) >= 2 and result[-1] == character and result[-2] == character:
28+
# If the rule is violated and no alternative character exists, break the loop
29+
if not max_heap:
30+
break
31+
32+
# Use the next most frequent character temporarily
33+
temp_cnt, temp_char = heappop(max_heap)
34+
result.append(temp_char) # Add the alternative character to the result
35+
36+
# Push the alternative character back with its updated count
37+
if (temp_cnt + 1) < 0:
38+
heappush(max_heap, (temp_cnt + 1, temp_char))
39+
40+
# Push the original character back to the heap to try adding it later
41+
heappush(max_heap, (-count, character))
42+
else:
43+
# If no violation, add the current character to the result
44+
count -= 1 # Decrease its count
45+
result.append(character)
46+
47+
# Push the character back into the heap if it still has remaining occurrences
48+
if count > 0:
49+
heappush(max_heap, (-count, character))
50+
51+
# Join the list into a string and return as the final result
52+
return "".join(result)
28 KB
Loading
26.6 KB
Loading
30.1 KB
Loading
52 KB
Loading
69.7 KB
Loading
77.7 KB
Loading
86.8 KB
Loading

0 commit comments

Comments
 (0)