1866. Number of Ways to Rearrange Sticks With K Sticks Visible
Problem Description
You have n
sticks with unique lengths from 1
to n
. You need to arrange these sticks in a row such that exactly k
sticks are visible when looking from the left side.
A stick is considered visible from the left if there are no longer sticks positioned to its left. In other words, a stick is visible if it's the tallest among all sticks from the leftmost position up to and including itself.
For example, given the arrangement [1, 3, 2, 5, 4]
:
- Stick with length
1
is visible (it's the first stick) - Stick with length
3
is visible (it's taller than all sticks to its left, which is just1
) - Stick with length
2
is not visible (stick3
to its left is taller) - Stick with length
5
is visible (it's taller than all sticks to its left:1
,3
, and2
) - Stick with length
4
is not visible (stick5
to its left is taller)
So this arrangement has exactly 3
visible sticks from the left.
Given integers n
(the number of sticks) and k
(the desired number of visible sticks), you need to find how many different arrangements of the n
sticks will result in exactly k
sticks being visible from the left.
Since the answer can be very large, return the result modulo 10^9 + 7
.
Intuition
Let's think about this problem step by step. We need to count arrangements where exactly k
sticks are visible from the left.
The key insight is to consider what happens with the last (rightmost) stick in our arrangement. There are two cases:
Case 1: The last stick is visible from the left
For the last stick to be visible, it must be the tallest stick (length i
when we have i
sticks total). If we place the tallest stick at the end, it will always be visible because nothing to its left can block it.
In this case, we need the remaining i-1
sticks in front of it to form an arrangement where exactly j-1
sticks are visible (since the last stick adds one more visible stick). This gives us f[i-1][j-1]
ways.
Case 2: The last stick is NOT visible from the left
For the last stick to be invisible, there must be a taller stick somewhere to its left. This means the last stick cannot be the tallest one (length i
). It can be any of the other i-1
shorter sticks.
When we place one of these i-1
shorter sticks at the end, it doesn't affect the visibility count from the left - the number of visible sticks remains the same. We need the first i-1
positions to have exactly j
visible sticks. For each such arrangement of i-1
sticks with j
visible, we can choose any of the i-1
non-tallest sticks to place at the end. This gives us f[i-1][j] * (i-1)
ways.
Combining both cases, we get the recurrence relation:
f[i][j] = f[i-1][j-1] + f[i-1][j] * (i-1)
The base case is f[0][0] = 1
(empty arrangement with zero visible sticks), and we build up to f[n][k]
which gives us our answer.
This dynamic programming approach works because each subproblem f[i][j]
only depends on smaller subproblems with i-1
sticks, allowing us to build the solution bottom-up.
Learn more about Math, Dynamic Programming and Combinatorics patterns.
Solution Approach
We use dynamic programming to solve this problem. Let's define f[i][j]
as the number of arrangements of i
sticks where exactly j
sticks are visible from the left.
Initialization:
- Create a 2D DP table
f
with dimensions(n+1) × (k+1)
initialized with zeros - Set base case:
f[0][0] = 1
(empty arrangement with zero visible sticks) - All other states start at 0
State Transition:
For each state f[i][j]
where i
represents the number of sticks and j
represents the number of visible sticks:
f[i][j] = f[i-1][j-1] + f[i-1][j] * (i-1)
This formula comes from two cases:
f[i-1][j-1]
: Place the tallest stick (lengthi
) at the end, making it visiblef[i-1][j] * (i-1)
: Place any of thei-1
shorter sticks at the end, keeping it invisible
Implementation Details:
def rearrangeSticks(self, n: int, k: int) -> int:
mod = 10**9 + 7
f = [[0] * (k + 1) for _ in range(n + 1)]
f[0][0] = 1
for i in range(1, n + 1):
for j in range(1, k + 1):
f[i][j] = (f[i - 1][j - 1] + f[i - 1][j] * (i - 1)) % mod
return f[n][k]
Algorithm Flow:
- Iterate through all states from
i = 1
ton
(number of sticks) - For each
i
, iterate throughj = 1
tok
(number of visible sticks) - Apply the transition formula with modulo to prevent overflow
- Return
f[n][k]
as the final answer
Time Complexity: O(n × k)
- we fill a 2D table of size n × k
Space Complexity: O(n × k)
- for storing the DP table
The modulo operation 10^9 + 7
is applied at each step to keep numbers manageable and prevent integer overflow, as the number of arrangements can grow very large.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with n = 3
sticks (lengths 1, 2, 3) and we want exactly k = 2
visible sticks.
Step 1: Initialize DP Table
We create a table f[i][j]
where i
is the number of sticks and j
is the number of visible sticks.
j=0 j=1 j=2 i=0 1 0 0 i=1 0 0 0 i=2 0 0 0 i=3 0 0 0
Step 2: Fill the DP Table
For i=1
(1 stick of length 1):
f[1][1] = f[0][0] + f[0][1] * 0 = 1 + 0 = 1
- Only arrangement: [1], which has 1 visible stick
For i=2
(2 sticks of lengths 1, 2):
f[2][1] = f[1][0] + f[1][1] * 1 = 0 + 1 * 1 = 1
- Arrangement: [2,1] (only stick 2 is visible)
f[2][2] = f[1][1] + f[1][2] * 1 = 1 + 0 = 1
- Arrangement: [1,2] (both sticks are visible)
For i=3
(3 sticks of lengths 1, 2, 3):
f[3][1] = f[2][0] + f[2][1] * 2 = 0 + 1 * 2 = 2
- Arrangements: [3,1,2] and [3,2,1] (only stick 3 is visible)
f[3][2] = f[2][1] + f[2][2] * 2 = 1 + 1 * 2 = 3
- Let's verify this with actual arrangements:
- From
f[2][1]
: Place stick 3 at end of [2,1] → [2,1,3] (sticks 2 and 3 visible) - From
f[2][2]
: Place stick 1 or 2 at end of [1,2] → [1,2,3] gives wrong count - Actually: [1,3,2] (1 and 3 visible), [2,3,1] (2 and 3 visible), [2,1,3] (2 and 3 visible)
- From
- Let's verify this with actual arrangements:
Final Answer: f[3][2] = 3
The three valid arrangements with exactly 2 visible sticks are:
- [1,3,2]: Sticks 1 and 3 are visible
- [2,1,3]: Sticks 2 and 3 are visible
- [2,3,1]: Sticks 2 and 3 are visible
The recurrence relation captures both scenarios:
- Adding the tallest stick at the end (making it visible)
- Adding any shorter stick at the end (keeping it hidden behind taller sticks to its left)
Solution Implementation
1class Solution:
2 def rearrangeSticks(self, n: int, k: int) -> int:
3 """
4 Calculate the number of ways to arrange n sticks such that exactly k sticks are visible.
5 A stick is visible if all sticks to its left are shorter.
6
7 Args:
8 n: Total number of sticks (each with unique height from 1 to n)
9 k: Number of sticks that should be visible
10
11 Returns:
12 Number of valid arrangements modulo 10^9 + 7
13 """
14 MOD = 10**9 + 7
15
16 # dp[i][j] represents the number of ways to arrange i sticks
17 # such that exactly j sticks are visible
18 dp = [[0] * (k + 1) for _ in range(n + 1)]
19
20 # Base case: 0 sticks with 0 visible sticks = 1 way (empty arrangement)
21 dp[0][0] = 1
22
23 # Fill the DP table
24 for num_sticks in range(1, n + 1):
25 for num_visible in range(1, k + 1):
26 # Case 1: Place the shortest stick at the front (becomes visible)
27 # This adds one more visible stick
28 place_shortest_first = dp[num_sticks - 1][num_visible - 1]
29
30 # Case 2: Place the shortest stick anywhere except the front (not visible)
31 # There are (num_sticks - 1) positions where it won't be visible
32 place_shortest_not_first = dp[num_sticks - 1][num_visible] * (num_sticks - 1)
33
34 # Total ways is the sum of both cases
35 dp[num_sticks][num_visible] = (place_shortest_first + place_shortest_not_first) % MOD
36
37 return dp[n][k]
38
1class Solution {
2 public int rearrangeSticks(int n, int k) {
3 // Define modulo value for large number handling
4 final int MOD = (int) 1e9 + 7;
5
6 // dp[i][j] represents the number of ways to arrange i sticks
7 // such that exactly j sticks are visible from the left
8 int[][] dp = new int[n + 1][k + 1];
9
10 // Base case: 0 sticks with 0 visible sticks = 1 way (empty arrangement)
11 dp[0][0] = 1;
12
13 // Fill the DP table
14 for (int numSticks = 1; numSticks <= n; numSticks++) {
15 for (int visibleSticks = 1; visibleSticks <= k; visibleSticks++) {
16 // Two cases for placing the i-th stick:
17 // 1. Place the tallest stick at the leftmost position (it will be visible)
18 // This reduces the problem to arranging (i-1) sticks with (j-1) visible
19 // Contribution: dp[i-1][j-1]
20
21 // 2. Place the tallest stick at any other position (positions 2 to i)
22 // It won't be visible from the left as there's a taller stick before it
23 // We have (i-1) positions to choose from (excluding the leftmost)
24 // This reduces the problem to arranging (i-1) sticks with j visible
25 // Contribution: dp[i-1][j] * (i-1)
26
27 dp[numSticks][visibleSticks] = (int) ((dp[numSticks - 1][visibleSticks - 1] +
28 dp[numSticks - 1][visibleSticks] * (long) (numSticks - 1)) % MOD);
29 }
30 }
31
32 // Return the number of ways to arrange n sticks with exactly k visible from left
33 return dp[n][k];
34 }
35}
36
1class Solution {
2public:
3 int rearrangeSticks(int n, int k) {
4 // Modulo value for preventing integer overflow
5 const int MOD = 1e9 + 7;
6
7 // dp[i][j] represents the number of ways to arrange i sticks
8 // such that exactly j sticks are visible from the left
9 int dp[n + 1][k + 1];
10
11 // Initialize all values to 0
12 memset(dp, 0, sizeof(dp));
13
14 // Base case: 0 sticks with 0 visible sticks = 1 way (empty arrangement)
15 dp[0][0] = 1;
16
17 // Fill the DP table
18 for (int numSticks = 1; numSticks <= n; ++numSticks) {
19 for (int visibleSticks = 1; visibleSticks <= k; ++visibleSticks) {
20 // Two choices for placing the current stick:
21 // 1. Place the shortest stick at the front (becomes visible)
22 // This adds one more visible stick: dp[numSticks - 1][visibleSticks - 1]
23 // 2. Place the shortest stick at any of the (numSticks - 1) non-front positions
24 // This doesn't change the number of visible sticks:
25 // (numSticks - 1) * dp[numSticks - 1][visibleSticks]
26 dp[numSticks][visibleSticks] = (dp[numSticks - 1][visibleSticks - 1] +
27 (static_cast<long long>(numSticks - 1) * dp[numSticks - 1][visibleSticks])) % MOD;
28 }
29 }
30
31 // Return the number of ways to arrange n sticks with exactly k visible
32 return dp[n][k];
33 }
34};
35
1/**
2 * Calculates the number of ways to rearrange n sticks such that exactly k sticks are visible.
3 * A stick is visible if all sticks to its left are shorter than it.
4 *
5 * @param n - Total number of sticks (each with unique height from 1 to n)
6 * @param k - Number of sticks that should be visible from the left
7 * @returns The number of valid arrangements modulo 10^9 + 7
8 */
9function rearrangeSticks(n: number, k: number): number {
10 // Modulo value to prevent integer overflow
11 const MOD: number = 10 ** 9 + 7;
12
13 // Dynamic programming table
14 // dp[i][j] represents the number of ways to arrange i sticks with exactly j visible
15 const dp: number[][] = Array.from({ length: n + 1 }, () =>
16 Array.from({ length: k + 1 }, () => 0)
17 );
18
19 // Base case: 0 sticks with 0 visible has exactly 1 way (empty arrangement)
20 dp[0][0] = 1;
21
22 // Fill the DP table using the recurrence relation
23 for (let numSticks = 1; numSticks <= n; numSticks++) {
24 for (let numVisible = 1; numVisible <= k; numVisible++) {
25 // Two cases for placing the i-th stick:
26 // 1. Place the tallest stick at the front (makes it visible): dp[i-1][j-1]
27 // 2. Place the shortest stick anywhere except the front (keeps it hidden): (i-1) * dp[i-1][j]
28 dp[numSticks][numVisible] = (
29 dp[numSticks - 1][numVisible - 1] +
30 (numSticks - 1) * dp[numSticks - 1][numVisible]
31 ) % MOD;
32 }
33 }
34
35 // Return the number of ways to arrange n sticks with exactly k visible
36 return dp[n][k];
37}
38
Time and Space Complexity
The time complexity of this code is O(n × k)
. The algorithm uses a nested loop structure where the outer loop iterates from 1
to n
(n iterations) and the inner loop iterates from 1
to k
(k iterations). For each combination of i
and j
, we perform a constant time operation to calculate f[i][j]
. Therefore, the total number of operations is proportional to n × k
.
The space complexity is O(n × k)
. The algorithm creates a 2D array f
with dimensions (n + 1) × (k + 1)
to store the dynamic programming states. This array requires O((n + 1) × (k + 1))
space, which simplifies to O(n × k)
. The only other variables used are mod
, i
, and j
, which require constant space O(1)
. Thus, the overall space complexity is dominated by the 2D array, resulting in O(n × k)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Interpretation of the Recurrence Relation
A common mistake is misunderstanding which stick we're adding and where. The code comments mention "shortest stick" but the mathematical formula uses the "tallest stick" perspective. This conceptual mismatch can lead to confusion and errors.
The Problem:
The code comments say we're placing the "shortest stick," but the recurrence relation f[i][j] = f[i-1][j-1] + f[i-1][j] * (i-1)
is actually based on considering the tallest stick among the first i
sticks.
Why This Happens:
When working with permutations of sticks numbered 1 to i
, we can think about it two ways:
- Adding stick
i
(the tallest) to arrangements of sticks 1 toi-1
- Adding stick 1 (the shortest) to arrangements of renumbered sticks 2 to
i
Both perspectives are valid due to the relative nature of heights, but mixing them causes confusion.
Solution: Be consistent with your mental model. The cleaner approach is to think about adding the tallest stick:
def rearrangeSticks(self, n: int, k: int) -> int:
MOD = 10**9 + 7
# dp[i][j] = ways to arrange i sticks with j visible
dp = [[0] * (k + 1) for _ in range(n + 1)]
dp[0][0] = 1
for i in range(1, n + 1):
for j in range(1, k + 1):
# Add tallest stick (height i) at the front -> always visible
dp[i][j] = dp[i-1][j-1]
# Add tallest stick at any of the i-1 non-front positions -> never visible
dp[i][j] = (dp[i][j] + dp[i-1][j] * (i-1)) % MOD
return dp[n][k]
2. Forgetting to Apply Modulo During Intermediate Calculations
The Problem:
Only applying modulo at the very end or forgetting it in intermediate steps can cause integer overflow, especially for large values of n
and k
.
Incorrect approach:
# Wrong - can overflow before modulo dp[i][j] = dp[i-1][j-1] + dp[i-1][j] * (i-1) return dp[n][k] % MOD
Solution: Apply modulo after each arithmetic operation that could produce large numbers:
# Correct - prevents overflow dp[i][j] = (dp[i-1][j-1] + dp[i-1][j] * (i-1)) % MOD
3. Index Out of Bounds When j > i
The Problem: It's impossible to have more visible sticks than total sticks (j > i), but if we don't handle this case, we might access invalid states or get incorrect results.
Solution: Add a boundary check or ensure the loop limits prevent this:
for i in range(1, n + 1):
for j in range(1, min(i, k) + 1): # j cannot exceed i
dp[i][j] = (dp[i-1][j-1] + dp[i-1][j] * (i-1)) % MOD
4. Space Optimization Pitfall
The Problem: Since we only need the previous row to compute the current row, you might try to optimize space to O(k) using a 1D array. However, updating in-place incorrectly can overwrite values still needed.
Incorrect space optimization:
# Wrong - overwrites values still needed
dp = [0] * (k + 1)
dp[0] = 1
for i in range(1, n + 1):
for j in range(1, k + 1): # Wrong direction!
dp[j] = (dp[j-1] + dp[j] * (i-1)) % MOD
Solution: If optimizing space, iterate backwards through j to avoid overwriting needed values:
# Correct space-optimized version
dp = [0] * (k + 1)
dp[0] = 1
for i in range(1, n + 1):
for j in range(min(i, k), 0, -1): # Iterate backwards
dp[j] = (dp[j-1] + dp[j] * (i-1)) % MOD
Which of the following is a good use case for backtracking?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
Want a Structured Path to Master System Design Too? Don’t Miss This!