diff --git a/DIRECTORY.md b/DIRECTORY.md index 25fd4680..465438f2 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -157,6 +157,9 @@ * [Test Minimum Moves To Spread Stones](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/greedy/spread_stones/test_minimum_moves_to_spread_stones.py) * 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 + * Ransom Note + * [Test Ransom Note](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/ransom_note/test_ransom_note.py) * Heap * Kclosestelements * [Test Find K Closest Elements](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/kclosestelements/test_find_k_closest_elements.py) diff --git a/algorithms/hash_table/__init__.py b/algorithms/hash_table/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/algorithms/dictionary_square_keys/README.md b/algorithms/hash_table/dictionary_square_keys/README.md similarity index 100% rename from algorithms/dictionary_square_keys/README.md rename to algorithms/hash_table/dictionary_square_keys/README.md diff --git a/algorithms/dictionary_square_keys/__init__.py b/algorithms/hash_table/dictionary_square_keys/__init__.py similarity index 100% rename from algorithms/dictionary_square_keys/__init__.py rename to algorithms/hash_table/dictionary_square_keys/__init__.py diff --git a/algorithms/hash_table/ransom_note/README.md b/algorithms/hash_table/ransom_note/README.md new file mode 100644 index 00000000..68fe895f --- /dev/null +++ b/algorithms/hash_table/ransom_note/README.md @@ -0,0 +1,68 @@ +# Ransom Note + +Given two strings, ransom_note and magazine, check if ransom_note can be constructed using the letters from magazine. +Return TRUE if it can be constructed, FALSE otherwise. + +> Note: A ransom note is a written message that can be constructed by using the letters available in the given magazine. +> The magazine can have multiple instances of the same letter. Each instance of the letter in the magazine can only be +> used once to construct the ransom note. + +## Constraints + +- 1 <= `ransom_note.length`, `magazine.length` <= 10^3 +- The `ransom_note` and `magazine` consist of lowercase English letters + +## Examples + +![Example 1](./images/examples/ransom_note_example_1.png) +![Example 2](./images/examples/ransom_note_example_2.png) +![Example 3](./images/examples/ransom_note_example_3.png) +![Example 4](./images/examples/ransom_note_example_4.png) + +## Topics + +- Hash Table +- String +- Counting + +## Solution +An optimized approach to solve this problem is to keep track of the occurrences of characters using the hash map. We +store the frequency of each character of the magazine in the hash map. After storing the frequencies, we iterate over +each character in the ransom note and check if the character is present in the hash map and its frequency is greater +than zero. If it is, we decrement the frequency by 1, indicating that we’ve used that character to construct the ransom +note. If the character is not present in the hash map or its frequency is 0, we immediately return FALSE since it's +impossible to construct the ransom note. + +If we successfully iterate through all characters in the ransom note without encountering a character that is not present +in the hash map or its frequency is 0, we return TRUE, indicating that we can construct the ransom note from the +characters available in the magazine. + +![Solution 1](./images/solutions/ransom_note_solution_1.png) +![Solution 2](./images/solutions/ransom_note_solution_2.png) +![Solution 3](./images/solutions/ransom_note_solution_3.png) +![Solution 4](./images/solutions/ransom_note_solution_4.png) +![Solution 5](./images/solutions/ransom_note_solution_5.png) +![Solution 6](./images/solutions/ransom_note_solution_6.png) +![Solution 7](./images/solutions/ransom_note_solution_7.png) +![Solution 8](./images/solutions/ransom_note_solution_8.png) +![Solution 9](./images/solutions/ransom_note_solution_9.png) +![Solution 10](./images/solutions/ransom_note_solution_10.png) +![Solution 11](./images/solutions/ransom_note_solution_11.png) + +### Summary + +- Create a hash map to store the frequencies of each character in the magazine. +- Iterate through each character in the ransom note and check the following conditions: + - If the character is not in the hash map or the frequency of the character is 0, return FALSE + - Otherwise, decrement the frequency of the character in the hash map by 1. +- Return TRUE if we successfully iterate through all characters in the ransom note. + +### Time Complexity + +The time complexity of this solution is O(n+m), where n is the length of the ransom note and m is the length of the +magazine. + +### Space Complexity + +The space complexity of this solution is O(1) because we have a constant number of lowercase English letters +(26 unique characters). Therefore, the space required by the hash map will remain constant. diff --git a/algorithms/hash_table/ransom_note/__init__.py b/algorithms/hash_table/ransom_note/__init__.py new file mode 100644 index 00000000..dea95b8f --- /dev/null +++ b/algorithms/hash_table/ransom_note/__init__.py @@ -0,0 +1,71 @@ +from collections import Counter + + +def can_construct(ransom_note: str, magazine: str) -> bool: + """ + Checks if it is possible to construct a ransom note from the given letters in the magazine where each letter in the + magazine can only be used once. If a letter in the magazine occurs more than once, it can only be used that many + number of times, so `magazine=aabc`, means we can use letter a twice, but not more than that. + + Complexity Analysis: + + Time O(n + m): Where n is the number of letters in `ransom_note` and `m` is the number of letters in `magazine`. This + is because we iterate through the magazine to count occurrences of the number of letters and again through ransom_note + to check if each letter is present in the magazine + + Space O(1): Since there are only English letters which are 26, the space used by the hash table is always count going + to be constant. + + Args: + ransom_note(str): the string with which we intend to construct from the magazine. + magazine(str): the string with which we intend to use to construct ransom_note + Returns: + bool: True if we can construct ransom note from the magazine, false otherwise. + """ + # No need to proceed if we don't have a magazine to construct a ransom note from + if not magazine: + return False + + # Count the number of occurrences of each letter in the magazine. This will be used to keep track of the number of + # letters we can use when constructing the ransom note + occurrences = Counter(magazine) + + # Iterate through each letter in the ransom note to check if it is in the magazine + for letter in ransom_note: + # If a letter does not exist in the frequency map of the magazine or the count has now become 0 meaning we can't + # use the letter from the magazine, then there is no need to proceed with the iteration, we can't construct + # the ransom note + if letter not in occurrences or occurrences[letter] == 0: + return False + + # if the letter is in the occurrences, we decrease the count of the occurrences of the letter and the number + # of letters left to construct the ransom note + occurrences[letter] -= 1 + + return True + + +def can_construct_2(ransom_note: str, magazine: str) -> bool: + # create an empty hash map to store the frequency of each character in the magazine string + frequency = {} + + for char in magazine: + # if character is already present in hash map then increment + # its frequency by 1 + if char in frequency: + frequency[char] += 1 + + # else count its first occurrence + else: + frequency[char] = 1 + + for char in ransom_note: + # if the character is not in the hash map or its count is 0, return False + if char not in frequency or frequency[char] == 0: + return False + + # otherwise, decrease the character's frequency in the hash map by 1 + else: + frequency[char] -= 1 + + return True diff --git a/algorithms/hash_table/ransom_note/images/examples/ransom_note_example_1.png b/algorithms/hash_table/ransom_note/images/examples/ransom_note_example_1.png new file mode 100644 index 00000000..4405977e Binary files /dev/null and b/algorithms/hash_table/ransom_note/images/examples/ransom_note_example_1.png differ diff --git a/algorithms/hash_table/ransom_note/images/examples/ransom_note_example_2.png b/algorithms/hash_table/ransom_note/images/examples/ransom_note_example_2.png new file mode 100644 index 00000000..43443f7d Binary files /dev/null and b/algorithms/hash_table/ransom_note/images/examples/ransom_note_example_2.png differ diff --git a/algorithms/hash_table/ransom_note/images/examples/ransom_note_example_3.png b/algorithms/hash_table/ransom_note/images/examples/ransom_note_example_3.png new file mode 100644 index 00000000..51b8d8df Binary files /dev/null and b/algorithms/hash_table/ransom_note/images/examples/ransom_note_example_3.png differ diff --git a/algorithms/hash_table/ransom_note/images/examples/ransom_note_example_4.png b/algorithms/hash_table/ransom_note/images/examples/ransom_note_example_4.png new file mode 100644 index 00000000..f007182c Binary files /dev/null and b/algorithms/hash_table/ransom_note/images/examples/ransom_note_example_4.png differ diff --git a/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_1.png b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_1.png new file mode 100644 index 00000000..9e037d47 Binary files /dev/null and b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_1.png differ diff --git a/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_10.png b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_10.png new file mode 100644 index 00000000..d07ad76a Binary files /dev/null and b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_10.png differ diff --git a/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_11.png b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_11.png new file mode 100644 index 00000000..3397a022 Binary files /dev/null and b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_11.png differ diff --git a/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_2.png b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_2.png new file mode 100644 index 00000000..d7dd93ba Binary files /dev/null and b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_2.png differ diff --git a/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_3.png b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_3.png new file mode 100644 index 00000000..a79771fd Binary files /dev/null and b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_3.png differ diff --git a/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_4.png b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_4.png new file mode 100644 index 00000000..b2b97982 Binary files /dev/null and b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_4.png differ diff --git a/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_5.png b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_5.png new file mode 100644 index 00000000..0702829d Binary files /dev/null and b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_5.png differ diff --git a/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_6.png b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_6.png new file mode 100644 index 00000000..b5bd8e2e Binary files /dev/null and b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_6.png differ diff --git a/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_7.png b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_7.png new file mode 100644 index 00000000..26d60a91 Binary files /dev/null and b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_7.png differ diff --git a/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_8.png b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_8.png new file mode 100644 index 00000000..1fa77579 Binary files /dev/null and b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_8.png differ diff --git a/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_9.png b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_9.png new file mode 100644 index 00000000..76987800 Binary files /dev/null and b/algorithms/hash_table/ransom_note/images/solutions/ransom_note_solution_9.png differ diff --git a/algorithms/hash_table/ransom_note/test_ransom_note.py b/algorithms/hash_table/ransom_note/test_ransom_note.py new file mode 100644 index 00000000..6052fdf3 --- /dev/null +++ b/algorithms/hash_table/ransom_note/test_ransom_note.py @@ -0,0 +1,37 @@ +import unittest +from parameterized import parameterized +from algorithms.hash_table.ransom_note import can_construct, can_construct_2 + +RANSOM_NOTE_TEST_CASES = [ + ( + "long_note_success", + "codinginterviewquestions", + "aboincsdefoetingvqtniewonoregessnutins", + True, + ), + ("missing_letter", "code", "coingd", False), + ("shuffled_letters", "codinginterview", "vieewidingcodinter", True), + ("subset_of_magazine", "program", "programming", True), + ("repeated_letters", "me", "meme", True), + ("single_char_mismatch", "a", "b", False), + ("insufficient_repeated_char", "aa", "ab", False), + ("sufficient_repeated_char", "aa", "aab", True), + ("empty_ransom_note", "", "abc", True), + ("empty_magazine", "a", "", False), +] + + +class RansomNoteTestCase(unittest.TestCase): + @parameterized.expand(RANSOM_NOTE_TEST_CASES) + def test_can_construct(self, _, ransom_note: str, magazine: str, expected: bool): + actual = can_construct(ransom_note, magazine) + self.assertEqual(expected, actual) + + @parameterized.expand(RANSOM_NOTE_TEST_CASES) + def test_can_construct_2(self, _, ransom_note: str, magazine: str, expected: bool): + actual = can_construct_2(ransom_note, magazine) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main() diff --git a/datastructures/linked_lists/circular/__init__.py b/datastructures/linked_lists/circular/__init__.py index 980cc9f8..2a87f13f 100644 --- a/datastructures/linked_lists/circular/__init__.py +++ b/datastructures/linked_lists/circular/__init__.py @@ -1,7 +1,7 @@ -from typing import Optional, Any, Union, Tuple, Self, List +from typing import Optional, Any, Union, Tuple, List -from datastructures.linked_lists import LinkedList, Node, T -from .node import CircularNode +from datastructures.linked_lists import LinkedList, T +from datastructures.linked_lists.circular.node import CircularNode class CircularLinkedList(LinkedList): @@ -86,7 +86,7 @@ def unshift(self, node): def shift(self): pass - def pop(self) -> Optional[Node]: + def pop(self) -> Optional[CircularNode]: pass def delete_node(self, node_: CircularNode): @@ -183,7 +183,7 @@ def delete_node_by_key(self, key: Any): def delete_nodes_by_key(self, key: T): pass - def delete_middle_node(self) -> Optional[Node]: + def delete_middle_node(self) -> Optional[CircularNode]: pass def display(self): @@ -198,7 +198,7 @@ def display_backward(self): def alternate_split(self): pass - def split_list(self) -> Optional[Tuple[Self, Optional[Self]]]: + def split_list(self) -> Optional[Tuple[CircularNode, Optional[CircularNode]]]: """ Splits a circular linked list into two halves and returns the two halves in a tuple. If the size is 0, i.e. no nodes are in this linked list, then it returns None. If the size is 1, then the first portion of the tuple, at @@ -233,30 +233,30 @@ def split_list(self) -> Optional[Tuple[Self, Optional[Self]]]: second_list.append(current.data) - return self, second_list + return self.head, second_list def is_palindrome(self) -> bool: pass - def pairwise_swap(self) -> Node: + def pairwise_swap(self) -> CircularNode: pass - def swap_nodes_at_kth_and_k_plus_1(self, k: int) -> Node: + def swap_nodes_at_kth_and_k_plus_1(self, k: int) -> CircularNode: pass - def move_to_front(self, node: Node): + def move_to_front(self, node: CircularNode): pass def move_tail_to_head(self): pass - def partition(self, data: Any) -> Union[Node, None]: + def partition(self, data: Any) -> Union[CircularNode, None]: pass def remove_tail(self): pass - def remove_duplicates(self) -> Optional[Node]: + def remove_duplicates(self) -> Optional[CircularNode]: pass def rotate(self, k: int): @@ -265,7 +265,7 @@ def rotate(self, k: int): def reverse_groups(self, k: int): pass - def odd_even_list(self) -> Optional[Node]: + def odd_even_list(self) -> Optional[CircularNode]: pass def maximum_pair_sum(self) -> int: diff --git a/datastructures/linked_lists/circular/test_circular_linked_list.py b/datastructures/linked_lists/circular/test_circular_linked_list.py index 93801fad..6e3fd85f 100644 --- a/datastructures/linked_lists/circular/test_circular_linked_list.py +++ b/datastructures/linked_lists/circular/test_circular_linked_list.py @@ -1,5 +1,5 @@ import unittest -from . import CircularLinkedList +from datastructures.linked_lists.circular import CircularLinkedList class CircularLinkedListAppendTestCase(unittest.TestCase): @@ -65,10 +65,13 @@ def test_1(self): for d in data: circular_linked_list.append(d) - first_list, second_list = circular_linked_list.split_list() + result = circular_linked_list.split_list() + self.assertIsNotNone(result) - self.assertEqual(expected[0], list(first_list)) - self.assertEqual(expected[1], list(second_list)) + first_list_head, second_list_head = result + + self.assertEqual(expected[0], list(first_list_head)) + self.assertEqual(expected[1], list(second_list_head)) if __name__ == "__main__": diff --git a/datastructures/linked_lists/linked_list_utils.py b/datastructures/linked_lists/linked_list_utils.py index 6fdb0218..7ed97a37 100644 --- a/datastructures/linked_lists/linked_list_utils.py +++ b/datastructures/linked_lists/linked_list_utils.py @@ -136,6 +136,7 @@ def remove_cycle(head: Optional[Node]) -> Optional[Node]: return head + def remove_nth_from_end(head: Optional[Node], n: int) -> Optional[Node]: """ Removes the nth node from a linked list from the head given the head of the linked list and the position from the @@ -172,4 +173,3 @@ def remove_nth_from_end(head: Optional[Node], n: int) -> Optional[Node]: # Return the modified head node of the linked list with the node removed return head -