Before attempting this problem, you should be comfortable with:
Recursion - Exploring all valid orderings by branching on pickup and delivery choices
Dynamic Programming - Memoizing states to avoid redundant computation of overlapping subproblems
Combinatorics - Understanding permutations and how constraints reduce valid arrangements
Modular Arithmetic - Applying modulo operations to prevent integer overflow in large results
1. Recursion
Intuition
We need to count all valid orderings where each delivery happens after its corresponding pickup. At any point, we can either pick up a new order (if any remain) or deliver an order that has already been picked up. The number of choices at each step depends on how many orders are still available for pickup and how many are picked but not yet delivered.
Algorithm
Use recursion with two state variables: the count of picked orders and delivered orders.
At each step, if we can pick up more orders, branch with (n - picked) choices for which order to pick next.
If there are orders picked but not delivered, branch with (picked - delivered) choices for which order to deliver.
Return 1 when all orders are picked and delivered (base case).
Multiply the count of choices at each branch and sum all paths.
classSolution:defcountOrders(self, n:int)->int:
MOD =1000000007defdfs(picked, delivered):if picked == n and delivered == n:return1
res =0if picked < n:
res =(res +(n - picked)* dfs(picked +1, delivered))% MOD
if delivered < picked:
res =(res +(picked - delivered)* dfs(picked, delivered +1))% MOD
return res
return dfs(0,0)
publicclassSolution{privatestaticfinalintMOD=1_000_000_007;publicintcountOrders(int n){returndfs(0,0, n);}privateintdfs(int picked,int delivered,int n){if(picked == n && delivered == n){return1;}long res =0;if(picked < n){
res =((res +(n - picked)*1L*dfs(picked +1, delivered, n))%MOD);}if(delivered < picked){
res =((res +(picked - delivered)*1L*dfs(picked, delivered +1, n))%MOD);}return(int) res;}}
classSolution{public:staticconstint MOD =1'000'000'007;intcountOrders(int n){returndfs(0,0, n);}private:intdfs(int picked,int delivered,int n){if(picked == n && delivered == n){return1;}int res =0;if(picked < n){
res =(res +(n - picked)*1LL*dfs(picked +1, delivered, n))% MOD;}if(delivered < picked){
res =(res +(picked - delivered)*1LL*dfs(picked, delivered +1, n))% MOD;}return res;}};
classSolution{/**
* @param {number} n
* @return {number}
*/countOrders(n){constMOD=1_000_000_007;constdfs=(picked, delivered)=>{if(picked === n && delivered === n){return1;}let res =0;if(picked < n){
res =(res +(n - picked)*dfs(picked +1, delivered))%MOD;}if(delivered < picked){
res =(res +(picked - delivered)*dfs(picked, delivered +1))%MOD;}return res;};returndfs(0,0);}}
publicclassSolution{privateconstint MOD =1_000_000_007;publicintCountOrders(int n){returnDfs(0,0, n);}privateintDfs(int picked,int delivered,int n){if(picked == n && delivered == n){return1;}long res =0;if(picked < n){
res =(res +(n - picked)*1L*Dfs(picked +1, delivered, n))% MOD;}if(delivered < picked){
res =(res +(picked - delivered)*1L*Dfs(picked, delivered +1, n))% MOD;}return(int) res;}}
funccountOrders(n int)int{
MOD :=1000000007var dfs func(picked, delivered int)int
dfs =func(picked, delivered int)int{if picked == n && delivered == n {return1}
res :=0if picked < n {
res =(res +(n-picked)*dfs(picked+1, delivered))% MOD
}if delivered < picked {
res =(res +(picked-delivered)*dfs(picked, delivered+1))% MOD
}return res
}returndfs(0,0)}
class Solution {privateval MOD =1_000_000_007funcountOrders(n: Int): Int {returndfs(0,0, n)}privatefundfs(picked: Int, delivered: Int, n: Int): Int {if(picked == n && delivered == n){return1}var res: Long =0if(picked < n){
res =(res +(n - picked).toLong()*dfs(picked +1, delivered, n))% MOD
}if(delivered < picked){
res =(res +(picked - delivered).toLong()*dfs(picked, delivered +1, n))% MOD
}return res.toInt()}}
classSolution{letMOD=1_000_000_007funccountOrders(_ n:Int)->Int{funcdfs(_ picked:Int,_ delivered:Int)->Int{if picked == n && delivered == n {return1}var res =0if picked < n {
res =(res +(n - picked)*dfs(picked +1, delivered))%MOD}if delivered < picked {
res =(res +(picked - delivered)*dfs(picked, delivered +1))%MOD}return res
}returndfs(0,0)}}
Time & Space Complexity
Time complexity: O(2n)
Space complexity: O(n) for recursion stack.
2. Dynamic Programming (Top-Down)
Intuition
The recursive solution recalculates the same states multiple times. For example, the state (picked=2, delivered=1) might be reached through different orderings. By caching these results, we avoid redundant computation.
Algorithm
Create a 2D memoization table indexed by (picked, delivered).
Before computing a state, check if it exists in the cache and return it if found.
Apply the same recursive logic as before: pick an unpicked order or deliver a picked order.
Store the computed result in the cache before returning.
Start the recursion from (0, 0) and return the cached result.
classSolution{/**
* @param {number} n
* @return {number}
*/countOrders(n){constMOD=1_000_000_007;const dp = Array.from({length: n +1},()=>Array(n +1).fill(-1));constdfs=(picked, delivered)=>{if(picked === n && delivered === n){return1;}if(dp[picked][delivered]!==-1){return dp[picked][delivered];}let res =0;if(picked < n){
res =(res +(n - picked)*dfs(picked +1, delivered))%MOD;}if(delivered < picked){
res =(res +(picked - delivered)*dfs(picked, delivered +1))%MOD;}
dp[picked][delivered]= res;return res;};returndfs(0,0);}}
publicclassSolution{privateconstint MOD =1_000_000_007;privateint[,] dp;publicintCountOrders(int n){
dp =newint[n +1, n +1];for(int i =0; i <= n; i++){for(int j =0; j <= n; j++){
dp[i, j]=-1;}}
dp[n, n]=1;returnDfs(0,0, n);}privateintDfs(int picked,int delivered,int n){if(dp[picked, delivered]!=-1){return dp[picked, delivered];}long res =0;if(picked < n){
res =(res +(n - picked)*1L*Dfs(picked +1, delivered, n))% MOD;}if(delivered < picked){
res =(res +(picked - delivered)*1L*Dfs(picked, delivered +1, n))% MOD;}
dp[picked, delivered]=(int)res;return(int)res;}}
funccountOrders(n int)int{
MOD :=1000000007
dp :=make([][]int, n+1)for i :=range dp {
dp[i]=make([]int, n+1)for j :=range dp[i]{
dp[i][j]=-1}}
dp[n][n]=1var dfs func(picked, delivered int)int
dfs =func(picked, delivered int)int{if dp[picked][delivered]!=-1{return dp[picked][delivered]}
res :=0if picked < n {
res =(res +(n-picked)*dfs(picked+1, delivered))% MOD
}if delivered < picked {
res =(res +(picked-delivered)*dfs(picked, delivered+1))% MOD
}
dp[picked][delivered]= res
return res
}returndfs(0,0)}
class Solution {privateval MOD =1_000_000_007privatelateinitvar dp: Array<IntArray>funcountOrders(n: Int): Int {
dp =Array(n +1){IntArray(n +1){-1}}
dp[n][n]=1returndfs(0,0, n)}privatefundfs(picked: Int, delivered: Int, n: Int): Int {if(dp[picked][delivered]!=-1){return dp[picked][delivered]}var res: Long =0if(picked < n){
res =(res +(n - picked).toLong()*dfs(picked +1, delivered, n))% MOD
}if(delivered < picked){
res =(res +(picked - delivered).toLong()*dfs(picked, delivered +1, n))% MOD
}
dp[picked][delivered]= res.toInt()return res.toInt()}}
classSolution{letMOD=1_000_000_007var dp:[[Int]]=[]funccountOrders(_ n:Int)->Int{
dp =Array(repeating:Array(repeating:-1, count: n +1), count: n +1)
dp[n][n]=1funcdfs(_ picked:Int,_ delivered:Int)->Int{if dp[picked][delivered]!=-1{return dp[picked][delivered]}var res =0if picked < n {
res =(res +(n - picked)*dfs(picked +1, delivered))%MOD}if delivered < picked {
res =(res +(picked - delivered)*dfs(picked, delivered +1))%MOD}
dp[picked][delivered]= res
return res
}returndfs(0,0)}}
Time & Space Complexity
Time complexity: O(n2)
Space complexity: O(n2)
3. Dynamic Programming (Bottom-Up)
Intuition
Instead of recursing from (0, 0) and memoizing, we can build the solution iteratively. We start from the base case where no orders are processed and accumulate the number of ways to reach each state by considering all valid transitions.
Algorithm
Create a 2D DP table where dp[picked][delivered] represents the number of ways to reach that state.
Initialize dp[0][0] = 1 as the starting point.
Iterate through all states in order of increasing picked and delivered counts.
For each state, add its contribution to the next states: picking a new order or delivering a picked order.
In the bottom-up approach, when processing states for a given number of picked orders, we only need the current row of delivered counts. After processing all deliveries for the current picked count, we can transition to the next picked count and discard the old row.
Algorithm
Use a 1D array dp to track the number of ways for each delivered count.
For each picked count, first process all possible deliveries within that row.
Before moving to the next picked count, create a new array and populate it based on the pickup transition.
Continue until all n orders are processed.
Return dp[n] which represents the state where all n orders are picked and delivered.
Think of placing pickup and delivery events into 2n slots. For each order, we place its pickup first and then choose where to put the delivery among the remaining slots. When placing order i, there are (2i - 1) slots left, and we can place the delivery in any of the (2i - 1) * 2i / 2 ways (choosing 2 slots for the pair where pickup comes first).
Algorithm
Initialize the result to 1 and start with 2n available slots.
For each order, compute the number of valid ways to place its pickup-delivery pair: slots * (slots - 1) / 2.
Multiply this into the result and reduce the slot count by 2.
classSolution:defcountOrders(self, n:int)->int:
MOD =1000000007
slots, res =2* n,1while slots >0:
valid_choices = slots *(slots -1)//2
res =(res * valid_choices)% MOD
slots -=2return res
publicclassSolution{publicintcountOrders(int n){intMOD=1000000007;long slots =2* n, res =1;while(slots >0){long validChoices = slots *(slots -1)/2;
res =(res * validChoices)%MOD;
slots -=2;}return(int) res;}}
classSolution{public:intcountOrders(int n){constint MOD =1000000007;longlong slots =2* n, res =1;while(slots >0){longlong validChoices = slots *(slots -1)/2;
res =(res * validChoices)% MOD;
slots -=2;}return(int) res;}};
classSolution{/**
* @param {number} n
* @return {number}
*/countOrders(n){constMOD=1000000007;let slots =2* n,
res =1;while(slots >0){let validChoices =(slots *(slots -1))/2;
res =(res * validChoices)%MOD;
slots -=2;}return res;}}
publicclassSolution{publicintCountOrders(int n){int MOD =1000000007;long slots =2* n, res =1;while(slots >0){long validChoices = slots *(slots -1)/2;
res =(res * validChoices)% MOD;
slots -=2;}return(int)res;}}
funccountOrders(n int)int{
MOD :=1000000007
slots :=2* n
res :=1for slots >0{
validChoices := slots *(slots -1)/2
res =(res * validChoices)% MOD
slots -=2}return res
}
class Solution {funcountOrders(n: Int): Int {val MOD =1000000007var slots =2L* n
var res =1Lwhile(slots >0){val validChoices = slots *(slots -1)/2
res =(res * validChoices)% MOD
slots -=2}return res.toInt()}}
classSolution{funccountOrders(_ n:Int)->Int{letMOD=1000000007var slots =2* n
var res =1while slots >0{let validChoices = slots *(slots -1)/2
res =(res * validChoices)%MOD
slots -=2}return res
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(1)
6. Probability
Intuition
The total number of arrangements of 2n events is (2n)!. However, for each order, the delivery must come after the pickup, which has a 1/2 probability in a random arrangement. Since we have n independent orders, the valid arrangements are (2n)! / 2^n.
Algorithm
Initialize the result to 1.
Iterate through slots from 1 to 2n.
Multiply the result by the current slot number.
Whenever the slot is even (meaning we just placed a delivery), divide by 2 to account for the ordering constraint.
Take modulo at each step and return the final result.
classSolution:defcountOrders(self, n:int)->int:
MOD =1000000007
res =1for slot inrange(1,2* n +1):
res *= slot
if slot %2==0:
res >>=1
res %= MOD
return res
publicclassSolution{publicintcountOrders(int n){intMOD=1000000007;long res =1;for(int slot =1; slot <=2* n; slot++){
res *= slot;if(slot %2==0){
res >>=1;}
res %=MOD;}return(int) res;}}
classSolution{public:intcountOrders(int n){constint MOD =1000000007;longlong res =1;for(int slot =1; slot <=2* n; slot++){
res *= slot;if(slot %2==0){
res >>=1;}
res %= MOD;}return(int) res;}};
classSolution{/**
* @param {number} n
* @return {number}
*/countOrders(n){constMOD=BigInt(1000000007);let res =BigInt(1);for(let slot =1; slot <=2* n; slot++){
res *=BigInt(slot);if(slot %2===0){
res /=BigInt(2);}
res %=MOD;}returnNumber(res);}}
publicclassSolution{publicintCountOrders(int n){int MOD =1000000007;long res =1;for(int slot =1; slot <=2* n; slot++){
res *= slot;if(slot %2==0){
res /=2;}
res %= MOD;}return(int)res;}}
funccountOrders(n int)int{
MOD :=1000000007
res :=1for slot :=1; slot <=2*n; slot++{
res *= slot
if slot%2==0{
res /=2}
res %= MOD
}return res
}
class Solution {funcountOrders(n: Int): Int {val MOD =1000000007var res =1Lfor(slot in1..2* n){
res *= slot
if(slot %2==0){
res /=2}
res %= MOD
}return res.toInt()}}
classSolution{funccountOrders(_ n:Int)->Int{letMOD=1000000007var res =1for slot in1...(2* n){
res *= slot
if slot %2==0{
res /=2}
res %=MOD}return res
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(1)
Common Pitfalls
Forgetting Modular Arithmetic
The result grows extremely fast (factorial-like), causing integer overflow. You must take modulo at each multiplication step, not just at the end.
# Wrong: overflow before final mod
res = res * validChoices
return res % MOD
# Correct: mod at each step
res =(res * validChoices)% MOD
Allowing Delivery Before Pickup
Some solutions incorrectly count all permutations of 2n events. The constraint that each delivery must come after its pickup is essential and reduces valid arrangements by a factor of 2^n.
Integer Overflow in Intermediate Calculations
When multiplying two integers before taking modulo, the intermediate result can overflow. Use explicit casting to long or 64-bit integers.
// Wrong: overflow before mod
res =(res +(n - picked)*dfs(...))%MOD;// Correct: cast to long first
res =(res +(n - picked)*1L*dfs(...))%MOD;
Miscounting Slot Choices
In the combinatorics approach, the valid choices for placing a pickup-delivery pair is slots * (slots - 1) / 2, not slots * (slots - 1). The division by 2 accounts for the fact that pickup must come before delivery.
Wrong Base Case in Recursion
The base case should return 1 when all orders are picked AND delivered, not just when all are picked. Returning early causes undercounting.