Skip to content

Commit 38b0722

Browse files
committed
feat(algorithms, dynamic programming minimum path sum): find the minimum path sum in a grid
1 parent 2289c9d commit 38b0722

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1412
-40
lines changed

algorithms/arrays/find_missing_elem/README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Find Missing element
1+
# Find Missing Element
22

33
An array A consisting of N different integers is given. The array contains integers in the range [1..(N + 1)], which
44
means that exactly one element is missing.
@@ -40,3 +40,8 @@ within the range [1..(N + 1)].
4040
- Binary Search
4141
- Bit Manipulation
4242
- Sorting
43+
44+
---
45+
46+
Find Missing Numbers
47+

algorithms/arrays/find_missing_elem/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,7 @@ def find_missing_number_sum_of_n_terms(nums):
5555
expected_sum = len_nums * (len_nums + 1) // 2
5656

5757
return expected_sum - total_sum
58+
59+
60+
def find_missing_numbers(nums: List[int]) -> List[int]:
61+
pass

algorithms/dynamic_programming/min_path_sum/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,39 @@ grows quadratically.
5353
### Space Complexity
5454

5555
The space complexity is O(n) since we only maintain a single working array `dp` with one entry per row.
56+
57+
---
58+
59+
## Min Path Sum in Grid
60+
61+
Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right, which minimizes the sum
62+
of all numbers along its path.
63+
64+
> Note: You can only move either down or right at any point in time.
65+
66+
Example 1
67+
```text
68+
Input: grid = [[1,3,1],[1,5,1],[4,2,1]]
69+
Output: 7
70+
Explanation: Because the path 1 → 3 → 1 → 1 → 1 minimizes the sum.
71+
```
72+
73+
Example 2:
74+
75+
```text
76+
Input: grid = [[1,2,3],[4,5,6]]
77+
Output: 12
78+
```
79+
80+
### Constraints
81+
82+
- m == grid.length
83+
- n == grid[i].length
84+
- 1 <= m, n <= 200
85+
- 0 <= grid[i][j] <= 200
86+
87+
## Topics
88+
89+
- Array
90+
- Dynamic Programming
91+
- Matrix

algorithms/dynamic_programming/min_path_sum/__init__.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import List
22

33

4-
def min_path_sum(triangle: List[List[int]]) -> int:
4+
def min_path_sum_in_triangle(triangle: List[List[int]]) -> int:
55
"""
66
Finds the minimum path sum in a given triangle of numbers
77
This uses bottom up dynamic programming by starting at the bottom, we ensure that every decision made at a higher row
@@ -32,7 +32,7 @@ def min_path_sum(triangle: List[List[int]]) -> int:
3232
return triangle[0][0]
3333

3434

35-
def min_path_sum_2(triangle: List[List[int]]) -> int:
35+
def min_path_sum_in_triangle_2(triangle: List[List[int]]) -> int:
3636
"""
3737
Finds the minimum path sum in a given triangle of numbers
3838
This uses bottom up dynamic programming by starting at the bottom, we ensure that every decision made at a higher row
@@ -60,3 +60,46 @@ def min_path_sum_2(triangle: List[List[int]]) -> int:
6060

6161
# the result trickles to the apex
6262
return dp[0]
63+
64+
65+
def min_path_sum_grid(grid: List[List[int]]) -> int:
66+
# m = number of rows, n = number of columns
67+
m = len(grid)
68+
n = len(grid[0])
69+
70+
# Iterate over every cell in row-major order
71+
for i in range(m):
72+
for j in range(n):
73+
if i == 0 and j > 0:
74+
# First row but not [0][0]: we can only come from the left
75+
grid[i][j] += grid[i][j - 1]
76+
elif j == 0 and i > 0:
77+
# First column but not [0][0]: we can only come from above
78+
grid[i][j] += grid[i - 1][j]
79+
elif i > 0 and j > 0:
80+
# For all other cells, choose the minimum of:
81+
# - the path sum from above (i-1, j)
82+
# - the path sum from the left (i, j-1)
83+
grid[i][j] += min(grid[i - 1][j], grid[i][j - 1])
84+
85+
# The bottom-right cell now contains the minimum path sum
86+
return grid[m - 1][n - 1]
87+
88+
89+
def min_path_sum_grid_2(grid: List[List[int]]) -> int:
90+
if not grid or not grid[0]:
91+
return 0
92+
93+
n_rows, n_cols = len(grid), len(grid[0])
94+
95+
for i in range(1, n_rows):
96+
grid[i][0] += grid[i - 1][0]
97+
98+
for i in range(1, n_cols):
99+
grid[0][i] += grid[0][i - 1]
100+
101+
for r in range(1, n_rows):
102+
for c in range(1, n_cols):
103+
grid[r][c] += min(grid[r - 1][c], grid[r][c - 1])
104+
105+
return grid[-1][-1]

