1643. Kth Smallest Instructions
Problem Description
Bob starts at position (0, 0)
on a grid and needs to reach a destination at position (row, column)
. He can only move in two directions:
- Right (represented by 'H' for horizontal)
- Down (represented by 'V' for vertical)
The path Bob takes is represented as a string of instructions. For example, if the destination is (2, 3)
, Bob needs to move down 2 times and right 3 times. Valid instruction strings include "HHHVV"
(go right 3 times, then down 2 times) or "HVHVH"
(alternating moves).
Since there are multiple valid paths to reach the destination, all possible instruction strings can be ordered lexicographically (dictionary order, where 'H' comes before 'V'). The problem asks you to find the k-th smallest instruction string in this lexicographical ordering.
The solution uses combinatorial mathematics to determine which character ('H' or 'V') to place at each position. At each step, it calculates how many valid paths exist if we choose 'H' next using the combination formula C(h+v-1, h-1)
. If k
is greater than this count, it means the k-th path must start with 'V' instead of 'H'. Otherwise, it starts with 'H'. This process continues, building the path character by character until reaching the destination.
The key insight is that the number of ways to arrange h
horizontal moves and v
vertical moves with 'H' at the current position is exactly C(h+v-1, h-1)
, which helps determine whether the k-th path should have 'H' or 'V' at each position.
Intuition
The key observation is that we need to make exactly h
horizontal moves and v
vertical moves to reach the destination, regardless of the order. This means every valid path is a permutation of h
'H's and v
'V's.
To find the k-th lexicographically smallest path, we can build it character by character from left to right. At each position, we need to decide whether to place an 'H' or a 'V'.
Since 'H' comes before 'V' lexicographically, we want to place 'H' whenever possible to get smaller strings. But how do we know if we should place 'H' at the current position for the k-th smallest path?
The insight is to count how many valid paths exist if we place 'H' at the current position. If we place 'H' now, we have h-1
horizontal moves and v
vertical moves remaining. The number of ways to arrange these remaining moves is C(h+v-1, h-1)
(choosing h-1
positions for 'H' from h+v-1
total positions).
This count tells us something crucial:
- If
k ≤ C(h+v-1, h-1)
, then the k-th path is among those that start with 'H' - If
k > C(h+v-1, h-1)
, then we've "skipped" all paths starting with 'H', so the k-th path must start with 'V'
When we choose 'V', we need to adjust k
by subtracting the number of paths we skipped (those starting with 'H'). This way, we maintain the relative position of k among the remaining paths.
By repeating this process for each position, we build the k-th smallest path character by character, always making the locally optimal choice based on combinatorial counting.
Learn more about Math, Dynamic Programming and Combinatorics patterns.
Solution Approach
The solution implements a greedy algorithm with combinatorial counting to build the k-th smallest path character by character.
Algorithm Steps:
-
Initialize variables: Extract
v
(vertical moves) andh
(horizontal moves) from the destination. Create an empty listans
to build the result string. -
Iterate through each position: Loop
h + v
times, as we need exactly that many characters in our path. -
Handle edge case: If
h == 0
, we have no horizontal moves left, so we must append 'V'. -
Calculate combinations: For the general case where
h > 0
, calculatex = comb(h + v - 1, h - 1)
. This represents the number of valid paths if we place 'H' at the current position. -
Make decision based on k:
- If
k > x
: The k-th path doesn't start with 'H' at this position. We append 'V', decrementv
by 1, and updatek -= x
to adjust for the skipped paths. - If
k ≤ x
: The k-th path starts with 'H' at this position. We append 'H' and decrementh
by 1.
- If
-
Build result: After processing all positions, join the characters in
ans
to form the final instruction string.
Example walkthrough:
For destination (2, 3)
and k = 2
:
- Start with
v=2, h=3
- Position 1:
x = C(4, 2) = 6
, sincek=2 ≤ 6
, append 'H', nowh=2
- Position 2:
x = C(3, 1) = 3
, sincek=2 ≤ 3
, append 'H', nowh=1
- Position 3:
x = C(2, 0) = 1
, sincek=2 > 1
, append 'V', nowv=1, k=1
- Position 4:
x = C(1, 0) = 1
, sincek=1 ≤ 1
, append 'H', nowh=0
- Position 5:
h=0
, so append 'V' - Result: "HHVHV"
The algorithm runs in O(h + v)
time with O(h + v)
space for storing the result.
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 find the 5th lexicographically smallest path to reach destination (2, 3)
.
Initial Setup:
- Destination:
(2, 3)
means we needv = 2
vertical moves andh = 3
horizontal moves - Total moves needed:
2 + 3 = 5
- We're looking for
k = 5
Step-by-step Process:
Position 1:
- Current state:
v = 2
,h = 3
,k = 5
- Since
h > 0
, calculate combinations:C(2+3-1, 3-1) = C(4, 2) = 6
- This means there are 6 valid paths if we place 'H' at position 1
- Decision: Since
k = 5 ≤ 6
, the 5th path is among those starting with 'H' - Action: Append 'H', update
h = 2
- Result so far: "H"
Position 2:
- Current state:
v = 2
,h = 2
,k = 5
- Calculate:
C(2+2-1, 2-1) = C(3, 1) = 3
- Decision: Since
k = 5 > 3
, the 5th path does NOT have 'H' at position 2 - Action: Append 'V', update
v = 1
, and adjustk = 5 - 3 = 2
- Result so far: "HV"
Position 3:
- Current state:
v = 1
,h = 2
,k = 2
- Calculate:
C(1+2-1, 2-1) = C(2, 1) = 2
- Decision: Since
k = 2 ≤ 2
, place 'H' at position 3 - Action: Append 'H', update
h = 1
- Result so far: "HVH"
Position 4:
- Current state:
v = 1
,h = 1
,k = 2
- Calculate:
C(1+1-1, 1-1) = C(1, 0) = 1
- Decision: Since
k = 2 > 1
, place 'V' at position 4 - Action: Append 'V', update
v = 0
, and adjustk = 2 - 1 = 1
- Result so far: "HVHV"
Position 5:
- Current state:
v = 0
,h = 1
,k = 1
- Since
v = 0
, we can only place 'H' - Action: Append 'H', update
h = 0
- Final result: "HVHVH"
Verification:
The 5th lexicographically smallest path to (2, 3)
is "HVHVH", which correctly uses 3 'H's (horizontal moves) and 2 'V's (vertical moves) to reach the destination.
Solution Implementation
1from math import comb
2from typing import List
3
4class Solution:
5 def kthSmallestPath(self, destination: List[int], k: int) -> str:
6 """
7 Find the k-th lexicographically smallest path from (0,0) to destination.
8 Path consists of 'H' (horizontal right) and 'V' (vertical down) moves.
9
10 Args:
11 destination: Target coordinates [row, column]
12 k: The k-th smallest path to find (1-indexed)
13
14 Returns:
15 String representing the k-th smallest path
16 """
17 # Extract vertical and horizontal distances to destination
18 vertical_moves, horizontal_moves = destination
19 result_path = []
20
21 # Build the path character by character
22 for _ in range(horizontal_moves + vertical_moves):
23 if horizontal_moves == 0:
24 # No more horizontal moves left, must go vertical
25 result_path.append("V")
26 else:
27 # Calculate number of paths that start with 'H'
28 # This is the number of ways to arrange remaining moves after placing 'H'
29 paths_starting_with_h = comb(horizontal_moves + vertical_moves - 1,
30 horizontal_moves - 1)
31
32 if k > paths_starting_with_h:
33 # k-th path starts with 'V' since it's beyond paths starting with 'H'
34 result_path.append("V")
35 vertical_moves -= 1
36 # Adjust k to find position among paths starting with 'V'
37 k -= paths_starting_with_h
38 else:
39 # k-th path starts with 'H'
40 result_path.append("H")
41 horizontal_moves -= 1
42
43 return "".join(result_path)
44
1class Solution {
2 public String kthSmallestPath(int[] destination, int k) {
3 // Extract vertical and horizontal distances from destination
4 int verticalSteps = destination[0];
5 int horizontalSteps = destination[1];
6 int totalSteps = verticalSteps + horizontalSteps;
7
8 // Build Pascal's triangle to calculate combinations C(n, k)
9 // combinations[i][j] represents C(i, j) = number of ways to choose j items from i items
10 int[][] combinations = new int[totalSteps + 1][horizontalSteps + 1];
11
12 // Initialize base case: C(n, 0) = 1 for all n
13 combinations[0][0] = 1;
14
15 // Fill the combinations table using Pascal's triangle formula
16 // C(i, j) = C(i-1, j) + C(i-1, j-1)
17 for (int i = 1; i <= totalSteps; ++i) {
18 combinations[i][0] = 1; // C(i, 0) = 1
19 for (int j = 1; j <= horizontalSteps; ++j) {
20 combinations[i][j] = combinations[i - 1][j] + combinations[i - 1][j - 1];
21 }
22 }
23
24 // Build the k-th smallest path
25 StringBuilder result = new StringBuilder();
26
27 // Process each position in the path
28 for (int i = totalSteps; i > 0; --i) {
29 if (horizontalSteps == 0) {
30 // No more horizontal steps left, must go vertical
31 result.append('V');
32 } else {
33 // Calculate number of paths that start with 'H'
34 // This equals the number of ways to arrange remaining steps after choosing 'H'
35 int pathsStartingWithH = combinations[verticalSteps + horizontalSteps - 1][horizontalSteps - 1];
36
37 if (k > pathsStartingWithH) {
38 // k-th path starts with 'V' since it's beyond all paths starting with 'H'
39 result.append('V');
40 k -= pathsStartingWithH; // Adjust k for remaining paths
41 --verticalSteps;
42 } else {
43 // k-th path starts with 'H'
44 result.append('H');
45 --horizontalSteps;
46 }
47 }
48 }
49
50 return result.toString();
51 }
52}
53
1class Solution {
2public:
3 string kthSmallestPath(vector<int>& destination, int k) {
4 // Extract vertical and horizontal distances to destination
5 int verticalSteps = destination[0];
6 int horizontalSteps = destination[1];
7 int totalSteps = verticalSteps + horizontalSteps;
8
9 // Build Pascal's triangle to calculate combinations C(n, k)
10 // combinations[i][j] represents C(i, j) = number of ways to choose j items from i items
11 int combinations[totalSteps + 1][horizontalSteps + 1];
12 memset(combinations, 0, sizeof(combinations));
13
14 // Initialize base case: C(n, 0) = 1 for all n
15 combinations[0][0] = 1;
16
17 // Fill the combinations table using Pascal's triangle formula
18 // C(i, j) = C(i-1, j) + C(i-1, j-1)
19 for (int i = 1; i <= totalSteps; ++i) {
20 combinations[i][0] = 1; // C(i, 0) = 1
21 for (int j = 1; j <= horizontalSteps; ++j) {
22 combinations[i][j] = combinations[i - 1][j] + combinations[i - 1][j - 1];
23 }
24 }
25
26 // Build the k-th smallest path character by character
27 string result;
28
29 for (int i = 0; i < totalSteps; ++i) {
30 // If no horizontal steps left, must go vertical
31 if (horizontalSteps == 0) {
32 result.push_back('V');
33 } else {
34 // Calculate number of paths that start with 'H'
35 // This equals the number of ways to arrange remaining steps after choosing 'H'
36 int pathsStartingWithH = combinations[verticalSteps + horizontalSteps - 1][horizontalSteps - 1];
37
38 // If k is greater than paths starting with 'H',
39 // the k-th path must start with 'V'
40 if (k > pathsStartingWithH) {
41 result.push_back('V');
42 --verticalSteps;
43 k -= pathsStartingWithH; // Update k to find position among paths starting with 'V'
44 } else {
45 // The k-th path starts with 'H'
46 result.push_back('H');
47 --horizontalSteps;
48 }
49 }
50 }
51
52 return result;
53 }
54};
55
1function kthSmallestPath(destination: number[], k: number): string {
2 // Extract vertical and horizontal distances to destination
3 let verticalSteps = destination[0];
4 let horizontalSteps = destination[1];
5 const totalSteps = verticalSteps + horizontalSteps;
6
7 // Build Pascal's triangle to calculate combinations C(n, k)
8 // combinations[i][j] represents C(i, j) = number of ways to choose j items from i items
9 const combinations: number[][] = Array(totalSteps + 1)
10 .fill(null)
11 .map(() => Array(horizontalSteps + 1).fill(0));
12
13 // Initialize base case: C(n, 0) = 1 for all n
14 combinations[0][0] = 1;
15
16 // Fill the combinations table using Pascal's triangle formula
17 // C(i, j) = C(i-1, j) + C(i-1, j-1)
18 for (let i = 1; i <= totalSteps; i++) {
19 combinations[i][0] = 1; // C(i, 0) = 1
20 for (let j = 1; j <= horizontalSteps; j++) {
21 combinations[i][j] = combinations[i - 1][j] + combinations[i - 1][j - 1];
22 }
23 }
24
25 // Build the k-th smallest path character by character
26 let result = "";
27
28 for (let i = 0; i < totalSteps; i++) {
29 // If no horizontal steps left, must go vertical
30 if (horizontalSteps === 0) {
31 result += 'V';
32 } else {
33 // Calculate number of paths that start with 'H'
34 // This equals the number of ways to arrange remaining steps after choosing 'H'
35 const pathsStartingWithH = combinations[verticalSteps + horizontalSteps - 1][horizontalSteps - 1];
36
37 // If k is greater than paths starting with 'H',
38 // the k-th path must start with 'V'
39 if (k > pathsStartingWithH) {
40 result += 'V';
41 verticalSteps--;
42 k -= pathsStartingWithH; // Update k to find position among paths starting with 'V'
43 } else {
44 // The k-th path starts with 'H'
45 result += 'H';
46 horizontalSteps--;
47 }
48 }
49 }
50
51 return result;
52}
53
Time and Space Complexity
Time Complexity: O((h + v)²)
or O(n²)
where n = h + v
The algorithm iterates through a loop h + v
times. In each iteration, it computes comb(h + v - 1, h - 1)
. The combination function comb(n, k)
has a time complexity of O(min(k, n-k))
. In the worst case, when h
and v
are roughly equal, this becomes O(h + v)
for each combination calculation. Since we perform this calculation up to h + v
times, the overall time complexity is O((h + v)²)
.
Space Complexity: O(h + v)
or O(n)
where n = h + v
The space complexity is determined by:
- The
ans
list which stores exactlyh + v
characters:O(h + v)
- The variables
v
,h
,k
, andx
which use constant space:O(1)
- The final string created by
"".join(ans)
:O(h + v)
Therefore, the total space complexity is O(h + v)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Error in Combination Calculation
Pitfall: A common mistake is using the wrong parameters for the combination calculation, such as comb(h + v, h)
instead of comb(h + v - 1, h - 1)
. This error stems from misunderstanding what we're counting - we need the number of ways to arrange the remaining moves after placing the current character.
Why it happens: When we place an 'H' at the current position, we're left with h-1
horizontal moves and v
vertical moves to arrange. The total positions remaining is (h-1) + v = h + v - 1
, and we need to choose h-1
positions for the remaining 'H' characters.
Solution: Always remember that after placing a character, you're counting arrangements of the remaining characters. Use comb(h + v - 1, h - 1)
when determining if the current position should be 'H'.
2. Not Updating k When Choosing 'V'
Pitfall: Forgetting to update k
when placing a 'V' character. Some might only decrement v
but forget to adjust k -= paths_starting_with_h
.
Why it matters: The value of k
represents which path we're looking for among the remaining possibilities. When we skip all paths starting with 'H' (by choosing 'V'), we need to adjust our target position among the paths that start with 'V'.
Solution: Always update k
when choosing 'V':
if k > paths_starting_with_h: result_path.append("V") vertical_moves -= 1 k -= paths_starting_with_h # Don't forget this line!
3. Integer Overflow in Large Combinations
Pitfall: For large grids, combination values can become extremely large. While Python handles big integers naturally, in other languages this could cause overflow.
Example: For a 100x100 grid, C(199, 99)
is an enormous number that would overflow standard integer types in languages like Java or C++.
Solution:
- In Python: The built-in
math.comb
handles large numbers automatically - In other languages: Use specialized libraries for big integers or implement a solution that avoids calculating the full combination value (comparing ratios instead)
4. Incorrect Base Case Handling
Pitfall: Not properly handling the edge case when horizontal_moves == 0
. Some implementations might try to calculate combinations even when no horizontal moves remain, leading to errors or incorrect results.
Solution: Always check if horizontal_moves == 0
before calculating combinations:
if horizontal_moves == 0: result_path.append("V") # No need to update k or calculate combinations else: # Safe to calculate combinations here paths_starting_with_h = comb(horizontal_moves + vertical_moves - 1, horizontal_moves - 1)
Depth first search is equivalent to which of the tree traversal order?
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!