Reveal Cards In Increasing Order
Description
You get a deck of cards, each with a distinct integer, in any order. A dealer handles the deck like this:
- Turn the top card face up and set it aside.
- If cards are left, move the next top card to the bottom of the deck.
- Repeat until no cards are left.
Return an ordering of the deck, top card first, that makes the cards come face up in increasing order.
Examples
Example 1
Input: deck = [17, 13, 11, 2, 3, 5, 7]
Output: [2, 13, 3, 11, 5, 17, 7]
Explanation: Deal [2, 13, 3, 11, 5, 17, 7]: turn up 2 and move 13 to the bottom, turn up 3 and move 11 down, and so on. The cards come up 2, 3, 5, 7, 11, 13, 17.
Example 2
Input: deck = [1, 1000]
Output: [1, 1000]
Explanation: Turn up 1, move 1000 to the bottom, then turn it up.
Example 3
Input: deck = [42]
Output: [42]
Explanation: A single card is already in order.
Constraints
- 1 ≤ deck.length ≤ 1000
- 1 ≤ deck[i] ≤ 10^6
- All the values of
deckare unique
Editorial
Brute Force
Intuition
Run the deal backwards. The last card turned up is the largest, and before that the dealer had just moved a card from the top to the bottom. So start from an empty pile and take the cards from the largest down: move the bottom card back to the top (undoing the move), then put the card on top (undoing the turn-up). When every card is placed, the pile is the answer.
Step-by-Step Explanation
Let's trace deck = [17, 13, 11, 2, 3, 5, 7]:
- Put 17, the largest, on the empty pile. The pile is 17.
- Move 17 from the bottom back to the top, then put 13 on top. The pile is 13, 17.
- Move 17 from the bottom back to the top, then put 11 on top. The pile is 11, 17, 13.
- Move 13 from the bottom back to the top, then put 7 on top. The pile is 7, 13, 11, 17.
- Move 17 from the bottom back to the top, then put 5 on top. The pile is 5, 17, 7, 13, 11.
- Move 11 from the bottom back to the top, then put 3 on top. The pile is 3, 11, 5, 17, 7, 13.
- Move 13 from the bottom back to the top, then put 2 on top. The pile is 2, 13, 3, 11, 5, 17, 7.
Undo the Deal — Each card goes on top after the bottom card moves back up.
Algorithm
- Sort the cards from largest to smallest.
- For each card: if the pile isn't empty, move its bottom card to the top. Then put the card on top.
- Return the pile, top first.
Code
class Solution {
public:
vector<int> deckRevealedIncreasing(vector<int>& deck) {
vector<int> sorted = deck;
sort(sorted.rbegin(), sorted.rend()); // the largest first: it's turned up last
vector<int> pile; // top first
for (int card : sorted) {
if (!pile.empty()) { // undo "move the top card to the bottom"
pile.insert(pile.begin(), pile.back());
pile.pop_back();
}
pile.insert(pile.begin(), card); // undo "turn up the top card"
}
return pile;
}
};
class Solution:
def deckRevealedIncreasing(self, deck: list[int]) -> list[int]:
pile = [] # top first
for card in sorted(deck, reverse=True): # the largest first: it's turned up last
if pile:
pile.insert(0, pile.pop()) # undo "move the top card to the bottom"
pile.insert(0, card) # undo "turn up the top card"
return pile
class Solution {
public int[] deckRevealedIncreasing(int[] deck) {
int[] sorted = deck.clone();
Arrays.sort(sorted);
List<Integer> pile = new ArrayList<>(); // top first
for (int i = sorted.length - 1; i >= 0; i--) { // the largest first: it's turned up last
if (!pile.isEmpty()) pile.add(0, pile.remove(pile.size() - 1)); // undo "move the top card down"
pile.add(0, sorted[i]); // undo "turn up the top card"
}
int[] result = new int[pile.size()];
for (int i = 0; i < result.length; i++) result[i] = pile.get(i);
return result;
}
}
var deckRevealedIncreasing = function(deck) {
const sorted = [...deck].sort((a, b) => b - a); // the largest first: it's turned up last
const pile = []; // top first
for (const card of sorted) {
if (pile.length) pile.unshift(pile.pop()); // undo "move the top card to the bottom"
pile.unshift(card); // undo "turn up the top card"
}
return pile;
};
Complexity Analysis
Time Complexity: O(n²)
Sorting is O(n log n), but every insert at the top of a list shifts the whole pile: n inserts of up to n moves each.
Space Complexity: O(n)
The pile.
Why This Approach Is Not Efficient
With at most 1,000 cards it still fits: about 10^6 moves. The cost comes from inserting at the top of a list; a deque would make those O(1). But there's a simpler way to see the whole problem: the deal itself doesn't depend on the card values at all. Position 0 is always turned up first, position 1 always goes to the bottom, and so on.
So run the dealer forwards, once, on the positions instead of the cards, with a queue. The order in which positions come up is the order the sorted cards must fill them.
Optimal Approach - Deal the Positions With a Queue
Intuition
Put the positions 0 to n − 1 in a queue: that's the face-down deck. Sort the cards. Then deal the queue the dealer's way: pop the front position, which is turned up next, and give it the smallest card not yet placed; then, if positions are left, move the next front position to the back. Each position ends up with the card that comes up at its turn, so the cards come up sorted.
Step-by-Step Explanation
Let's trace deck = [17, 13, 11, 2, 3, 5, 7], sorted 2, 3, 5, 7, 11, 13, 17:
- Position 0 is turned up: it gets 2. Position 1 moves to the bottom.
- Position 2 is turned up: it gets 3. Position 3 moves to the bottom.
- Position 4 is turned up: it gets 5. Position 5 moves to the bottom.
- Position 6 is turned up: it gets 7. Position 1 moves to the bottom.
- Position 3 is turned up: it gets 11. Position 5 moves to the bottom.
- Position 1 is turned up: it gets 13. Position 5 moves to the bottom.
- Position 5 is the last: it gets 17.
The arrangement is [2, 13, 3, 11, 5, 17, 7].
Deal the Positions — The queue holds the positions still face down; each one turned up gets the next smallest card.
Algorithm
- Sort
deck. Put the positions 0 … n − 1 in a queue. - For each card, smallest first: pop the front position and write the card there. If the queue isn't empty, pop its front and push it to the back.
- Return the result array.
Code
JavaScript keeps the queue in an array with a head index, so no shift() is needed.
class Solution {
public:
vector<int> deckRevealedIncreasing(vector<int>& deck) {
int n = deck.size();
queue<int> positions; // the deck's positions, top first
for (int i = 0; i < n; i++) positions.push(i);
vector<int> sorted = deck;
sort(sorted.begin(), sorted.end());
vector<int> result(n);
for (int card : sorted) { // the smallest card goes to the first position turned up
result[positions.front()] = card; // turn the top position up
positions.pop();
if (!positions.empty()) { // move the next one to the bottom
positions.push(positions.front());
positions.pop();
}
}
return result;
}
};
from collections import deque
class Solution:
def deckRevealedIncreasing(self, deck: list[int]) -> list[int]:
positions = deque(range(len(deck))) # the deck's positions, top first
result = [0] * len(deck)
for card in sorted(deck): # the smallest card goes to the first position turned up
result[positions.popleft()] = card # turn the top position up
if positions:
positions.append(positions.popleft()) # move the next one to the bottom
return result
class Solution {
public int[] deckRevealedIncreasing(int[] deck) {
int n = deck.length;
Deque<Integer> positions = new ArrayDeque<>(); // the deck's positions, top first
for (int i = 0; i < n; i++) positions.offerLast(i);
int[] sorted = deck.clone();
Arrays.sort(sorted);
int[] result = new int[n];
for (int card : sorted) { // the smallest card goes to the first position turned up
result[positions.pollFirst()] = card; // turn the top position up
if (!positions.isEmpty()) positions.offerLast(positions.pollFirst()); // move the next one to the bottom
}
return result;
}
}
var deckRevealedIncreasing = function(deck) {
const n = deck.length;
const positions = []; // the deck's positions, top first, as a queue
for (let i = 0; i < n; i++) positions.push(i);
let head = 0; // the top of the queue; no shift() needed
const result = new Array(n);
const sorted = [...deck].sort((a, b) => a - b);
for (const card of sorted) { // the smallest card goes to the first position turned up
result[positions[head++]] = card; // turn the top position up
if (head < positions.length) positions.push(positions[head++]); // move the next one to the bottom
}
return result;
};
Complexity Analysis
Time Complexity: O(n log n)
The sort; the deal itself is O(n), one pop and at most one move per card.
Space Complexity: O(n)
The queue of positions and the result.
Common Mistakes
Each of these looks right on a quick read, and the tests still reject it. The outputs below come from running the code.
Moving a card down before turning one up. positions.append(positions.popleft())
On deck = [17, 13, 11, 2, 3, 5, 7] it returns [7, 2, 13, 3, 11, 5, 17], which deals as 7, 13, 11, 17, 3, 2 and 5; the answer is [2, 13, 3, 11, 5, 17, 7]. The dealer turns the top card up first and only then moves the next one to the bottom. Swapping the two steps deals a different order, so the cards land in the wrong positions.
Dealing the cards in the order given. for card in deck:
On deck = [5, 2, 4, 1, 3] it returns [5, 3, 2, 1, 4], which deals as 5, 2, 4, 1 and 3; the answer is [1, 5, 2, 4, 3]. The cards have to come up smallest first, so the smallest card goes to the first position turned up. Dealing the deck in the order it arrived only works when it arrives sorted.
Undoing the deal from the smallest card. for card in sorted(deck):
On deck = [17, 13, 11, 2, 3, 5, 7] it returns [17, 3, 13, 5, 11, 2, 7], which deals as 17, 13, 11, 7, 5, 3 and 2; the answer is [2, 13, 3, 11, 5, 17, 7]. Undoing the deal starts from the last card turned up, the largest. Starting from the smallest builds a deck that comes up in decreasing order.
Where This Pattern Is Taught
This problem practises running a process with a queue. It's taught in Introduction to Queues, alongside the Josephus circle of Find the Winner of the Circular Game.