You are given two positive integers startPos
and endPos
. Initially, you are standing at position startPos
on an infinite number line. With one step, you can move either one position to the left, or one position to the right.
Given a positive integer k
, return the number of different ways to reach the position endPos
starting from startPos
, such that you perform exactly k
steps. Since the answer may be very large, return it modulo 109 + 7
.
Two ways are considered different if the order of the steps made is not exactly the same.
Note that the number line includes negative integers.
Example 1:
Input: startPos = 1, endPos = 2, k = 3 Output: 3 Explanation: We can reach position 2 from 1 in exactly 3 steps in three ways: - 1 -> 2 -> 3 -> 2. - 1 -> 2 -> 1 -> 2. - 1 -> 0 -> 1 -> 2. It can be proven that no other way is possible, so we return 3.
Example 2:
Input: startPos = 2, endPos = 5, k = 10 Output: 0 Explanation: It is impossible to reach position 5 from position 2 in exactly 10 steps.
Constraints:
1 <= startPos, endPos, k <= 1000
This problem asks to find the number of distinct ways to reach a target position (endPos
) from a starting position (startPos
) in exactly k
steps, where each step can be either +1 or -1.
The core idea is to use dynamic programming to avoid redundant calculations. We define a recursive function dfs(i, j)
:
i
: Represents the absolute distance between the current position and the endPos
.j
: Represents the number of remaining steps.The function dfs(i, j)
returns the number of ways to reach endPos
from the current position (distance i
from endPos
) with j
steps remaining.
The base cases are:
i > j
(distance greater than remaining steps) or j < 0
(negative steps remaining), it's impossible to reach endPos
, so we return 0.j == 0
, we've used all steps. If i == 0
, we've reached endPos
(1 way), otherwise we haven't (0 ways).The recursive step:
If neither base case is met, we can either move one step to the left (dfs(i + 1, j - 1)
) or one step to the right (dfs(abs(i - 1), j - 1)
). We sum the results of these two recursive calls and take the modulo with 10^9 + 7
to avoid integer overflow.
To optimize the recursive calls, we use memoization. A 2D array f
stores the results of dfs(i, j)
. Before making a recursive call, we check if the result is already computed and stored in f
. If it is, we directly return the stored value; otherwise, we compute it recursively and store it in f
before returning.
Time Complexity: The state space of dfs(i, j)
is O(k*k) because 0 <= i <= k
and 0 <= j <= k
. Each state is visited at most once due to memoization. Therefore, the time complexity is O(k^2).
Space Complexity: The space complexity is also O(k^2) due to the memoization array f
of size (k+1) x (k+1). The recursive call stack depth could also reach O(k) in the worst case but is dominated by the space used by f
.
class Solution:
def numberOfWays(self, startPos: int, endPos: int, k: int) -> int:
mod = 10**9 + 7
@lru_cache(None) #Using lru_cache for memoization
def dfs(i: int, j: int) -> int:
if i > j or j < 0:
return 0
if j == 0:
return 1 if i == 0 else 0
return (dfs(i + 1, j - 1) + dfs(abs(i - 1), j - 1)) % mod
return dfs(abs(startPos - endPos), k)
The code implementations in other languages (Java, C++, Go, TypeScript) follow the same logic, only differing in syntax and data structure handling. The memoization techniques might vary slightly depending on language features (e.g., using HashMap
in Java for more flexible memoization if needed). The core algorithmic approach, however, remains consistent.