950. Reveal Cards In Increasing Order
Problem Description
You have a deck of cards represented as an integer array deck
, where each card has a unique integer value. The value of the i-th card is deck[i]
.
You need to arrange the deck in a specific order so that when you perform a particular revealing process, the cards are revealed in increasing order.
The revealing process works as follows:
- Take the top card from the deck, reveal it, and remove it from the deck
- If cards remain in the deck, move the next top card to the bottom of the deck
- Repeat steps 1-2 until all cards are revealed
Your task is to find the initial arrangement of the deck that will result in the cards being revealed in increasing order (smallest to largest) when following this process.
For example, if you have cards [17, 13, 11, 2, 3, 5, 7], you need to arrange them in such a way that when you:
- Reveal the top card (should be 2)
- Move the next card to bottom
- Reveal the top card (should be 3)
- Move the next card to bottom
- And continue this pattern...
The cards are revealed as: 2, 3, 5, 7, 11, 13, 17 (in increasing order).
The function should return an array representing the initial deck arrangement, where the first element is the top of the deck.
Intuition
The key insight is to work backwards from the desired outcome. We know we want to reveal cards in increasing order: smallest, second smallest, third smallest, and so on. Instead of trying to figure out the initial arrangement directly, we can simulate the revealing process in reverse.
Think about what happens during the forward process:
- We reveal a card (remove from top)
- We move the next top card to the bottom
- Repeat
To reverse this process:
- Start with the largest card (since it's revealed last)
- To "un-move" a card from bottom to top, we take the bottom card and put it on top
- To "un-reveal" a card, we place it on top of the deck
Working backwards with sorted values in descending order:
- Start with an empty deck
- For each card value (from largest to smallest):
- If the deck isn't empty, "undo" the move operation by taking the bottom card and putting it on top
- Place the current card on top of the deck
This reversal works because each step undoes what would happen in the forward process. When we place the largest card, it will be the last to be revealed. When we place the second-largest card and move a card from bottom to top, we're setting up the deck so that after revealing and moving cards in the forward process, the second-largest will be revealed at the right time.
By building the deck this way from largest to smallest, we ensure that when the forward revealing process is applied, cards will be revealed from smallest to largest.
Solution Approach
The solution implements the reverse simulation approach using a deque (double-ended queue) data structure, which allows efficient operations at both ends.
Implementation Steps:
-
Sort the deck in descending order: First, we sort the input array in reverse order using
sorted(deck, reverse=True)
. This gives us the cards from largest to smallest. -
Initialize a deque: Create an empty deque
q
that will hold our final arrangement. -
Build the deck backwards: For each card value
v
in the sorted array (from largest to smallest):- Undo the "move to bottom" operation: If the deque is not empty, we simulate undoing the move operation by taking the last card (
q.pop()
) and putting it at the front (q.appendleft()
). This reverses the action of moving a card from top to bottom. - Place the current card on top: Add the current card
v
to the front of the deque usingq.appendleft(v)
.
- Undo the "move to bottom" operation: If the deque is not empty, we simulate undoing the move operation by taking the last card (
-
Return the result: Convert the deque to a list and return it.
Why deque? The deque data structure is chosen because it provides O(1) time complexity for both:
appendleft()
: Adding elements to the frontpop()
: Removing elements from the end
Example walkthrough:
For deck = [17, 13, 11, 2, 3, 5, 7]
:
- Sorted descending:
[17, 13, 11, 7, 5, 3, 2]
- Process:
- Add 17:
q = [17]
- Move last to front, add 13:
q = [13, 17]
- Move last to front, add 11:
q = [11, 17, 13]
- Continue this pattern...
- Add 17:
- Final result:
[2, 13, 3, 11, 5, 17, 7]
This arrangement, when processed with the revealing algorithm, will reveal cards in order: 2, 3, 5, 7, 11, 13, 17.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's work through a small example with deck = [1, 2, 3, 4]
to understand how the reverse simulation builds the correct initial arrangement.
Goal: We want cards to be revealed in order: 1, 2, 3, 4
Step 1: Sort in descending order
- Sorted deck:
[4, 3, 2, 1]
- We'll process these from largest to smallest
Step 2: Build the deck backwards using a deque
Starting with empty deque q = []
Processing 4 (largest):
- Deque is empty, so no move operation to undo
- Add 4 to front:
q = [4]
Processing 3:
- Deque is not empty, so undo the move operation:
- Take last card (4) and put it at front:
q = [4]
(no change since only one card)
- Take last card (4) and put it at front:
- Add 3 to front:
q = [3, 4]
Processing 2:
- Deque is not empty, so undo the move operation:
- Take last card (4) and put it at front:
q = [4, 3]
- Take last card (4) and put it at front:
- Add 2 to front:
q = [2, 4, 3]
Processing 1:
- Deque is not empty, so undo the move operation:
- Take last card (3) and put it at front:
q = [3, 2, 4]
- Take last card (3) and put it at front:
- Add 1 to front:
q = [1, 3, 2, 4]
Step 3: Verify the result
Let's verify [1, 3, 2, 4]
reveals cards in order 1, 2, 3, 4:
Initial deck: [1, 3, 2, 4]
(top is leftmost)
- Reveal top card: 1 | Remaining:
[3, 2, 4]
- Move top to bottom:
[2, 4, 3]
- Reveal top card: 2 | Remaining:
[4, 3]
- Move top to bottom:
[3, 4]
- Reveal top card: 3 | Remaining:
[4]
- No cards to move
- Reveal top card: 4 | Remaining:
[]
Cards revealed in order: 1, 2, 3, 4 ✓
The reverse simulation correctly builds the initial arrangement by working backwards from the desired reveal order.
Solution Implementation
1from collections import deque
2from typing import List
3
4class Solution:
5 def deckRevealedIncreasing(self, deck: List[int]) -> List[int]:
6 """
7 Rearrange a deck of cards such that when revealed using a specific pattern,
8 the cards appear in increasing order.
9
10 The reveal pattern is:
11 1. Reveal the top card
12 2. Move the next top card to the bottom of the deck
13 3. Repeat until all cards are revealed
14
15 Args:
16 deck: List of integers representing the deck of cards
17
18 Returns:
19 List of integers representing the rearranged deck
20 """
21 # Initialize a double-ended queue to simulate the deck operations
22 result_queue = deque()
23
24 # Process cards in descending order to reverse the reveal process
25 for card_value in sorted(deck, reverse=True):
26 # If queue is not empty, move the last card to the front
27 # This reverses the "move top card to bottom" operation
28 if result_queue:
29 result_queue.appendleft(result_queue.pop())
30
31 # Add the current card to the front of the queue
32 # This reverses the "reveal top card" operation
33 result_queue.appendleft(card_value)
34
35 # Convert deque back to list and return
36 return list(result_queue)
37
1class Solution {
2 public int[] deckRevealedIncreasing(int[] deck) {
3 // Create a deque to simulate the reveal process in reverse
4 Deque<Integer> deque = new ArrayDeque<>();
5
6 // Sort the deck in ascending order
7 Arrays.sort(deck);
8
9 int length = deck.length;
10
11 // Build the deque by simulating the reveal process in reverse
12 // Starting from the largest card (last element after sorting)
13 for (int i = length - 1; i >= 0; i--) {
14 // If deque is not empty, move the last card to the front
15 // This reverses the "put bottom card to bottom" step
16 if (!deque.isEmpty()) {
17 deque.offerFirst(deque.pollLast());
18 }
19
20 // Add current card to the front of deque
21 // This reverses the "reveal top card" step
22 deque.offerFirst(deck[i]);
23 }
24
25 // Convert deque to result array
26 int[] result = new int[length];
27
28 // Poll elements from the back of deque to maintain correct order
29 for (int i = length - 1; i >= 0; i--) {
30 result[i] = deque.pollLast();
31 }
32
33 return result;
34 }
35}
36
1class Solution {
2public:
3 vector<int> deckRevealedIncreasing(vector<int>& deck) {
4 // Sort the deck in descending order
5 sort(deck.rbegin(), deck.rend());
6
7 // Use deque to simulate the revealing process in reverse
8 deque<int> resultQueue;
9
10 // Process each card from largest to smallest
11 for (int cardValue : deck) {
12 // If queue is not empty, move the last card to the front
13 // This simulates the reverse of: reveal top card, then move next card to bottom
14 if (!resultQueue.empty()) {
15 resultQueue.push_front(resultQueue.back());
16 resultQueue.pop_back();
17 }
18
19 // Add the current card to the front of the queue
20 resultQueue.push_front(cardValue);
21 }
22
23 // Convert deque to vector and return the result
24 return vector<int>(resultQueue.begin(), resultQueue.end());
25 }
26};
27
1function deckRevealedIncreasing(deck: number[]): number[] {
2 // Sort the deck in descending order
3 deck.sort((a, b) => b - a);
4
5 // Use array to simulate the revealing process in reverse
6 const resultQueue: number[] = [];
7
8 // Process each card from largest to smallest
9 for (const cardValue of deck) {
10 // If queue is not empty, move the last card to the front
11 // This simulates the reverse of: reveal top card, then move next card to bottom
12 if (resultQueue.length > 0) {
13 const lastCard = resultQueue.pop()!;
14 resultQueue.unshift(lastCard);
15 }
16
17 // Add the current card to the front of the queue
18 resultQueue.unshift(cardValue);
19 }
20
21 // Return the result array
22 return resultQueue;
23}
24
Time and Space Complexity
Time Complexity: O(n log n)
where n
is the length of the deck array.
- Sorting the deck takes
O(n log n)
time - The for loop iterates through all
n
elements once - Inside the loop,
q.pop()
andq.appendleft()
operations on a deque areO(1)
operations - Therefore, the loop contributes
O(n)
time - Overall time complexity:
O(n log n) + O(n) = O(n log n)
Space Complexity: O(n)
where n
is the length of the deck array.
- The sorted operation creates a new sorted list which takes
O(n)
space - The deque
q
stores alln
elements from the deck, requiringO(n)
space - The final list conversion also creates a new list of size
n
, but this is the return value - Overall space complexity:
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Attempting Forward Simulation Instead of Reverse
One of the most common mistakes is trying to simulate the revealing process forward rather than backward. Many people initially try to place cards directly in their final positions by simulating the reveal process, which becomes extremely complex and error-prone.
Incorrect Approach:
# Trying to simulate forward - This doesn't work!
def incorrectApproach(deck):
sorted_deck = sorted(deck)
result = [0] * len(deck)
positions = list(range(len(deck)))
for i, card in enumerate(sorted_deck):
# Try to find where to place this card
# This becomes very complicated...
Why it fails: The forward simulation requires tracking multiple indices and simulating the exact reveal process while placing cards, which leads to complex index management and off-by-one errors.
2. Forgetting to Handle the Special Case of Single Card
While the provided solution handles this correctly due to the if result_queue:
check, a common mistake is not considering edge cases properly.
Potential Bug:
def buggyVersion(deck):
result_queue = deque()
for card_value in sorted(deck, reverse=True):
# Missing the check for empty queue!
result_queue.appendleft(result_queue.pop()) # Error when queue is empty
result_queue.appendleft(card_value)
return list(result_queue)
3. Using Wrong Data Structure (List Instead of Deque)
Using a regular list instead of deque leads to inefficient O(n) operations for inserting at the beginning.
Inefficient Implementation:
def inefficientVersion(deck):
result = []
for card_value in sorted(deck, reverse=True):
if result:
# O(n) operation - moving last element to front
result = [result[-1]] + result[:-1]
# O(n) operation - inserting at beginning
result = [card_value] + result
return result
Time Complexity Issue: This changes the overall complexity from O(n log n) to O(n²).
4. Incorrect Order of Operations When Building Backwards
A subtle but critical mistake is performing the operations in the wrong order during the reverse simulation.
Wrong Order:
def wrongOrder(deck):
result_queue = deque()
for card_value in sorted(deck, reverse=True):
# Adding card first, then moving - This is wrong!
result_queue.appendleft(card_value)
if len(result_queue) > 1:
result_queue.appendleft(result_queue.pop())
return list(result_queue)
Correct Order: First undo the "move to bottom" operation (if needed), then place the current card on top.
Solution to Avoid These Pitfalls:
-
Think in Reverse: Always approach this problem by thinking about undoing the operations rather than simulating forward.
-
Use Proper Data Structures: Stick with deque for O(1) operations at both ends.
-
Trace Through Small Examples: Before coding, manually trace through a small example (like [1, 2, 3]) in reverse to understand the pattern.
-
Validate with the Original Process: After getting your result, verify it by simulating the actual reveal process to ensure cards appear in increasing order.
Verification Helper:
def verify_solution(arranged_deck):
"""Verify that the arranged deck reveals cards in increasing order."""
queue = deque(arranged_deck)
revealed = []
while queue:
# Reveal top card
revealed.append(queue.popleft())
# Move next top card to bottom (if exists)
if queue:
queue.append(queue.popleft())
# Check if revealed cards are in increasing order
return revealed == sorted(revealed)
Which data structure is used to implement priority queue?
Recommended Readings
Queue Intro Think of the last time you stood in line to buy movie tickets The first person in line gets their ticket first and any newcomers join the end of the line This system where the first person to arrive is the first to be served is a queue in real
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!