We need to count how many ways we can return to index 0 after exactly steps moves, where each move can go left, right, or stay in place. This is a classic decision tree problem where at each step we have three choices.
The key observation is that we can never move further than steps positions to the right, since we would need at least that many steps just to return. This lets us bound our search space to min(steps, arrLen) positions.
We use memoization to avoid recomputing the same subproblems. The state is defined by the current position i and remaining steps.
min(steps, arrLen) since we cannot move further than steps positions.dfs(i, steps) that returns the number of ways to reach index 0 from position i with steps moves remaining.steps == 0, return 1 if we are at index 0, otherwise return 0.i > 0, move right if i < arrLen - 1) and sum the results.dfs(0, steps) as the answer.class Solution:
def numWays(self, steps: int, arrLen: int) -> int:
mod = 10**9 + 7
arrLen = min(arrLen, steps)
dp = {}
def dfs(i, steps):
if steps == 0:
return i == 0
if (i, steps) in dp:
return dp[(i, steps)]
res = dfs(i, steps - 1)
if i > 0:
res = (res + dfs(i - 1, steps - 1)) % mod
if i < arrLen - 1:
res = (res + dfs(i + 1, steps - 1)) % mod
dp[(i, steps)] = res
return res
return dfs(0, steps)Where is the number of steps and is the size of the array.
Instead of thinking recursively from the end state backward, we can build up the solution iteratively. We start from the base case (0 steps taken, standing at index 0) and compute how many ways there are to be at each position after 1 step, then 2 steps, and so on.
At each step, the number of ways to reach position i is the sum of ways to reach i from i-1 (moving right), from i (staying), and from i+1 (moving left) in the previous step.
dp[step][i] represents the number of ways to be at position i after step moves.dp[0][0] = 1 since we start at index 0 with 0 steps taken.1 to steps:i from 0 to arrLen - 1:dp[step-1][i]i > 0): dp[step-1][i-1]i < arrLen - 1): dp[step-1][i+1]dp[steps][0].class Solution:
def numWays(self, steps: int, arrLen: int) -> int:
mod = 10**9 + 7
arrLen = min(arrLen, steps)
dp = [[0] * (arrLen + 1) for _ in range(steps + 1)]
dp[0][0] = 1
for step in range(1, steps + 1):
for i in range(arrLen):
res = dp[step - 1][i]
if i > 0:
res = (res + dp[step - 1][i - 1]) % mod
if i < arrLen - 1:
res = (res + dp[step - 1][i + 1]) % mod
dp[step][i] = res
return dp[steps][0]Where is the number of steps and is the size of the array.
Looking at the bottom-up solution, we notice that computing the values for step k only requires values from step k-1. We do not need the entire history of all steps.
This means we can reduce space from O(steps * positions) to O(positions) by keeping only two arrays: one for the current step and one for the previous step.
dp where dp[i] represents the number of ways to be at position i after the current number of steps.dp[0] = 1 for the starting position.next_dp to store results for this step.i, compute next_dp[i] by summing contributions from positions i-1, i, and i+1 in dp.dp with next_dp.dp[0] after all steps.class Solution:
def numWays(self, steps: int, arrLen: int) -> int:
mod = 10**9 + 7
arrLen = min(steps, arrLen)
dp = [0] * arrLen
dp[0] = 1
for _ in range(steps):
next_dp = [0] * arrLen
for i in range(arrLen):
next_dp[i] = dp[i]
if i > 0:
next_dp[i] = (next_dp[i] + dp[i - 1]) % mod
if i < arrLen - 1:
next_dp[i] = (next_dp[i] + dp[i + 1]) % mod
dp = next_dp
return dp[0]Where is the number of steps and is the size of the array.
We can push the space optimization even further. Instead of using two separate arrays, we can update the DP array in place if we are careful about the order of updates.
The trick is to process positions from left to right while keeping track of the previous value before it gets overwritten. This way, when we update dp[i], we still have access to the old dp[i-1] (saved in a temporary variable) and the old dp[i+1] (not yet updated).
dp where dp[i] represents ways to reach position i.dp[0] = 1.prev to store the old value of dp[i-1] before it was updated.i from 0 to arrLen - 1:dp[i] as cur before modifying it.prev (contribution from the left) if i > 0.dp[i+1] (contribution from the right) if i < arrLen - 1.prev = cur for the next iteration.dp[0].class Solution:
def numWays(self, steps: int, arrLen: int) -> int:
mod = 10**9 + 7
arrLen = min(steps, arrLen)
dp = [0] * arrLen
dp[0] = 1
for _ in range(steps):
prev = 0
for i in range(arrLen):
cur = dp[i]
if i > 0:
dp[i] = (dp[i] + prev) % mod
if i < arrLen - 1:
dp[i] = (dp[i] + dp[i + 1]) % mod
prev = cur
return dp[0]Where is the number of steps and is the size of the array.
Without optimization, the DP table would be O(steps * arrLen) which can be prohibitively large when arrLen is huge but steps is small. The key insight is that you can never move more than steps positions to the right (since you need steps to return). Always limit the effective array length to min(steps, arrLen) to avoid TLE or memory issues.
When at position i, moving left requires i > 0 and moving right requires i < arrLen - 1. A common mistake is using i < arrLen for the right boundary (allowing out-of-bounds movement) or forgetting the left boundary check entirely. These off-by-one errors cause incorrect results or runtime errors.
In bottom-up DP, you need to carefully track whether dp[step][i] represents the number of ways to reach position i after step moves (forward direction) or the number of ways to return to position 0 from position i with step moves remaining (backward direction). Mixing these up leads to incorrect recurrence relations and wrong answers.