Time Needed to Buy Tickets
EasyPrompt
There are n people in a line queuing to buy tickets, where the 0th person is at the front of the line and the (n - 1)th person is at the back of the line.
You are given a 0-indexed integer array tickets of length n where the number of tickets that the ith person would like to buy is tickets[i].
Each person takes exactly 1 second to buy a ticket. A person can only buy 1 ticket at a time and has to go back to the end of the line (which happens instantaneously) in order to buy more tickets. If a person does not have any tickets left to buy, the person will leave the line.
Return the time taken for the person initially at position k (0-indexed) to finish buying tickets.
Example 1:
Input: tickets = [2,3,2], k = 2
Output: 6
Explanation:
- The queue starts as [2,3,2], where the kth person is underlined.
- After the person at the front has bought a ticket, the queue becomes [3,2,1] at 1 second.
- Continuing this process, the queue becomes [2,1,2] at 2 seconds.
- Continuing this process, the queue becomes [1,2,1] at 3 seconds.
- Continuing this process, the queue becomes [2,1] at 4 seconds. Note: the person at the front left the queue.
- Continuing this process, the queue becomes [1,1] at 5 seconds.
- Continuing this process, the queue becomes [1] at 6 seconds. The kth person has bought all their tickets, so return 6.
Example 2:
Input: tickets = [5,1,1,1], k = 0
Output: 8
Explanation:
- The queue starts as [5,1,1,1], where the kth person is underlined.
- After the person at the front has bought a ticket, the queue becomes [1,1,1,4] at 1 second.
- Continuing this process for 3 seconds, the queue becomes [4] at 4 seconds.
- Continuing this process for 4 seconds, the queue becomes [] at 8 seconds. The kth person has bought all their tickets, so return 8.
Constraints:
n == tickets.length1 <= n <= 1001 <= tickets[i] <= 1000 <= k < n
Approaches
3 approaches with complexity analysis and trade-offs.
This approach directly simulates the ticket-buying process as described in the problem. We use a queue to represent the line of people. Each person buys a ticket, and if they need more, they are sent to the back of the queue. We keep track of the elapsed time until the person at index k has bought all their tickets.
Algorithm
- Create a
Queueand populate it with the indices of people, from0ton-1. - Initialize a
timevariable to0. - Start a loop that continues as long as the person at index
khas tickets to buy. - Inside the loop, dequeue the person's index
pfrom the front of the queue. - Increment the
timecounter. - Decrement the ticket count for person
p. - If person
pis the target person (p == k) and they have just finished buying tickets (tickets[p] == 0), return the totaltime. - If person
pstill needs more tickets (tickets[p] > 0), add their indexpback to the end of the queue.
Walkthrough
The most intuitive way to solve this problem is to replicate the process exactly. We can use a queue, a data structure that perfectly models a first-in, first-out line.
- Initialization: We start by creating a queue and adding the initial indices of all people (
0, 1, ..., n-1) into it. We also initialize atimecounter to zero. - Simulation Loop: The simulation runs in a loop. In each iteration, we simulate one second of time:
- We take the person at the front of the queue (by dequeuing their index).
- We increment our
timecounter. - This person buys one ticket, so we decrement their required ticket count in the
ticketsarray. - We then check if this person was our target person at index
kand if they have now bought all their tickets. If so, the simulation is over, and we return the currenttime. - If the person still has tickets to buy, they go to the back of the line, which we simulate by enqueuing their index.
- If a person finishes buying all their tickets (and they are not the person at index
kwhose completion would stop the process), they simply leave the line and are not added back to the queue.
This process continues until the person at index k buys their last ticket.
import java.util.LinkedList;import java.util.Queue; class Solution { public int timeRequiredToBuy(int[] tickets, int k) { Queue<Integer> queue = new LinkedList<>(); for (int i = 0; i < tickets.length; i++) { queue.add(i); } int time = 0; // The loop continues as long as the queue is not empty. // The condition to stop is checked inside the loop. while (!queue.isEmpty()) { int personIndex = queue.poll(); time++; tickets[personIndex]--; if (personIndex == k && tickets[k] == 0) { return time; } if (tickets[personIndex] > 0) { queue.add(personIndex); } } return time; // Should not be reached given problem constraints }}Complexity
Time
O(N * max(tickets)). In the worst case, the person at `k` needs `tickets[k]` passes through the line. In each pass, there are at most `N` people. Thus, the total time is proportional to the product of the number of people and the number of tickets.
Space
O(N), where N is the number of people. This is for the queue which stores the indices of the people in line.
Trade-offs
Pros
Straightforward to understand as it directly models the real-world process.
Correctly handles the state of the queue at every second.
Cons
Less efficient compared to other approaches, with a time complexity dependent on the number of tickets.
Uses extra space for the queue, which can be up to O(n).
This approach also simulates the process but avoids using an explicit queue data structure. Instead, we can use the tickets array itself and a pointer that iterates through it circularly to represent the line. This can be slightly more efficient in practice due to better memory locality and no overhead from queue operations.
Algorithm
- Initialize
time = 0. - Use a
whileloop that continues as long as the person atkneeds tickets (tickets[k] > 0). - Inside the
whileloop, start aforloop to iterate through all people from indexi = 0ton-1. - For each person
i, check if they still need tickets (tickets[i] > 0). - If they do, decrement their ticket count (
tickets[i]--) and increment the totaltime. - After each ticket purchase, check if the person who just bought a ticket was the target person (
i == k) and if they are now finished (tickets[k] == 0). If so, break the loops and return thetime.
Walkthrough
Instead of managing a separate queue of indices, we can simulate the round-robin nature of the line by repeatedly iterating over the tickets array. Each full iteration from index 0 to n-1 represents one pass where everyone in the line gets a chance to buy a ticket.
- Initialization: We only need a
timevariable, initialized to0. - Simulation: We use nested loops. The outer loop represents the passage of time and continues until our target person at index
kis done. The inner loop iterates fromi = 0ton-1, representing one round of ticket buying.- For each person
i, if they have tickets left (tickets[i] > 0), we simulate them buying one ticket by decrementingtickets[i]and incrementingtime. - A crucial check is performed after every single second (every time
timeis incremented): we see if the person at indexkhas just finished. Iftickets[k]becomes0, we have found our answer and can immediately return the currenttime.
- For each person
This method modifies the input array tickets to keep track of the remaining tickets for each person.
class Solution { public int timeRequiredToBuy(int[] tickets, int k) { int time = 0; int n = tickets.length; // This outer loop can be a while(true) or while(tickets[k] > 0). // It ensures we keep making passes until person k is done. while (tickets[k] > 0) { for (int i = 0; i < n; i++) { // Process person i only if they still need tickets. if (tickets[i] > 0) { tickets[i]--; time++; } // Check for completion condition after each second. if (i == k && tickets[k] == 0) { return time; } } } return time; }}Complexity
Time
O(N * tickets[k]). The logic is equivalent to the queue simulation. The outer loop effectively runs `tickets[k]` times, and the inner loop runs `N` times for each pass.
Space
O(1), assuming we can modify the input `tickets` array. If not, O(N) would be required to hold a copy.
Trade-offs
Pros
More space-efficient, using O(1) extra space if the input array can be modified.
May have slightly better performance than the queue approach due to avoiding queue overhead and having better cache-friendliness.
Cons
The time complexity is still not optimal and is similar to the queue-based approach.
The nested loop structure might seem inefficient, as it iterates over people who may have already finished buying tickets.
The most efficient approach involves a single pass over the tickets array. Instead of simulating the process second-by-second, we can logically deduce the total time by calculating how many tickets each person will have bought by the time the person at index k finishes.
Algorithm
- Initialize
time = 0. - Store the number of tickets the person at
kwants to buy in a variable, let's call ittarget_tickets. - Iterate through the
ticketsarray with an indexifrom0ton-1. - For each person
i, calculate their contribution to the total time:- If the person is at or before
kin the line (i <= k), they will get to buy tickets in every round thatkparticipates in. The number of tickets they buy is the minimum of their own requirement andk's requirement. So, addmin(tickets[i], target_tickets)totime. - If the person is after
kin the line (i > k), they will participate in one fewer round thank. This is because the process stops as soon askbuys their last ticket, and people afterkin that final round don't get a turn. So, addmin(tickets[i], target_tickets - 1)totime.
- If the person is at or before
- After the loop finishes,
timewill hold the total time required.
Walkthrough
We can solve this problem by thinking about the state of the system when the person at index k finishes. Let T_k = tickets[k] be the number of tickets person k needs.
Person k will go through the line T_k times. This means there will be T_k - 1 full rounds where everyone gets a chance to buy a ticket, followed by a final, partial round.
Let's analyze the total time by summing up the time spent by each person:
-
For people at indices
i <= k(including personk):- These people are either ahead of or are person
k. They will be in the line for allT_krounds that personkis. - If a person
ineedstickets[i]tickets, andtickets[i] < T_k, they will finish early after buying alltickets[i]tickets. - If
tickets[i] >= T_k, they will buy one ticket in each of theT_krounds alongside personk. - Therefore, each person
i <= kcontributesmin(tickets[i], T_k)seconds to the total time.
- These people are either ahead of or are person
-
For people at indices
i > k:- These people are behind person
kin the line. - They will participate fully in the first
T_k - 1rounds. - In the final round (the
T_k-th round for personk), personkbuys their last ticket and leaves. The process stops then. The people ati > kdo not get a chance to buy a ticket in this final round. - Therefore, each person
i > kcontributesmin(tickets[i], T_k - 1)seconds to the total time.
- These people are behind person
By summing these contributions in a single loop, we can find the answer in linear time.
class Solution { public int timeRequiredToBuy(int[] tickets, int k) { int time = 0; int targetTickets = tickets[k]; for (int i = 0; i < tickets.length; i++) { if (i <= k) { time += Math.min(tickets[i], targetTickets); } else { // i > k time += Math.min(tickets[i], targetTickets - 1); } } return time; }}Complexity
Time
O(N), where N is the number of people. We only need to iterate through the `tickets` array once.
Space
O(1), as we only use a few variables to store the running total and target value.
Trade-offs
Pros
Extremely efficient, with optimal O(N) time complexity.
Uses constant O(1) extra space.
Avoids the overhead of simulation and data structures.
Cons
The logic is less direct and requires a careful analytical step to derive the formula, making it potentially harder to come up with during an interview.
Solutions
Solution
class Solution { public int timeRequiredToBuy ( int [] tickets , int k ) { int ans = 0 ; for ( int i = 0 ; i < tickets . length ; i ++) { if ( i <= k ) { ans += Math . min ( tickets [ k ], tickets [ i ]); } else { ans += Math . min ( tickets [ k ] - 1 , tickets [ i ]); } } return ans ; } }class Solution {public: int timeRequiredToBuy(vector<int> &tickets, int k) { int ans = 0; for (int i = 0; i < tickets.size(); ++i) { if (i <= k) { ans += min(tickets[k], tickets[i]); } else { ans += min(tickets[k] - 1, tickets[i]); } } return ans; }};class Solution : def timeRequiredToBuy ( self , tickets : List [ int ], k : int ) -> int : ans = 0 for i , t in enumerate ( tickets ): if i <= k : ans += min ( tickets [ k ], t ) else : ans += min ( tickets [ k ] - 1 , t ) return ansVideo 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.