Skip to content

Commit 1f56d49

Browse files
committed
LC 403. Frog Jump (Rust Memo)
1 parent 0319057 commit 1f56d49

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ Solutions to LeetCode problems. The first column links to the problem in LeetCod
233233
| [393. UTF-8 Validation][lc393] | 🟠 Medium | [![python](res/py.png)][lc393py] |
234234
| [394. Decode String][lc394] | 🟠 Medium | [![python](res/py.png)][lc394py] |
235235
| [399. Evaluate Division][lc399] | 🟠 Medium | [![python](res/py.png)][lc399py] |
236+
| [403. Frog Jump][lc403] | 🔴 Hard | [![rust](res/rs.png)][lc403rs] |
236237
| [406. Queue Reconstruction by Height][lc406] | 🟠 Medium | [![python](res/py.png)][lc406py] |
237238
| [409. Longest Palindrome][lc409] | 🟢 Easy | [![python](res/py.png)][lc409py] |
238239
| [416. Partition Equal Subset Sum][lc416] | 🟠 Medium | [![python](res/py.png)][lc416py] |
@@ -1060,6 +1061,8 @@ Solutions to LeetCode problems. The first column links to the problem in LeetCod
10601061
[lc394py]: leetcode/decode-string.py
10611062
[lc399]: https://leetcode.com/problems/evaluate-division/
10621063
[lc399py]: leetcode/evaluate-division.py
1064+
[lc403]: https://leetcode.com/problems/frog-jump/
1065+
[lc403rs]: leetcode/frog-jump.rs
10631066
[lc406]: https://leetcode.com/problems/queue-reconstruction-by-height/
10641067
[lc406py]: leetcode/queue-reconstruction-by-height.py
10651068
[lc409]: https://leetcode.com/problems/longest-palindrome/

leetcode/frog-jump.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// 403. Frog Jump
2+
// 🔴 Hard
3+
//
4+
// https://leetcode.com/problems/frog-jump/
5+
//
6+
// Tags: Array - Dynamic Programming
7+
8+
struct Solution;
9+
impl Solution {
10+
/// We can use dynamic programming, have an internal function that, given
11+
/// an index and a jump value, determines if we can make that jump, the
12+
/// function uses the position at the current index and the jump value to
13+
/// compute the landing position, then binary search for it to the right
14+
/// of the current index in the input vector. If the landing position is
15+
/// found, we recursively try the 3 possible jumps from that position until
16+
/// we either cannot make the next jump or reach the end. Use a hashmap to
17+
/// store combinations of index and jump that we have already computed.
18+
///
19+
/// Time complexity: O(n*log(n)) - We can try a maximum of 3 different
20+
/// jump values, k-1, k, k+1 from each position in the input vector, for
21+
/// each, we will binary search the landing position to the right of the
22+
/// current position.
23+
/// Space complexity: O(n^2) - The memo hashmap can have an entry for each
24+
/// possible combination of index and jump value.
25+
///
26+
/// Runtime 12 ms Beats 71.43%
27+
/// Memory 4.80 MB Beats 71.43%
28+
pub fn can_cross(stones: Vec<i32>) -> bool {
29+
// let mut memo: HashMap<(usize, i32), bool> = HashMap::new();
30+
const N: usize = 2001;
31+
let mut memo: [[Option<bool>; N]; N] = [[None; N]; N];
32+
fn can_jump_k(
33+
stones: &Vec<i32>,
34+
current_idx: usize,
35+
k: i32,
36+
memo: &mut [[Option<bool>; N]; N],
37+
) -> bool {
38+
if k == 0 || k as usize > N - 1 {
39+
return false;
40+
}
41+
if let Some(result) = memo[current_idx][k as usize] {
42+
return result;
43+
}
44+
let target = stones[current_idx] + k;
45+
match stones[current_idx..].binary_search(&target) {
46+
Ok(slice_index) => {
47+
let idx = current_idx + slice_index;
48+
let result = idx == stones.len() - 1
49+
|| can_jump_k(stones, idx, k - 1, memo)
50+
|| can_jump_k(stones, idx, k, memo)
51+
|| can_jump_k(stones, idx, k + 1, memo);
52+
memo[current_idx][k as usize] = Some(result);
53+
result
54+
}
55+
Err(_) => {
56+
memo[current_idx][k as usize] = Some(false);
57+
false
58+
}
59+
}
60+
}
61+
can_jump_k(&stones, 0, 1, &mut memo)
62+
}
63+
64+
/// Similar to the previous solution but use a vector instead of a hashmap.
65+
/// It is also possible to use an array bounded to 2001 because that
66+
/// maximum is known at compile time, but, with the given tests, the
67+
/// memory usage is higher.
68+
///
69+
/// Time complexity: O(n*log(n)) - We can try a maximum of 3 different
70+
/// jump values, k-1, k, k+1 from each position in the input vector, for
71+
/// each, we will binary search the landing position to the right of the
72+
/// current position.
73+
/// Space complexity: O(n^2) - The memo vector can have an entry for each
74+
/// possible combination of index and jump value, both of them bound to n.
75+
///
76+
/// Runtime 3 ms Beats 100%
77+
/// Memory 5.90 MB Beats 71.43%
78+
pub fn can_cross_2(stones: Vec<i32>) -> bool {
79+
let n = stones.len();
80+
if n < 2 || stones[1] - stones[0] != 1 || stones[n - 1] as usize >= n * n / 2 {
81+
return false;
82+
}
83+
let mut memo: Vec<Vec<Option<bool>>> = vec![vec![None; n]; n];
84+
fn can_jump_k(
85+
stones: &Vec<i32>,
86+
current_idx: usize,
87+
k: i32,
88+
n: i32,
89+
memo: &mut Vec<Vec<Option<bool>>>,
90+
) -> bool {
91+
if k == 0 || k > n - 1 {
92+
return false;
93+
}
94+
if let Some(result) = memo[current_idx][k as usize] {
95+
return result;
96+
}
97+
let target = stones[current_idx] + k;
98+
match stones[current_idx..].binary_search(&target) {
99+
Ok(slice_index) => {
100+
let idx = current_idx + slice_index;
101+
let result = idx == stones.len() - 1
102+
|| can_jump_k(stones, idx, k - 1, n, memo)
103+
|| can_jump_k(stones, idx, k, n, memo)
104+
|| can_jump_k(stones, idx, k + 1, n, memo);
105+
memo[current_idx][k as usize] = Some(result);
106+
result
107+
}
108+
Err(_) => {
109+
memo[current_idx][k as usize] = Some(false);
110+
false
111+
}
112+
}
113+
}
114+
can_jump_k(&stones, 0, 1, n as i32, &mut memo)
115+
}
116+
}
117+
118+
// Tests.
119+
fn main() {
120+
let tests = [
121+
(vec![0, 2147483647], false),
122+
(vec![0, 1, 3, 5, 6, 8, 12, 17], true),
123+
(vec![0, 1, 2, 3, 4, 8, 9, 11], false),
124+
];
125+
for t in tests {
126+
assert_eq!(Solution::can_cross(t.0.clone()), t.1);
127+
assert_eq!(Solution::can_cross_2(t.0), t.1);
128+
}
129+
println!("\x1b[92m» All tests passed!\x1b[0m")
130+
}

0 commit comments

Comments
 (0)