第一次尝试
先O(N)遍历一次检查数组元素是不是连续的,不连续返回false
维护一个最小堆和一个res序列,里面的元素是(value,cnt),根据value来排序,我们用last来标记当前序列选择的最后一个元素,用x,cnt表示弹出的堆顶元素
如果当前堆顶元素能够和last构成连续,那么执行如下操作
cnt-=1,更新last为x,然后放入res序列里面,等待下一轮遍历
如果构不成连续,返回False
如果res不为空,重新构造heap执行上面的步骤,直到res为空
暴 力 大 失 败
题解
受不了了直接看题解,贪心一点都不会做
哈希表+最小堆
一个直观的想法就是如果我们知道一个连续序列里面的最后一个元素和它的长度,就能推出这个序列,那么我们对于题目要确定的连续序列,只需要保存last element和length这两个信息就行了
然后我们对于当前的数x,判断它是否能加入序列,一共有两种情况
- 存在一个以x-1结尾的序列,此时x能够加入这个序列,将其length-1,last element更新为x
- 不存在以x-1结尾的序列,此时需要新建一个length为1,last element为x的序列
对于情况1,可能存在多个以x-1为结尾的序列,那么我们应该选择length最短的序列,因为题目要求分割的子序列长度大于等于3,所以我们应该分配元素给最短的序列
当遍历完所有元素,那么我们可以检查所有序列,假如序列长度都大于等于3,那么返回True,否则返回False
现在的问题是如何有效的存储last element和length的信息
一个巧妙的做法是为序列维护一个最小堆,也就是我们使用哈希表来存储信息,这些信息的key是last element,value是最小堆,最小堆里面存储着length,由于一个last element可能有多个序列,例如[1,2,3,3]
其中的3对应着两个length,一个是[1,2,3]
的3,另一个是[3]
的1,所以我们需要用小根堆来维护这些length,保证当存在x = last element + 1
的时候,能够last element中最短的序列用来叠加
当我们遇到一个x = last element + 1
的时候,就可以从last element中取出一个最短序列(也就是小根堆的堆顶)用来更新x对应的长度,所以这是不断pop和push的过程,下面是python代码
class Solution:
def isPossible(self, nums: List[int]) -> bool:
# 如果key值不存在,创建并且value是一个list
mp = collections.defaultdict(list)
for x in nums:
queue = mp.get(x-1)
if queue:
length = heapq.heappop(queue)
heapq.heappush(mp[x],length+1)
else:
heapq.heappush(mp[x],1)
return not any(queue and queue[0]<3 for queue in mp.values())
关键是这个dict+小根堆的组合就挺难想到的。。。然后中间的思想关键的地方就是选取length最小的序列然后+1
c++版
class Solution {
public:
bool isPossible(vector<int>& nums) {
unordered_map<int, priority_queue<int, vector<int>,greater<int>>> mp;
for (auto& x : nums) {
if (mp.find(x) == mp.end()) {
mp[x] = priority_queue<int, vector<int>,greater<int>>();
}
if (mp.find(x - 1) != mp.end()) {
int prevLength = mp[x - 1].top();
mp[x - 1].pop();
// 以num-1结尾的序列为空,及时移除num-1,避免被重复检索,导致上面的pop出错
if (mp[x - 1].empty()) {
mp.erase(x - 1);
}
mp[x].push(prevLength + 1);
} else {
mp[x].push(1);
}
}
for (auto it = mp.begin(); it != mp.end(); ++it) {
priority_queue<int, vector<int>,greater<int>> queue = it->second;
if (queue.top() < 3) {
return false;
}
}
return true;
}
};
java版
class Solution {
public boolean isPossible(int[] nums) {
Map<Integer,PriorityQueue<Integer>> map = new HashMap<Integer,PriorityQueue<Integer>>();
for(int x:nums){
if (!map.containsKey(x)){
map.put(x,new PriorityQueue<Integer>());
}
if(map.containsKey(x-1)){
int length = map.get(x-1).poll();
if(map.get(x-1).isEmpty()){
map.remove(x-1);
}
map.get(x).offer(length+1);
}else{
map.get(x).offer(1);
}
}
Set<Map.Entry<Integer,PriorityQueue<Integer>>> entrySet = map.entrySet();
for(Map.Entry<Integer,PriorityQueue<Integer>> entry:entrySet){
PriorityQueue<Integer> queue = entry.getValue();
if(queue.peek()<3)
return false;
}
return true;
}
}
时间复杂度O(nlogn)
贪心
官方题解说得很清楚,这里的思路是方法一的进阶
基于分析,把一个元素加入已存在的子序列比新建一个长度为1的子序列更优。对于当前元素x,如果我们存在x-1的序列,则直接加入x-1的序列,如果不存在,那么我们根据题目要求(序列长度至少为3),在数组中找到x+1,x+2,然后构造[x,x+1,x+2]的子序列,并且新建这个以x+2为结尾的序列,相当于我们一口气创建长度为3的子序列,比方法一新建一个长度为1的子序列更优。假如我们找不到[x,x+1,x+2]这三个序列,那么直接返回false即可
我们使用两张哈希表来完成操作,第一张哈希表记录了每个元素的剩余次数,第二张hash表记录以x为结尾的元素个数
由此我们可以写出Python代码
注意用get来赋予dict默认值
class Solution:
def isPossible(self, nums: List[int]) -> bool:
# 如果key值不存在,创建并且value是一个list
dct = Counter(nums)
seq = Counter()
for k in nums:
v = dct[k]
if v>0:
if seq.get(k-1,0)>0:
dct[k]-=1
seq[k-1] -=1
seq[k] +=1
else:
x2,x3 = dct.get(k+1,0),dct.get(k+2,0)
if not x2 or not x3:
return False
dct[k]-=1
dct[k+1]-=1
dct[k+2]-=1
seq[k+2] +=1
return True
时间复杂度O(n)