algorithms/dynamic_programming/min_path_sum/test_min_path_sum.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,50 @@
22
import copy
33
from typing import List
44
from parameterized import parameterized
5-
from algorithms.dynamic_programming.min_path_sum import min_path_sum, min_path_sum_2
5+
from algorithms.dynamic_programming.min_path_sum import min_path_sum_in_triangle, min_path_sum_in_triangle_2, min_path_sum_grid, min_path_sum_grid_2
66

7-
TEST_CASES = [
7+
MIN_PATH_SUM_TRIANGLE_TEST_CASES = [
88
([[5]], 5),
99
([[2], [3, 4]], 5),
1010
([[1000], [2000, 3000]], 3000),
1111
([[2], [3, 4], [6, 5, 7], [4, 1, 8, 3]], 11),
1212
([[7], [3, 8], [8, 1, 0], [2, 7, 4, 4], [4, 5, 2, 6, 5]], 17),
1313
]
1414

15+
MIN_PATH_SUM_GRID_TEST_CASES = [
16+
([[1,3,1],[1,5,1],[4,2,1]], 7),
17+
([[1,2,3],[4,5,6]], 12),
18+
([[1, 2, 5], [3, 2, 1]], 6),
19+
([[5, 9, 1, 3], [4, 2, 1, 7], [3, 1, 1, 2]], 15),
20+
([[5]], 5),
21+
([[1, 0, 0], [1, 1, 0], [1, 1, 1]], 2),
22+
([[7, 1, 3, 2], [2, 5, 10, 1], [4, 2, 1, 3]], 17),
23+
]
1524

16-
class MinPathSumInTriangleTestCase(unittest.TestCase):
17-
@parameterized.expand(TEST_CASES)
25+
26+
class MinPathSumTestCase(unittest.TestCase):
27+
@parameterized.expand(MIN_PATH_SUM_TRIANGLE_TEST_CASES)
1828
def test_min_path_sum_in_triangle(self, triangle: List[List[int]], expected: int):
1929
input_triangle = copy.deepcopy(triangle)
20-
actual = min_path_sum(input_triangle)
30+
actual = min_path_sum_in_triangle(input_triangle)
2131
self.assertEqual(expected, actual)
2232

23-
@parameterized.expand(TEST_CASES)
33+
@parameterized.expand(MIN_PATH_SUM_TRIANGLE_TEST_CASES)
2434
def test_min_path_sum_2_in_triangle(self, triangle: List[List[int]], expected: int):
2535
input_triangle = copy.deepcopy(triangle)
26-
actual = min_path_sum_2(input_triangle)
36+
actual = min_path_sum_in_triangle_2(input_triangle)
37+
self.assertEqual(expected, actual)
38+
39+
@parameterized.expand(MIN_PATH_SUM_GRID_TEST_CASES)
40+
def test_min_path_sum_in_grid(self, grid: List[List[int]], expected: int):
41+
input_triangle = copy.deepcopy(grid)
42+
actual = min_path_sum_grid(input_triangle)
43+
self.assertEqual(expected, actual)
44+
45+
@parameterized.expand(MIN_PATH_SUM_GRID_TEST_CASES)
46+
def test_min_path_sum_2_in_grid(self, grid: List[List[int]], expected: int):
47+
input_triangle = copy.deepcopy(grid)
48+
actual = min_path_sum_grid_2(input_triangle)
2749
self.assertEqual(expected, actual)
2850

2951

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Top K Frequent Words
2+
3+
Given a list of strings words and an integer k, return the k most frequently occurring strings.
4+
5+
Note: The result should be sorted in descending order based on frequency. If multiple words have the same frequency,
6+
they should be sorted in lexicographical order.
7+
8+
## Constraints
9+
10+
- 1 <= words.length <= 500
11+
- 1 <= words[i].length <= 10
12+
- words[i] consists of lowercase English letters.
13+
- k is in the range [1, The number of unique words[i]]
14+
15+
## Examples
16+
17+
Example 1:
18+
```text
19+
Input: words = ["i","love","leetcode","i","love","coding"], k = 2
20+
Output: ["i","love"]
21+
Explanation: "i" and "love" are the two most frequent words.
22+
Note that "i" comes before "love" due to a lower alphabetical order.
23+
```
24+
25+
Example 2:
26+
```text
27+
Input: words = ["the","day","is","sunny","the","the","the","sunny","is","is"], k = 4
28+
Output: ["the","is","sunny","day"]
29+
Explanation: "the", "is", "sunny" and "day" are the four most frequent words, with the number of occurrence being 4, 3,
30+
2 and 1 respectively.
31+
```
32+
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from typing import List, Optional
2+
from collections import defaultdict, Counter
3+
from datastructures.trees.trie import Trie
4+
5+
6+
def top_k_frequent_words(words: List[str], k: int) -> List[str]:
7+
frequency_map = defaultdict(int)
8+
buckets: List[Optional[Trie]] = [None] * (len(words) + 1)
9+
top_k = []
10+
11+
for word in words:
12+
frequency_map[word] += 1
13+
14+
for word, frequency in frequency_map.items():
15+
if buckets[frequency] is None:
16+
buckets[frequency] = Trie()
17+
buckets[frequency].insert(word)
18+
19+
for i in range(len(buckets) - 1, -1, -1):
20+
if buckets[i] is not None:
21+
retrieve_words = buckets[i].get_all_words()
22+
if len(retrieve_words) < k:
23+
top_k.extend(retrieve_words)
24+
k -= len(retrieve_words)
25+
else:
26+
top_k.extend(retrieve_words[:k])
27+
break
28+
return top_k
29+
30+
31+
def top_k_frequent_words_2(words: List[str], k: int) -> List[str]:
32+
word_freq = Counter(sorted(words))
33+
34+
top_k = word_freq.most_common(k)
35+
36+
return [w for w, c in top_k]
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import unittest
2+
from typing import List
3+
4+
from parameterized import parameterized
5+
6+
from algorithms.trie.topkfreqwords import top_k_frequent_words_2, top_k_frequent_words
7+
8+
TOP_K_FREQUENT_WORDS_TEST_CASES = [
9+
(["i", "love", "leetcode", "i", "love", "coding"], 2, ["i", "love"]),
10+
(
11+
["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"],
12+
4,
13+
["the", "is", "sunny", "day"],
14+
),
15+
(["i", "love", "leetcode", "i", "love", "coding"], 3, ["i", "love", "coding"]),
16+
]
17+
18+
19+
class TopKFrequentWordsTestCase(unittest.TestCase):
20+
@parameterized.expand(TOP_K_FREQUENT_WORDS_TEST_CASES)
21+
def test_top_k_frequent_words(self, words: List[str], k: int, expected: List[str]):
22+
actual = top_k_frequent_words(words, k)
23+
self.assertEqual(expected, actual)
24+
25+
@parameterized.expand(TOP_K_FREQUENT_WORDS_TEST_CASES)
26+
def test_top_k_frequent_words_2(
27+
self, words: List[str], k: int, expected: List[str]
28+
):
29+
actual = top_k_frequent_words_2(words, k)
30+
self.assertEqual(expected, actual)
31+
32+
33+
if __name__ == "__main__":
34+
unittest.main()

datastructures/linked_lists/singly_linked_list/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -855,7 +855,7 @@ We will first traverse the linked list and check which groups of k nodes can be
855855
the head.
856856
- We set a pointer, `ptr`, equal to the `dummy` node. We will use this pointer to traverse the linked list.
857857
- We traverse the linked list till `ptr` becomes NULL:
858-
- We initialize a pointer, tracker, to ptr. This pointer will be used to keep track of the number of nodes in the
858+
- We initialize a pointer, `tracker`, to `ptr`. This pointer will be used to keep track of the number of nodes in the
859859
current group in the linked list.
860860
- We use a nested loop to try to move `tracker` _k_ nodes forward in the linked list. If tracker becomes NULL before
861861
moving _k_ nodes forward, the end of the linked list has been reached and the current group can not be traversed,

datastructures/lists/missing_array/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Missing Array
2+
13
You get an array of arrays. If you sort the arrays by their length, you will see, that their length-values are
24
consecutive. But one array is missing!
35

0 commit comments

Comments
 (0)