分割数组为连续子序列 图解题解
这道题到底在问什么
- 输入
- nums=[1,2,3,3,4,5]
- 输出
- true (拆成 [1,2,3] 和 [3,4,5])
- 输入
- nums=[1,2,3,4,4,5]
- 输出
- false (剩下的凑不出长度 ≥ 3 的连续段)
最优解:一步一步想明白
- 3核心口诀:能接尾就接尾,不能接就起一条新三连,都不行就 false。为什么优先接尾?因为接尾不浪费、还能让已有短序列变长,更容易达标。下面每一帧都在套这套规则。
- 4第一步:数一遍每个数有几个,填进 cnt(剩余可用次数)。need 现在全是 0,因为还没有任何子序列在等着被接。
- 5先把 need 读懂:need[x] 大于 0,就表示前面有现成的子序列结尾在 x-1,正张着嘴等一个 x。接下来从第 0 个开始扫。
- 6扫到第 0 个 1。它还有剩(cnt[1]=1)。先查 need[1]=0:有没有序列正等着接 1?
- 7没人等 1,那就用 1、2、3 现起一条长度 3 的新序列(绿色)。把 2、3 各扣 1 表示被领走,这条新序列结尾是 3,改成等 4:need[4] 加 1。
- 8扫到第 1 个 2,但 cnt[2] 已经是 0,说明这个 2 早就被前面某条序列取走了,跳过它。
- 92 没库存了,跳过。两张表都不动,继续看下一个。
- 10扫到第 2 个 3。它还有剩(cnt[3]=1)。先查 need[3]=0:有没有序列正等着接 3?
- 11没人等 3,那就用 3、4、5 现起一条长度 3 的新序列(绿色)。把 4、5 各扣 1 表示被领走,这条新序列结尾是 5,改成等 6:need[6] 加 1。
- 12扫到第 3 个 3,但 cnt[3] 已经是 0,说明这个 3 早就被前面某条序列取走了,跳过它。
- 133 没库存了,跳过。两张表都不动,继续看下一个。
- 14扫到第 4 个 4。它还有剩(cnt[4]=1)。先查 need[4]=1:有没有序列正等着接 4?
- 15有序列在等 4!把 4 接上去(绿色)。这条序列结尾从 3 变成 4,于是它改成等 5:need[4] 减 1、need[5] 加 1。优先接尾,不浪费。
- 16扫到第 5 个 4,但 cnt[4] 已经是 0,说明这个 4 早就被前面某条序列取走了,跳过它。
- 174 没库存了,跳过。两张表都不动,继续看下一个。
- 18扫到第 6 个 5。它还有剩(cnt[5]=1)。先查 need[5]=1:有没有序列正等着接 5?
- 19有序列在等 5!把 5 接上去(绿色)。这条序列结尾从 4 变成 5,于是它改成等 6:need[5] 减 1、need[6] 加 1。优先接尾,不浪费。
- 20扫到第 7 个 5,但 cnt[5] 已经是 0,说明这个 5 早就被前面某条序列取走了,跳过它。
- 215 没库存了,跳过。两张表都不动,继续看下一个。
- 22扫完回放一下分配结果。绿色这 5 个是第一条 [1,2,3,4,5],剩下灰色的 3、4、5 正好是第二条 [3,4,5],两条都长度 ≥ 3。
- 23全部扫完,一路都没卡住。8 个数正好拆成两条:[1,2,3,4,5] 和 [3,4,5],两条都达标,返回 true。need 里残留的几个 6 只是「曾经等过」的记号,没有未完成的短序列。
⚠️ 容易写错的地方
✗ 错:把「子序列」当成「连续子数组」
✓ 对:可以挑着取,只要段内逐 1 递增
[1,2,3,3,4,5] 要拆成两条交错的序列,连续子数组做不到
✗ 错:只顾起新三连、不优先接尾
✓ 对:need[x] > 0 时必须先接尾
接尾能把已有短序列养长,盲目起新三连会留下一堆长度不足的残段
✗ 错:忘了消耗 cnt 导致一个数被重复使用
✓ 对:用掉一个就 cnt 减 1
不扣会让同一个 x 被多条序列同时取走,结果偏乐观
完整代码(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 isPossible(self, nums: List[int]) -> bool:
d = defaultdict(list)
for v in nums:
if h := d[v - 1]:
heappush(d[v], heappop(h) + 1)
else:
heappush(d[v], 1)
return all(not v or v and v[0] > 2 for v in d.values())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:
bool isPossible(vector<int>& nums) {
unordered_map<int, priority_queue<int, vector<int>, greater<int>>> d;
for (int v : nums) {
if (d.count(v - 1)) {
auto& q = d[v - 1];
d[v].push(q.top() + 1);
q.pop();
if (q.empty()) {
d.erase(v - 1);
}
} else {
d[v].push(1);
}
}
for (auto& [_, v] : d) {
if (v.top() < 3) {
return false;
}
}
return true;
}
};Java
import java.util.*;
class Solution {
public boolean isPossible(int[] nums) {
Map<Integer, PriorityQueue<Integer>> d = new HashMap<>();
for (int v : nums) {
if (d.containsKey(v - 1)) {
PriorityQueue<Integer> q = d.get(v - 1);
d.computeIfAbsent(v, k -> new PriorityQueue<>()).offer(q.poll() + 1);
if (q.isEmpty()) {
d.remove(v - 1);
}
} else {
d.computeIfAbsent(v, k -> new PriorityQueue<>()).offer(1);
}
}
for (PriorityQueue<Integer> v : d.values()) {
if (v.peek() < 3) {
return false;
}
}
return true;
}
}复杂度
时间
O(n log n)
参考代码每个结尾值挂最小堆,每次接尾做一次堆操作带 log;动画演示的 cnt/need 哈希贪心版均摊 O(1),可做到 O(n)
空间
O(n)
每个结尾值一个堆(或两张哈希表)最多记 O(n) 个元素
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 分割数组为连续子序列 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么「优先接尾」一定是对的,不会错过更优解?+
关键在于:当一条以 x-1 结尾的短序列还没到长度 3,又来了一个 x,如果不接它、转而拿 x 去起新三连,那条短序列就永远补不齐而失败。接尾既不会让任何序列变差,又能救活短序列,所以贪心地优先接尾是安全的最优选择。
这题和「最长连续序列(LC128)」有何不同?+
LC128 是在无序集合里找最长的一条连续段,用哈希集合从每段起点扩展即可;本题数组已排序,要求把全部元素分配进若干条「长度 ≥ 3 的连续递增子序列」,重点是分配与可行性判定,用 cnt/need 贪心或每个结尾值挂最小堆来解。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 分割数组为连续子序列 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。