LeetCode 969中等双指针 · 滑窗
煎饼排序 图解题解
这道题到底在问什么
给定整数数组 arr(一个 1 到 n 的排列)。一次「煎饼翻转」选一个 k,把前 k 个元素整体反转。请返回一串 k 值,按顺序执行后让 arr 升序,翻转次数不超过 10 倍长度即算正确。
- 输入
- arr=[3,2,4,1]
- 输出
- [3,4,2,3,2] (5 次翻转后变成 [1,2,3,4])
- 输入
- arr=[1,2,3]
- 输出
- [] (本来就有序,不用翻)
最优解:一步一步想明白
- 3记牢这套「找最大,翻到顶,再翻到底」,下面每一轮都在套它。
- 4这就是要排的一摞煎饼,数组 [3,2,4,1]。我们一轮一轮地把当前最大的那张归位,紫色框出的就是还没排好的前缀。
- 5第 1 轮,未排前缀是 [下标 0 到 3]。先看下标 0 的 3,暂时当作最大。
- 6看下标 1 的 2,比 3 小,最大还是 3。
- 7看下标 2 的 4,比刚才的 3 大,最大刷新成 4。
- 8看下标 3 的 1,比 4 小,最大还是 4。
- 9这轮最大的饼是 4,它最终该躺在下标 3(前缀的最末位)。分两步翻过去。
- 10第一翻:翻最上面 3 张(k=3),把 4 先翻到最顶。高亮的就是要翻的区间。
- 11翻完前 3 张,4 现在到了最顶(下标 0)。
- 12第二翻:翻最上面 4 张(k=4),把顶上的 4 一路翻到下标 3。
- 13翻完前 4 张,4 归位在下标 3(蓝色),已排好的尾巴又长了一节。
- 14第 2 轮,未排前缀是 [下标 0 到 2]。先看下标 0 的 1,暂时当作最大。
- 15看下标 1 的 3,比刚才的 1 大,最大刷新成 3。
- 16看下标 2 的 2,比 3 小,最大还是 3。
- 17这轮最大的饼是 3,它最终该躺在下标 2(前缀的最末位)。分两步翻过去。
- 18第一翻:翻最上面 2 张(k=2),把 3 先翻到最顶。高亮的就是要翻的区间。
- 19翻完前 2 张,3 现在到了最顶(下标 0)。
- 20第二翻:翻最上面 3 张(k=3),把顶上的 3 一路翻到下标 2。
- 21翻完前 3 张,3 归位在下标 2(蓝色),已排好的尾巴又长了一节。
- 22第 3 轮,未排前缀是 [下标 0 到 1]。先看下标 0 的 2,暂时当作最大。
- 23看下标 1 的 1,比 2 小,最大还是 2。
- 24这轮最大是 2,而且它已经在最顶(下标 0)了,第一次翻转可以省掉,直接做第二翻。
- 25第二翻:翻最上面 2 张(k=2),把顶上的 2 一路翻到下标 1。
- 26翻完前 2 张,2 归位在下标 1。最小的 1 也顺势落到下标 0,整摞全好了。
- 27全部升序排好![1,2,3,4]。一路记录的翻转张数连起来就是答案:k 序列 3,4,2,3,2,一共 5 次翻转。
⚠️ 容易写错的地方
✗ 错:把 k 当成下标
✓ 对:k 是「张数」,翻的是前 k 张即区间 [0, k-1]
最大在下标 j 要翻到顶,记的 k 是 j+1 不是 j,差一位会翻错段
✗ 错:最大已在顶还多翻一次
✓ 对:j=0 时跳过第一翻,直接做第二翻
多翻 k=1 等于没翻,纯属浪费;写成翻 [0,0] 还会污染答案序列
✗ 错:最大已在末位仍硬翻
✓ 对:j=i 时整轮跳过
它已归位,再翻会把已排好的部分打乱
完整代码(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 pancakeSort(self, arr: List[int]) -> List[int]:
def reverse(arr, j):
i = 0
while i < j:
arr[i], arr[j] = arr[j], arr[i]
i, j = i + 1, j - 1
n = len(arr)
ans = []
for i in range(n - 1, 0, -1):
j = i
while j > 0 and arr[j] != i + 1:
j -= 1
if j < i:
if j > 0:
ans.append(j + 1)
reverse(arr, j)
ans.append(i + 1)
reverse(arr, i)
return ansC++
#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> pancakeSort(vector<int>& arr) {
int n = arr.size();
vector<int> ans;
for (int i = n - 1; i > 0; --i) {
int j = i;
for (; j > 0 && arr[j] != i + 1; --j)
;
if (j == i) continue;
if (j > 0) {
ans.push_back(j + 1);
reverse(arr.begin(), arr.begin() + j + 1);
}
ans.push_back(i + 1);
reverse(arr.begin(), arr.begin() + i + 1);
}
return ans;
}
};Java
import java.util.*;
class Solution {
public List<Integer> pancakeSort(int[] arr) {
int n = arr.length;
List<Integer> ans = new ArrayList<>();
for (int i = n - 1; i > 0; --i) {
int j = i;
for (; j > 0 && arr[j] != i + 1; --j)
;
if (j < i) {
if (j > 0) {
ans.add(j + 1);
reverse(arr, j);
}
ans.add(i + 1);
reverse(arr, i);
}
}
return ans;
}
private void reverse(int[] arr, int j) {
for (int i = 0; i < j; ++i, --j) {
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
}复杂度
时间
O(n²)
共 n-1 轮,每轮扫一遍前缀找最大并翻转,都是 O(n)
空间
O(1)
原地翻转,只用几个下标变量;答案数组不计入额外空间
翻转次数
≤ 2(n-1)
每轮最多两翻,远在 10n 的限制内
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 煎饼排序 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这套贪心的翻转次数上界是多少?会超题目限制吗?+
每个元素归位最多用两翻,一共 n-1 轮,所以翻转次数不超过 2(n-1)。题目允许 10 倍长度,2(n-1) 远小于 10n,永远不会超限。
能不能找到「最少翻转次数」的解?+
本解只保证次数在限制内,不追求最少。求精确最少翻转次数的「煎饼数」是个很难的组合问题,没有已知的高效精确算法,面试通常只要这套 O(n²) 的简单贪心即可。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 煎饼排序 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。