Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
88 changes: 88 additions & 0 deletions pystrings/longest_happy_string/README.md
Original file line number Diff line number Diff line change
@@ -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).
52 changes: 52 additions & 0 deletions pystrings/longest_happy_string/__init__.py
Original file line number Diff line number Diff line change
@@ -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)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions pystrings/longest_happy_string/test_longest_happy_string.py
Original file line number Diff line number Diff line change
@@ -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()
6 changes: 3 additions & 3 deletions pystrings/similar_string_groups/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List
from typing import List, Tuple
from datastructures import DisjointSetUnion, UnionFind


Expand Down Expand Up @@ -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))
Expand Down
Loading