Before attempting this problem, you should be comfortable with:
Consider placing sticks from tallest to shortest. The tallest stick must be visible. For the remaining sticks, we decide where to place each one relative to previously placed sticks. If a stick is placed at the leftmost available position, it becomes visible. Otherwise, it hides behind a taller stick already placed to its left.
dfs(N, K) where N is the number of sticks remaining and K is how many still need to be visible.N == K, all remaining sticks must be visible (only one way: place them in increasing order from left), return 1.N == 0 or K == 0 (but not both), it's impossible, return 0.dfs(N-1, K-1).N-1 positions behind a taller stick: (N-1) * dfs(N-1, K).10^9 + 7.The recursive solution has overlapping subproblems. By caching results in a 2D table, we avoid redundant computation.
(n+1) x (k+1) initialized to -1.dfs(N, K), check if the result is already computed.dfs(N-1, K-1) + (N-1) * dfs(N-1, K).10^9 + 7.class Solution:
def rearrangeSticks(self, n: int, k: int) -> int:
MOD = 10**9 + 7
dp = [[-1] * (k + 1) for _ in range(n + 1)]
def dfs(N, K):
if N == K:
return 1
if N == 0 or K == 0:
return 0
if dp[N][K] != -1:
return dp[N][K]
dp[N][K] = (dfs(N - 1, K - 1) + (N - 1) * dfs(N - 1, K)) % MOD
return dp[N][K]
return dfs(n, k)Where represents the total number of sticks, and denotes the number of sticks that must be visible from the left side.
We fill the DP table iteratively from smaller subproblems to larger ones. dp[N][K] represents the number of ways to arrange N sticks with exactly K visible.
(n+1) x (k+1) initialized to 0.dp[1][1] = 1 (one stick, one visible).N from 2 to n, and each K from 1 to k:dp[N][K] = (dp[N-1][K-1] + (N-1) * dp[N-1][K]) % MODdp[n][k].class Solution:
def rearrangeSticks(self, n: int, k: int) -> int:
MOD = 10**9 + 7
dp = [[0] * (k + 1) for _ in range(n + 1)]
dp[1][1] = 1
for N in range(2, n + 1):
for K in range(1, k + 1):
dp[N][K] = (dp[N - 1][K - 1] + (N - 1) * dp[N - 1][K]) % MOD
return dp[n][k]Where represents the total number of sticks, and denotes the number of sticks that must be visible from the left side.
Each row only depends on the previous row, so we can use a 1D array and update it carefully to avoid overwriting values we still need.
k + 1.dp[1] = 1.N from 2 to n:K from 1 to k, tracking the previous value before updating.dp[K] = (prev + (N-1) * dp[K]) % MOD.dp[k].Where represents the total number of sticks, and denotes the number of sticks that must be visible from the left side.
The key insight is that we place sticks from tallest to shortest. The shortest remaining stick can either be placed at the leftmost position (becoming visible, contributing dfs(N-1, K-1)) or in one of N-1 positions behind a taller stick (contributing (N-1) * dfs(N-1, K)). A common mistake is thinking the multiplier should be N instead of N-1, or confusing which stick is being placed.
The term (N-1) * dfs(N-1, K) can cause integer overflow in languages like Java and C++ when both values are large. Always cast to long before multiplication: (long)(N-1) * dfs(N-1, K) % MOD. Failing to do this produces incorrect results for large inputs.
The base cases require careful handling. When N == K, all remaining sticks must be visible (return 1). When N == 0 or K == 0 (but not both equal), it is impossible (return 0). A common error is returning 1 when K == 0 regardless of N, or not handling the N == K case before checking K == 0.
In the space-optimized version, updating dp[K] requires the old value of dp[K-1] (stored in prev) and the current dp[K]. If you update in the wrong order or forget to save prev before modifying dp[K], you will use already-updated values and get wrong answers. Always save the current value before overwriting: tmp = dp[K]; dp[K] = ...; prev = tmp.