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
3 changes: 3 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment thread
BrianLusina marked this conversation as resolved.
* Heap
* Kclosestelements
* [Test Find K Closest Elements](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/kclosestelements/test_find_k_closest_elements.py)
Expand Down
Empty file.
68 changes: 68 additions & 0 deletions algorithms/hash_table/ransom_note/README.md
Original file line number Diff line number Diff line change
@@ -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.
71 changes: 71 additions & 0 deletions algorithms/hash_table/ransom_note/__init__.py
Original file line number Diff line number Diff line change
@@ -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
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.
37 changes: 37 additions & 0 deletions algorithms/hash_table/ransom_note/test_ransom_note.py
Original file line number Diff line number Diff line change
@@ -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()
26 changes: 13 additions & 13 deletions datastructures/linked_lists/circular/__init__.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand All @@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import unittest
from . import CircularLinkedList
from datastructures.linked_lists.circular import CircularLinkedList


class CircularLinkedListAppendTestCase(unittest.TestCase):
Expand Down Expand Up @@ -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__":
Expand Down
2 changes: 1 addition & 1 deletion datastructures/linked_lists/linked_list_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Loading