Medium
题目描述
给你一个按非递减顺序排列的整数数组 nums。
请你判断是否能够将 nums 分割成一个或多个子序列,且同时满足下述 两个 条件:
- 每个子序列都是一个连续递增序列(即,每个整数 恰好 比前一个整数大 1)。
- 所有子序列的长度至少为 3。
如果可以分割 nums 并满足上述条件,则返回 true;否则,返回 false。
数组的子序列是由原数组删除一些元素(也可以不删除)得到的一个新数组,且不改变其余元素的相对顺序。(例如,[1,3,5] 是 [1,2,3,4,5] 的一个子序列,而 [1,3,2] 不是)。
示例 1:
输入:nums = [1,2,3,3,4,5]
输出:true
解释:nums 可以分割成以下子序列:
[1,2,3,3,4,5] --> 1, 2, 3
[1,2,3,3,4,5] --> 3, 4, 5
示例 2:
输入:nums = [1,2,3,3,4,4,5,5]
输出:true
解释:nums 可以分割成以下子序列:
[1,2,3,3,4,4,5,5] --> 1, 2, 3, 4, 5
[1,2,3,3,4,4,5,5] --> 3, 4, 5
示例 3:
输入:nums = [1,2,3,4,4,5]
输出:false
解释:无法将 nums 分割成长度至少为 3 的连续递增子序列。
提示:
1 <= nums.length <= 10^4-1000 <= nums[i] <= 1000nums按非递减顺序排列
解题思路
这道题需要判断数组能否分割成多个长度至少为3的连续递增子序列。
贪心策略分析:
核心思想是贪心地构造子序列。对于当前数字,我们有两种选择:
- 将其接在已有的子序列末尾
- 开始一个新的子序列
贪心策略:优先选择第一种方案,因为这样能充分利用已有的子序列,避免产生过短的子序列。
算法步骤:
使用两个哈希表:
count:记录每个数字的剩余个数tail:记录以每个数字结尾的子序列个数
遍历数组中的每个数字:
- 如果该数字已用完,跳过
- 如果存在以
num-1结尾的子序列,将当前数字接上去(贪心选择) - 否则,尝试开始新子序列
[num, num+1, num+2] - 如果无法形成新子序列,返回false
这种贪心策略保证了每次都做出局部最优选择,最终得到全局最优解。
时间复杂度O(n),空间复杂度O(n),其中n为数组长度。
代码实现
class Solution {
public:
bool isPossible(vector<int>& nums) {
unordered_map<int, int> count, tail;
// 统计每个数字的个数
for (int num : nums) {
count[num]++;
}
for (int num : nums) {
if (count[num] == 0) continue;
// 优先接在已有子序列后面
if (tail[num - 1] > 0) {
tail[num - 1]--;
tail[num]++;
count[num]--;
}
// 尝试开始新的子序列
else if (count[num + 1] > 0 && count[num + 2] > 0) {
count[num]--;
count[num + 1]--;
count[num + 2]--;
tail[num + 2]++;
}
// 无法形成有效子序列
else {
return false;
}
}
return true;
}
};
class Solution:
def isPossible(self, nums: List[int]) -> bool:
from collections import defaultdict
count = defaultdict(int)
tail = defaultdict(int)
# 统计每个数字的个数
for num in nums:
count[num] += 1
for num in nums:
if count[num] == 0:
continue
# 优先接在已有子序列后面
if tail[num - 1] > 0:
tail[num - 1] -= 1
tail[num] += 1
count[num] -= 1
# 尝试开始新的子序列
elif count[num + 1] > 0 and count[num + 2] > 0:
count[num] -= 1
count[num + 1] -= 1
count[num + 2] -= 1
tail[num + 2] += 1
# 无法形成有效子序列
else:
return False
return True
public class Solution {
public bool IsPossible(int[] nums) {
var count = new Dictionary<int, int>();
var tail = new Dictionary<int, int>();
// 统计每个数字的个数
foreach (int num in nums) {
count[num] = count.GetValueOrDefault(num, 0) + 1;
}
foreach (int num in nums) {
if (count.GetValueOrDefault(num, 0) == 0) continue;
// 优先接在已有子序列后面
if (tail.GetValueOrDefault(num - 1, 0) > 0) {
tail[num - 1]--;
tail[num] = tail.GetValueOrDefault(num, 0) + 1;
count[num]--;
}
// 尝试开始新的子序列
else if (count.GetValueOrDefault(num + 1, 0) > 0 &&
count.GetValueOrDefault(num + 2, 0) > 0) {
count[num]--;
count[num + 1]--;
count[num + 2]--;
tail[num + 2] = tail.GetValueOrDefault(num + 2, 0) + 1;
}
// 无法形成有效子序列
else {
return false;
}
}
return true;
}
}
var isPossible = function(nums) {
const count = new Map();
const need = new Map();
for (const num of nums) {
count.set(num, (count.get(num) || 0) + 1);
}
for (const num of nums) {
if (count.get(num) === 0) continue;
if (need.get(num) > 0) {
need.set(num, need.get(num) - 1);
need.set(num + 1, (need.get(num + 1) || 0) + 1);
} else if (count.get(num + 1) > 0 && count.get(num + 2) > 0) {
count.set(num + 1, count.get(num + 1) - 1);
count.set(num + 2, count.get(num + 2) - 1);
need.set(num + 3, (need.get(num + 3) || 0) + 1);
} else {
return false;
}
count.set(num, count.get(num) - 1);
}
return true;
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 需要遍历数组两次,每次操作为常数时间 |
| 空间复杂度 | O(n) | 需要两个哈希表存储数字的计数和尾部信息 |
相关题目
. Top K Frequent Elements (Medium)