你将会获得一系列视频片段,这些片段来自于一项持续时长为 time
秒的体育赛事。这些片段可能有所重叠,也可能长度不一。
使用数组 clips
描述所有的视频片段,其中 clips[i] = [starti, endi]
表示:某个视频片段开始于 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 <= 100
0 <= starti <= endi <= 100
1 <= time <= 100
和跳跃游戏很相似,都是可以通过贪心/动态规划解决的区间覆盖问题。
有兴趣的可以查看两篇跳跃游戏的详解:
每日一题:跳跃游戏-CSDN博客
每日一题:跳跃游戏II-CSDN博客
针对本题的贪心算法:对输入区间按照左端点排序,而后遍历,在过程中维护当前最远覆盖距离以及下一次最远覆盖距离。(下一次最远覆盖距离指当前覆盖距离内的所有起始点能达到的最远距离的最大值)
需要首先对输入的区间作处理,获得在每个时间点的视频到达的最远时间:
for(auto& clip :clips){
if(clip[0] < time){
nums[clip[0]] = max(nums[clip[0]],clip[1]);
}
}
处理过后问题就基本完全变成了跳跃游戏II。
class Solution {
public:
int videoStitching(vector<vector<int>>& clips, int time) {
vector<int> nums(time);
for(auto& clip :clips){
if(clip[0] < time){
nums[clip[0]] = max(nums[clip[0]],clip[1]);
}
}
int end = 0;
int result = 0;
int farest = 0;
for (int i = 0; i < time; i++) {
if(i > end){
return -1;
}
farest = max(farest, nums[i]);
if (i == end) {
result++;
end = farest;
if (end >= nums.size()) {
return result;
}
}
}
return -1;
}
};
唯一不同就是题目不保证一定能到达time,也就是一定能跳到终点,所以需要加入当 i 已经大于当前能到达的最远点,就返回-1无法到达。
附跳跃游戏的详解:
- result是跳跃的次数,end是当前的终点,farthest是当前点跳跃能够到达的最远点。
- 遍历数组,除了最后一个元素,因为最后一个元素的位置不需要跳跃,自己就能到达自己。
- 我们时刻维护从当前点到达的最远距离,当我们到达了当前终点,就把最远距离设置成终点,这里体现贪心的思想。
- 同时,当到达了end时,也说明需要进行一次跳跃。
即:每次在上次能跳到的范围(i,end)内选择一个能跳的最远的位置(也就是能跳到farthest位置的点)作为下次的起跳点。
对于初学者,这看上去非常的反直觉,这是不是局部最优?为什么是全局最优?如果出现当前跳的最远,但是下下步跳得近了怎么办?
这里需要理解end的作用,如果把end抽象成一个分隔符,所谓跳跃过程就是在数组内插入分隔符的过程,使最终分出的子数组数量最小。
而fareset的作用是,保留上一个end到当前end这个区间范围内可以达到的最远值。
注意区间范围这个点。
在贪心算法中,每一步的end都是当前范围能到达的最远点,也即最大值farest,所以最终分出的间隔就会更少。
下面用一个具体图例做进一步解释,初始状态,进行第一次跳跃:
跳跃后在区间内遍历维护最远值farest:
这里有人可能会说,看起来像恰好1就跳到了较大值10。那如果我们把这里的1换成0会发生什么?
可以看到维护的farest,才是起到关键作用的值。和nums[end]中的值并无全部关系。 这也是上面提到的,保留上一个end到当前end这个区间范围内可以达到的最远值。
图中箭头描述的是end变化的过程,真实的跳跃过程和end的变化过程数量相同,但是路径不一定相同。(每条end箭头仅对应一条跳跃,比如这里是从2跳到3跳到10。)
继续遍历:
只要理解了end表示间隔且和真实跳跃一一对应,farest表示一个区间内跳到的最远距离这两个概念,这里的贪心算法就很好理解了。