Skip to content

Commit bb8b965

Browse files
committed
feat(algorithms, greedy): minimum number of pushes to type word
1 parent 5b2cb66 commit bb8b965

File tree

40 files changed

+318
-0
lines changed

40 files changed

+318
-0
lines changed

puzzles/arrays/longest_increasing_subsequence/README.md renamed to algorithms/dynamic_programming/longest_increasing_subsequence/README.md

File renamed without changes.

puzzles/arrays/longest_increasing_subsequence/__init__.py renamed to algorithms/dynamic_programming/longest_increasing_subsequence/__init__.py

File renamed without changes.

puzzles/arrays/longest_increasing_subsequence/test_longest_increasing_subsequence.py renamed to algorithms/dynamic_programming/longest_increasing_subsequence/test_longest_increasing_subsequence.py

File renamed without changes.
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
# Minimum Number of Pushes to Type Word
2+
3+
You are given a string word containing lowercase English letters.
4+
5+
Telephone keypads have keys mapped with distinct collections of lowercase English letters, which can be used to form
6+
words by pushing them. For example, the key 2 is mapped with ["a","b","c"], we need to push the key one time to type
7+
"a", two times to type "b", and three times to type "c" .
8+
9+
It is allowed to remap the keys numbered 2 to 9 to distinct collections of letters. The keys can be remapped to any
10+
amount of letters, but each letter must be mapped to exactly one key. You need to find the minimum number of times the
11+
keys will be pushed to type the string word.
12+
13+
Return the minimum number of pushes needed to type word after remapping the keys.
14+
15+
An example mapping of letters to keys on a telephone keypad is given below. Note that 1, *, #, and 0 do not map to any
16+
letters.
17+
18+
![Example 1](images/examples/minimum_number_of_pushes_to_type_word_ii_sample.png)
19+
20+
![Example 1](images/examples/minimum_number_of_pushes_to_type_word_ii_example_1.png)
21+
22+
> Input: word = "abcde"
23+
> Output: 5
24+
> Explanation: The remapped keypad given in the image provides the minimum cost.
25+
> "a" -> one push on key 2
26+
> "b" -> one push on key 3
27+
> "c" -> one push on key 4
28+
> "d" -> one push on key 5
29+
> "e" -> one push on key 6
30+
> Total cost is 1 + 1 + 1 + 1 + 1 = 5.
31+
> It can be shown that no other mapping can provide a lower cost.
32+
33+
![Example 2](images/examples/minimum_number_of_pushes_to_type_word_ii_example_2.png)
34+
35+
> Input: word = "xyzxyzxyzxyz"
36+
> Output: 12
37+
> Explanation: The remapped keypad given in the image provides the minimum cost.
38+
> "x" -> one push on key 2
39+
> "y" -> one push on key 3
40+
> "z" -> one push on key 4
41+
> Total cost is 1 * 4 + 1 * 4 + 1 * 4 = 12
42+
> It can be shown that no other mapping can provide a lower cost.
43+
> Note that the key 9 is not mapped to any letter: it is not necessary to map letters to every key, but to map all the letters.
44+
45+
![Example 3](images/examples/minimum_number_of_pushes_to_type_word_ii_example_3.png)
46+
47+
> Input: word = "aabbccddeeffgghhiiiiii"
48+
> Output: 24
49+
> Explanation: The remapped keypad given in the image provides the minimum cost.
50+
> "a" -> one push on key 2
51+
> "b" -> one push on key 3
52+
> "c" -> one push on key 4
53+
> "d" -> one push on key 5
54+
> "e" -> one push on key 6
55+
> "f" -> one push on key 7
56+
> "g" -> one push on key 8
57+
> "h" -> two pushes on key 9
58+
> "i" -> one push on key 9
59+
> Total cost is 1 * 2 + 1 * 2 + 1 * 2 + 1 * 2 + 1 * 2 + 1 * 2 + 1 * 2 + 2 * 2 + 6 * 1 = 24.
60+
> It can be shown that no other mapping can provide a lower cost.
61+
62+
![Example 4](./images/examples/minimum_number_of_pushes_to_type_word_ii_example_4.png)
63+
![Example 5](./images/examples/minimum_number_of_pushes_to_type_word_ii_example_5.png)
64+
![Example 6](./images/examples/minimum_number_of_pushes_to_type_word_ii_example_6.png)
65+
66+
## Constraints
67+
68+
- 1 <= `word.length` <= 10^5
69+
- `word` consists of lowercase English letters
70+
71+
## Topics
72+
73+
- Hash Table
74+
- String
75+
- Greedy
76+
- Sorting
77+
- Counting
78+
79+
## Solution
80+
81+
1. [Greedy Sorting](#greedy-sorting)
82+
2. [Using a heap](#using-a-heap)
83+
84+
### Greedy Sorting
85+
86+
To solve this problem, we use a greedy algorithm approach combined with sorting. Keeping in mind that we have 8 keys
87+
available (2-9), the primary intuition is to remap the keys so the 8 most frequently occurring characters in the given
88+
string are assigned as first key presses, the next most common 8 characters as second key presses, and so on.
89+
90+
We begin by counting the occurrences of each letter using a counter, which provides the frequency of each distinct
91+
letter. Next, we sort these frequencies in descending order.
92+
93+
Since there are 8 possible key assignments, we'll divide the frequency rank by 8 to group it as a first, second, or
94+
third key press. Note that dividing the frequencies by 8 will result in 0, 1, and 2. We must add 1 to this group number
95+
to get the actual number of presses required for letters in that group. Multiplying this by the number of times the
96+
character appears in the given string yields the total number of presses for that letter.
97+
98+
Finally, we will sum the total presses required to type the word.
99+
100+
This greedy way, combined with sorting by frequency, ensures that each decision (assignment of letters to keys) is
101+
optimal for minimizing key presses.
102+
103+
#### Algorithm
104+
105+
- Initialize a frequency vector frequency of size 26 to store the count of each letter in the word.
106+
- Iterate through each character c in word and increment the count in frequency at the index corresponding to c - 'a'.
107+
- Sort the frequency vector in descending order to prioritize letters with higher counts.
108+
- Initialize a variable totalPushes to store the total number of key presses required.
109+
- Iterate through the sorted frequency vector:
110+
- If the frequency of a letter is zero, break the loop as there are no more letters to process.
111+
- Calculate the number of pushes for each letter based on its position in the sorted list: (i / 8 + 1) * frequency[i].
112+
> the number of pushes required to obtain a single instance of it is (i / 8 + 1). Because there are 8 available
113+
> keys (2 − 9), for the first 8 values of i (i = 0 to i = 7) corresponding to the first 8 frequent letters,
114+
> (i / 8 + 1) will give 1. For the next 8 values of i (i=8 to i=15) corresponding to the next 8 frequent letters,
115+
> (i / 8 + 1) will give 2, and this pattern continues.
116+
- Accumulate this value in totalPushes.
117+
- Return totalPushes as the minimum number of key presses required to type the word.
118+
119+
![Solution Greedy 1](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_1.png)
120+
![Solution Greedy 2](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_2.png)
121+
![Solution Greedy 3](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_3.png)
122+
![Solution Greedy 4](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_4.png)
123+
![Solution Greedy 5](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_5.png)
124+
![Solution Greedy 6](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_6.png)
125+
![Solution Greedy 7](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_7.png)
126+
![Solution Greedy 8](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_8.png)
127+
![Solution Greedy 9](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_9.png)
128+
![Solution Greedy 10](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_10.png)
129+
![Solution Greedy 11](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_11.png)
130+
![Solution Greedy 12](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_12.png)
131+
![Solution Greedy 13](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_13.png)
132+
![Solution Greedy 14](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_14.png)
133+
![Solution Greedy 15](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_15.png)
134+
![Solution Greedy 16](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_16.png)
135+
![Solution Greedy 17](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_17.png)
136+
![Solution Greedy 18](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_18.png)
137+
![Solution Greedy 19](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_greedy_19.png)
138+
139+
#### Complexity Analysis
140+
141+
Let `n` be the length of the string
142+
143+
##### Time Complexity
144+
145+
Iterating through the word string to count the frequency of each letter takes `O(n)`.
146+
147+
Sorting the frequency array, which has a fixed size of 26 (for each letter in the alphabet), takes O(1) because the size
148+
of the array is constant.
149+
150+
Iterating through the frequency array to compute the total number of presses is O(1) because the array size is constant.
151+
152+
Overall, the dominant term is O(n) due to the frequency counting step.
153+
154+
##### Space Complexity
155+
156+
Frequency array and sorting takes O(1) space, as it always requires space for 26 integers.
157+
158+
Overall, the space complexity is O(1) because the space used does not depend on the input size.
159+
160+
---
161+
162+
### Using a Heap
163+
164+
Following the initial approach that used sorting and a greedy strategy, we now explore a similar yet refined method.
165+
166+
First, we count the frequency of each character in the word using an unordered map (or dictionary), where each key
167+
represents a character, and its value indicates how many times it appears in the word.
168+
169+
Next, we use a priority queue (or max-heap) to efficiently manage these frequencies. The priority queue enables quick
170+
retrieval of the character with the highest frequency by giving the most frequent characters the highest priority.
171+
172+
As we process characters from the priority queue, we dynamically assign them to keys based on their frequencies.
173+
Specifically, at each iteration, we extract the character with the highest frequency and assign it to the key with the
174+
least number of characters assigned.
175+
176+
To facilitate this, we maintain a record of the number of letters assigned to each key press count. This helps us
177+
determine the next available key press count for assigning characters. For instance, once a key press count of 1 is
178+
fully utilized, we proceed to a key press count of 2, and so on.
179+
180+
We assign the character with the highest frequency to the least costly available key press count, updating our record to
181+
reflect this assignment and marking the key press count as occupied. This process continues until all characters are assigned.
182+
183+
Finally, we calculate the total number of key presses required by summing the product of each character’s frequency and
184+
its assigned key press count. This gives us the optimal total number of key presses needed to type the word.
185+
186+
#### Algorithm
187+
188+
- Create a frequency map frequencyMap to store the count of each letter in the input string word.
189+
- Iterate through word and for each character, increment its count in frequencyMap.
190+
- Create a priority queue frequencyQueue to store the frequencies of letters in descending order.
191+
- Iterate through frequencyMap and push each frequency into frequencyQueue.
192+
- Initialize a variable totalPushes to 0 to keep track of the total number of presses.
193+
- Initialize an index variable index to 0.
194+
- Calculate the total number of presses by processing the frequencies in the priority queue.
195+
- While frequencyQueue is not empty:
196+
- Add the product of (1 + (index / 8)) and the top frequency from frequencyQueue to totalPushes.
197+
- Remove the top element from frequencyQueue.
198+
- Increment index by 1.
199+
- Return totalPushes as the minimum number of presses needed.
200+
201+
> Note: As shown in Slide 4, when calculating totalPushes, we multiply by 1. This value represents frequencyQueue.top(),
202+
> which is 1 in the visual example.
203+
204+
![Solution Heap 1](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_heap_1.png)
205+
![Solution Heap 2](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_heap_2.png)
206+
![Solution Heap 3](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_heap_3.png)
207+
![Solution Heap 4](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_heap_4.png)
208+
![Solution Heap 5](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_heap_5.png)
209+
![Solution Heap 6](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_heap_6.png)
210+
![Solution Heap 7](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_heap_7.png)
211+
![Solution Heap 8](./images/solutions/minimum_number_of_pushes_to_type_word_ii_solution_heap_8.png)
212+
213+
#### Complexity Analysis
214+
215+
##### Time Complexity
216+
217+
Iterating through the word string to count the frequency of each letter takes O(n).
218+
219+
Inserting each frequency into the priority queue and extracting the maximum frequency both operate with a time
220+
complexity of O(klogk), where k represents the number of distinct letters. Each of these operations—insertions, and
221+
extractions—is logarithmic due to the heap structure of the priority queue. However, since the number of distinct
222+
letters is limited to a maximum of 26 (one for each letter in the alphabet), the size of the priority queue remains
223+
constant and thus the time complexity effectively becomes O(1) in practice.
224+
225+
Overall, the dominant term is O(n) due to the frequency counting step.
226+
227+
##### Space Complexity
228+
229+
The frequency map and priority queue take O(26)=O(1) space, as it always requires a fixed space for 26 integers.
230+
231+
Overall, the space complexity is O(1) because the space used does not depend on the input size.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from collections import Counter
2+
import heapq
3+
4+
5+
def minimum_pushes_greedy_with_sorting(word: str) -> int:
6+
# frequency vector of size 26 that storees the count of each letter in the word
7+
# Space complexity here is O(1) because we have a constant space for the given list, i.e. 26
8+
frequency = [0] * 26
9+
10+
# Iterate throuch each character and increment the count in the frequency at the index corresponding to char - "a"
11+
# since we know from the given constraints that we will have lowercase English letters, this will work fine
12+
# This is an O(n) operation, as each character in the word is iterated through
13+
for char in word:
14+
frequency[ord(char) - ord("a")] += 1
15+
16+
# Sort the frequencies in descending order to prioritize letters with higher counts
17+
# O(n log(n)) operation to handle sorting, with a space complexity of O(n) as Python uses in-memory space to handle
18+
# the sorting using timsort
19+
frequency.sort(reverse=True)
20+
21+
# total number of key presses required
22+
total_pushes = 0
23+
24+
# iterate through the sorted frequency
25+
for i in range(26):
26+
# if the frequency of a letter is zero, break the loop as there are no more letters to process
27+
if frequency[i] == 0:
28+
break
29+
# calculate the number of pushes for each letter based on its position in the sorted list (i / 8 + 1) * frequency[i]
30+
total_pushes += (i // 8 + 1) * frequency[i]
31+
32+
return total_pushes
33+
34+
35+
def minimum_pushes_heap(word: str) -> int:
36+
# frequency_map to store the count of each letter
37+
frequency_map = Counter(word)
38+
39+
# Priority queue/max heap to store frequencies in descending order
40+
frequency_queue = [-freq for freq in frequency_map.values()]
41+
heapq.heapify(frequency_queue)
42+
43+
# total number of key presses required
44+
total_pushes = 0
45+
index = 0
46+
47+
# iterate through the sorted frequency_map
48+
while frequency_queue:
49+
total_pushes += (1 + (index // 8)) * -heapq.heappop(frequency_queue)
50+
index += 1
51+
52+
return total_pushes
24.6 KB
Loading
25.6 KB
Loading
24.6 KB
Loading
57.1 KB
Loading
63.2 KB
Loading

0 commit comments

Comments
 (0)