Medium
题目描述
你将会获得一系列视频片段,这些片段来自于一项总时长为 time 秒的体育赛事。这些视频片段可能有重叠,也可能长度不一。
每个视频片段都用一个数组 clips 表示,其中 clips[i] = [starti, endi] 表示第 i 个片段开始于 starti 并结束于 endi。
我们甚至可以对这些片段进行自由的重新剪辑:
- 例如,片段
[0, 7]可以剪切成[0, 1]+[1, 3]+[3, 7]三部分。
我们需要将这些片段进行再剪辑,并将剪辑后的内容拼接成覆盖整个运动过程的片段 [0, time]。返回所需片段的最小数目,如果无法完成该任务,则返回 -1。
示例 1:
输入:clips = [[0,2],[4,6],[8,10],[1,9],[1,5],[5,9]], time = 10
输出:3
解释:
我们选中 [0,2], [8,10], [1,9] 这三个片段。
然后,按下述方式重新剪辑:
将 [1,9] 剪切为 [1,2] + [2,8] + [8,9]。
现在手上的片段为 [0,2] + [2,8] + [8,10],而这些覆盖了整个比赛 [0, 10]。
示例 2:
输入:clips = [[0,1],[1,2]], time = 5
输出:-1
解释:
我们无法只用 [0,1] 和 [1,2] 覆盖 [0,5] 的整个过程。
示例 3:
输入:clips = [[0,1],[6,8],[0,2],[5,6],[0,4],[0,3],[6,7],[1,3],[4,7],[1,4],[2,5],[2,6],[3,4],[4,5],[5,7],[6,9]], time = 9
输出:3
解释:
我们可以选择 [0,4], [4,7] 和 [6,9] 这三个片段。
提示:
1 <= clips.length <= 1000 <= starti <= endi <= 1001 <= time <= 100
解题思路
这是一个经典的区间覆盖问题,可以用贪心算法或动态规划来解决。
贪心算法(推荐)
贪心策略的核心思想是:在每一步都选择能够覆盖当前位置且右端点最远的区间。
具体步骤:
- 将所有片段按起始时间排序
- 使用三个变量:
cur_end(当前覆盖的右端点)、next_end(下一步能到达的最远右端点)、count(使用的片段数) - 遍历排序后的片段:
- 如果当前片段的起始时间超过了
next_end,说明无法连续覆盖,返回-1 - 如果当前片段能延伸覆盖范围,更新
next_end - 当遍历完所有能从当前位置开始的片段后,选择其中右端点最远的片段
- 如果当前片段的起始时间超过了
动态规划
定义dp[i]为覆盖[0,i]所需的最少片段数。对于每个位置i,考虑所有能覆盖位置i的片段,从中选择使用片段数最少的方案。
贪心算法的时间复杂度更优,是首选解法。
代码实现
class Solution {
public:
int videoStitching(vector<vector<int>>& clips, int time) {
sort(clips.begin(), clips.end());
int count = 0, cur_end = 0, next_end = 0, i = 0;
while (cur_end < time) {
while (i < clips.size() && clips[i][0] <= cur_end) {
next_end = max(next_end, clips[i][1]);
i++;
}
if (next_end == cur_end) return -1;
cur_end = next_end;
count++;
}
return count;
}
};
class Solution:
def videoStitching(self, clips: List[List[int]], time: int) -> int:
clips.sort()
count = 0
cur_end = 0
next_end = 0
i = 0
while cur_end < time:
while i < len(clips) and clips[i][0] <= cur_end:
next_end = max(next_end, clips[i][1])
i += 1
if next_end == cur_end:
return -1
cur_end = next_end
count += 1
return count
public class Solution {
public int VideoStitching(int[][] clips, int time) {
Array.Sort(clips, (a, b) => a[0].CompareTo(b[0]));
int count = 0, curEnd = 0, nextEnd = 0, i = 0;
while (curEnd < time) {
while (i < clips.Length && clips[i][0] <= curEnd) {
nextEnd = Math.Max(nextEnd, clips[i][1]);
i++;
}
if (nextEnd == curEnd) return -1;
curEnd = nextEnd;
count++;
}
return count;
}
}
/**
* @param {number[][]} clips
* @param {number} time
* @return {number}
*/
var videoStitching = function(clips, time) {
clips.sort((a, b) => a[0] - b[0]);
let count = 0;
let currentEnd = 0;
let farthest = 0;
let i = 0;
while (currentEnd < time) {
while (i < clips.length && clips[i][0] <= currentEnd) {
farthest = Math.max(farthest, clips[i][1]);
i++;
}
if (farthest <= currentEnd) {
return -1;
}
currentEnd = farthest;
count++;
}
return count;
};
复杂度分析
| 算法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 贪心算法 | O(n log n) | O(1) |
| 动态规划 | O(n × time) | O(time) |
说明:
- 贪心算法的时间复杂度主要来自排序操作,遍历只需O(n)
- 贪心算法只使用了常数额外空间
- 动态规划需要O(time)的数组空间,时间复杂度较高