按递增顺序显示卡牌 图解题解
这道题到底在问什么
- 输入
- deck=[2,3,5,8,13,21](值的集合,摆放未知)
- 输出
- [2,8,3,21,5,13]
最优解:一步一步想明白
- 3记住这套「从大到小、先把队尾挪回队首撤销轮转、再把当前牌压入队首」,后面每一帧都在套它。
- 4先看手里有哪些牌。输入顺序其实无所谓,因为题目允许我们任意重排,真正决定答案的只是这些值和张数。
- 5把牌排好序,这一排就是我们希望被「依次亮出」的递增顺序。但注意:这不是牌堆的摆放,而是亮出的先后。难点正是把它倒推回摆放。
- 6从最大的牌开始。队列还空着,直接把 21 放进去(绿色)。它是最后才会被亮出的牌,所以最先入列。
- 7准备放入 13 之前,按套路要先把队尾挪回队首。可现在队列只有一张 21,挪过来还是它,走个流程即可,下一帧再压入 13。
- 8把当前这张 13 压到队首(绿色)。它比已在队列里的都小,会比它们更早被亮出,所以排在更靠前的位置。
- 9准备放入 8 之前,先撤销上一轮「把顶牌移到底部」:把队尾的 21 挪回队首(红色那张)。这一步保证倒放后队列能还原。
- 10把当前这张 8 压到队首(绿色)。它比已在队列里的都小,会比它们更早被亮出,所以排在更靠前的位置。
- 11准备放入 5 之前,先撤销上一轮「把顶牌移到底部」:把队尾的 13 挪回队首(红色那张)。这一步保证倒放后队列能还原。
- 12把当前这张 5 压到队首(绿色)。它比已在队列里的都小,会比它们更早被亮出,所以排在更靠前的位置。
- 13准备放入 3 之前,先撤销上一轮「把顶牌移到底部」:把队尾的 21 挪回队首(红色那张)。这一步保证倒放后队列能还原。
- 14把当前这张 3 压到队首(绿色)。它比已在队列里的都小,会比它们更早被亮出,所以排在更靠前的位置。
- 15准备放入 2 之前,先撤销上一轮「把顶牌移到底部」:把队尾的 8 挪回队首(红色那张)。这一步保证倒放后队列能还原。
- 16把当前这张 2 压到队首(绿色)。它比已在队列里的都小,会比它们更早被亮出,所以排在更靠前的位置。
- 17六张牌从大到小全部放完,队列从队首到队尾就是要求的牌堆摆放:[2, 8, 3, 21, 5, 13]。下面正着走一遍,验证它亮出来确实递增。
- 18亮出当前牌堆顶部的 2(绿色),记到「已亮出」里。到目前为止亮出的序列是 2,一直在变大。
- 19按规则,把新的顶牌 8 移到牌堆底部(红色那张到了队尾),轮到它后面的牌当顶。剩下的牌堆是 [3, 21, 5, 13, 8]。
- 20亮出当前牌堆顶部的 3(绿色),记到「已亮出」里。到目前为止亮出的序列是 2、3,一直在变大。
- 21按规则,把新的顶牌 21 移到牌堆底部(红色那张到了队尾),轮到它后面的牌当顶。剩下的牌堆是 [5, 13, 8, 21]。
- 22亮出当前牌堆顶部的 5(绿色),记到「已亮出」里。到目前为止亮出的序列是 2、3、5,一直在变大。
- 23按规则,把新的顶牌 13 移到牌堆底部(红色那张到了队尾),轮到它后面的牌当顶。剩下的牌堆是 [8, 21, 13]。
- 24亮出当前牌堆顶部的 8(绿色),记到「已亮出」里。到目前为止亮出的序列是 2、3、5、8,一直在变大。
- 25按规则,把新的顶牌 21 移到牌堆底部(红色那张到了队尾),轮到它后面的牌当顶。剩下的牌堆是 [13, 21]。
- 26亮出当前牌堆顶部的 13(绿色),记到「已亮出」里。到目前为止亮出的序列是 2、3、5、8、13,一直在变大。
- 27按规则,把新的顶牌 21 移到牌堆底部(红色那张到了队尾),轮到它后面的牌当顶。剩下的牌堆是 [21]。
- 28亮出当前牌堆顶部的 21(绿色),记到「已亮出」里。到目前为止亮出的序列是 2、3、5、8、13、21,一直在变大。
- 29回放结束,亮出的顺序正好是 2、3、5、8、13、21,一路严格递增。这证明刚才逆向推出的摆放 [2, 8, 3, 21, 5, 13] 完全正确。
⚠️ 容易写错的地方
✗ 错:用普通数组在队首反复插入
✓ 对:用双端队列 deque
数组头部插入是 O(n),整体退化成 O(n²);deque 队首/队尾操作都是 O(1)
✗ 错:从最小的牌开始放
✓ 对:从最大的牌开始放
倒放亮牌过程,最后亮出的是最大牌,所以它最先入列
✗ 错:第一张牌入列前也做轮转
✓ 对:队列为空时跳过轮转
没有「上一次移到底部」可撤销,空队列轮转没有意义
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def deckRevealedIncreasing(self, deck: List[int]) -> List[int]:
q = deque()
for v in sorted(deck, reverse=True):
if q:
q.appendleft(q.pop())
q.appendleft(v)
return list(q)C++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
vector<int> deckRevealedIncreasing(vector<int>& deck) {
sort(deck.rbegin(), deck.rend());
deque<int> q;
for (int v : deck) {
if (!q.empty()) {
q.push_front(q.back());
q.pop_back();
}
q.push_front(v);
}
return vector<int>(q.begin(), q.end());
}
};Java
import java.util.*;
class Solution {
public int[] deckRevealedIncreasing(int[] deck) {
Deque<Integer> q = new ArrayDeque<>();
Arrays.sort(deck);
int n = deck.length;
for (int i = n - 1; i >= 0; --i) {
if (!q.isEmpty()) {
q.offerFirst(q.pollLast());
}
q.offerFirst(deck[i]);
}
int[] ans = new int[n];
for (int i = n - 1; i >= 0; --i) {
ans[i] = q.pollLast();
}
return ans;
}
}复杂度
时间
O(n log n)
排序主导;之后每张牌的轮转+入列都是 O(1),模拟共 O(n)
空间
O(n)
一个双端队列存所有牌;排序栈开销 C++/Java O(log n)、Python 最坏 O(n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 按递增顺序显示卡牌 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么逆向能精确还原摆放?+
正向每一步固定是「亮出顶牌,再把新顶牌移到底部」。把时间倒过来,每一步就变成「先撤销移到底部(队尾挪回队首),再把刚亮的牌放回队首」。从最后一次操作一路倒推到第一次,队列就恢复成最初的摆放,且因为我们按从大到小放,亮出顺序天然递增。
能不能不逆向,用下标正向模拟?+
可以。把下标 0 到 n-1 放进队列,按亮牌规则正向走一遍,记录下标被亮出的先后;第 k 个被亮出的下标,就该摆放排序后第 k 小的牌。两种都是 O(n) 模拟,逆向法代码更短、更省心。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 按递增顺序显示卡牌 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。