Count All Valid Pickup and Delivery Options
HardPrompt
Given n orders, each order consists of a pickup and a delivery service.
Count all valid pickup/delivery possible sequences such that delivery(i) is always after of pickup(i).
Since the answer may be too large, return it modulo 10^9 + 7.
Example 1:
Input: n = 1
Output: 1
Explanation: Unique order (P1, D1), Delivery 1 always is after of Pickup 1.Example 2:
Input: n = 2
Output: 6
Explanation: All possible orders:
(P1,P2,D1,D2), (P1,P2,D2,D1), (P1,D1,P2,D2), (P2,P1,D1,D2), (P2,P1,D2,D1) and (P2,D2,P1,D1).
This is an invalid order (P1,D2,P2,D1) because Pickup 2 is after of Delivery 2.Example 3:
Input: n = 3
Output: 90
Constraints:
1 <= n <= 500
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses recursion with memoization (a top-down dynamic programming technique) to count the valid sequences. We define a function solve(unpicked, undelivered) that calculates the number of ways to arrange the remaining items, where unpicked is the count of orders yet to be picked up, and undelivered is the count of orders that have been picked up but not yet delivered.
Algorithm
- Create a 2D array
memoof size(n+1) x (n+1)and initialize it with a value indicating that the state has not been computed (e.g., 0 or -1). - Define a recursive function
solve(unpicked, undelivered):- If
unpicked == 0andundelivered == 0, it means we have successfully placed all items, so we return 1 (base case). - If
unpicked < 0orundelivered < 0, it's an invalid state, so return 0. - If
memo[unpicked][undelivered]is already computed, return the stored value. - Calculate the number of ways by placing a pickup:
res = (long)unpicked * solve(unpicked - 1, undelivered + 1). We haveunpickedchoices for the pickup. - Add the number of ways by placing a delivery:
res += (long)undelivered * solve(unpicked, undelivered - 1). We haveundeliveredchoices for the delivery. - Store the result modulo
10^9 + 7inmemo[unpicked][undelivered]and return it.
- If
- The main function initiates the process by calling
solve(n, 0).
Walkthrough
We can think of building the sequence of 2n events from left to right. At each step, we have two choices: place a pickup or place a delivery.
Let's define a state by (unpicked, undelivered):
unpicked: The number of orders for which we haven't placed the pickup yet.undelivered: The number of orders for which we have placed the pickup but not the delivery.
At any point, we have unpicked pickups we can place and undelivered deliveries we can place.
-
Choice 1: Place a Pickup.
- We have
unpickedchoices for which pickup to place (e.g., if P1, P2 are unpicked, we can choose either). - After placing a pickup, the number of unpicked orders decreases by 1, and the number of undelivered orders increases by 1.
- The number of ways for this choice is
unpicked * solve(unpicked - 1, undelivered + 1).
- We have
-
Choice 2: Place a Delivery.
- We can only place a delivery for an order whose pickup has already been placed. There are
undeliveredsuch orders. - After placing a delivery, the number of undelivered orders decreases by 1. The number of unpicked orders remains the same.
- The number of ways for this choice is
undelivered * solve(unpicked, undelivered - 1).
- We can only place a delivery for an order whose pickup has already been placed. There are
This leads to the following recurrence relation:
solve(unpicked, undelivered) = (unpicked * solve(unpicked - 1, undelivered + 1)) + (undelivered * solve(unpicked, undelivered - 1))
The base case is solve(0, 0) = 1, as there is one way to arrange zero items. We start with the initial call solve(n, 0). To avoid recomputing the same states, we use a 2D array memo[n+1][n+1] for memoization.
class Solution { long[][] memo; int MOD = 1_000_000_007; public int countOrders(int n) { memo = new long[n + 1][n + 1]; return (int) solve(n, 0); } private long solve(int unpicked, int undelivered) { if (unpicked == 0 && undelivered == 0) { return 1; } if (unpicked < 0 || undelivered < 0) { return 0; } if (memo[unpicked][undelivered] != 0) { return memo[unpicked][undelivered]; } long ans = 0; // Option 1: Pick up an order. // We have 'unpicked' choices. ans += (long) unpicked * solve(unpicked - 1, undelivered + 1); ans %= MOD; // Option 2: Deliver an order. // We have 'undelivered' choices. ans += (long) undelivered * solve(unpicked, undelivered - 1); ans %= MOD; return memo[unpicked][undelivered] = ans; }}Complexity
Time
O(n^2), as there are `n * n` possible states for `(unpicked, undelivered)`, and each state is computed once.
Space
O(n^2) for the memoization table `memo`.
Trade-offs
Pros
Conceptually straightforward application of dynamic programming.
Correctly solves the problem within the time limits for the given constraints.
Cons
Less efficient than the mathematical approach in both time and space.
The state representation and recurrence relation might be tricky to derive.
This approach derives a mathematical recurrence relation by considering the placement of one order pair at a time and then solves it iteratively. This is the most efficient method, leveraging combinatorial reasoning to arrive at a simple formula.
Algorithm
- Let
f(n)be the number of valid sequences fornorders. - Base Case:
f(1) = 1. - Recurrence:
f(n) = f(n-1) * n * (2n - 1). - Iterative Calculation:
- Initialize
ans = 1andMOD = 10^9 + 7. - Loop with a variable
ifrom 1 ton. - In each iteration, update the answer by multiplying with the ways to place the
i-th pair. The number of ways to place thei-th pickup and delivery among2itotal slots isi * (2i - 1). - Update the answer:
ans = (ans * i * (2L * i - 1)) % MOD. - After the loop,
answill hold the result forf(n).
- Initialize
Walkthrough
Let f(n) be the number of valid pickup and delivery sequences for n orders.
Base Case: For n=1, we have one sequence (P1, D1), so f(1) = 1.
Recurrence Relation: Let's find a formula for f(n) based on f(n-1). Assume we have a valid sequence for n-1 orders, which has 2(n-1) positions. Now, we need to insert the new pair, Pn and Dn. There are 2n total slots in the final sequence.
Consider the 2n available slots. We need to choose 2 slots for Pn and Dn. The number of ways to choose 2 slots is C(2n, 2). Once the slots are chosen, Pn must be in the first and Dn in the second to maintain validity. So there is only 1 way to place them for each pair of chosen slots. The remaining 2n-2 slots must be filled by the other n-1 pairs, which can be done in f(n-1) ways.
This gives the recurrence:
f(n) = C(2n, 2) * f(n-1)
f(n) = (2n * (2n-1) / 2) * f(n-1)
f(n) = n * (2n-1) * f(n-1)
We can compute f(n) iteratively:
f(1) = 1f(2) = f(1) * 2 * (2*2 - 1) = 1 * 2 * 3 = 6f(3) = f(2) * 3 * (2*3 - 1) = 6 * 3 * 5 = 90
This can be implemented with a simple loop.
Alternative Derivation
The total number of permutations of 2n distinct items is (2n)!. For each of the n pairs (Pi, Di), Pi must come before Di. By symmetry, in exactly half of the permutations, Pi is before Di. Since there are n such independent constraints, we divide by 2 for each pair.
Total valid sequences = (2n)! / 2^n.
This can be calculated as (1 * 3 * 5 * ... * (2n-1)) * n!, which is equivalent to the iterative calculation from the recurrence.
class Solution { public int countOrders(int n) { long ans = 1; int MOD = 1_000_000_007; for (int i = 1; i <= n; i++) { // For the i-th pair (Pi, Di), we have 2i-1 previous items. // Total slots available are 2i. // Ways to choose 2 slots for Pi and Di is C(2i, 2) = i * (2i - 1). // We multiply this with the arrangements of previous i-1 pairs. // ans for i = (ans for i-1) * i * (2i-1) ans = ans * i; ans %= MOD; ans = ans * (2L * i - 1); ans %= MOD; } return (int) ans; }}Complexity
Time
O(n), as we iterate from 1 to `n` once.
Space
O(1), as we only use a few variables to store the intermediate results.
Trade-offs
Pros
Extremely efficient in both time and space.
Simple and concise implementation.
Cons
Requires a combinatorial insight that might not be immediately obvious.
Solutions
Solution
class Solution {public int countOrders(int n) { final int mod = (int)1 e9 + 7; long f = 1; for (int i = 2; i <= n; ++i) { f = f * i * (2 * i - 1) % mod; } return (int)f; }}class Solution {public: int countOrders(int n) { const int mod = 1e9 + 7; long long f = 1; for (int i = 2; i <= n; ++i) { f = f * i * (2 * i - 1) % mod; } return f; }};class Solution: def countOrders(self, n: int) -> int: mod = 10 ** 9 + 7 f = 1 for i in range(2, n + 1): f = (f * i * (2 * i - 1)) % mod return fVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.