1024. 视频剪辑

1024. 视频剪辑

1024. 视频剪辑

你将会获得一系列视频片段,这些片段来自于一项持续时长为 T 秒的体育赛事。这些片段可能有所重叠,也可能长度不一。

视频片段 clips[i] 都用区间进行表示:开始于 clips[i][0] 并于 clips[i][1] 结束。我们甚至可以对这些片段自由地再剪辑,例如片段 [0, 7] 可以剪切成 [0, 1] + [1, 3] + [3, 7] 三部分。

我们需要将这些片段进行再剪辑,并将剪辑后的内容拼接成覆盖整个运动过程的片段([0, T])。返回所需片段的最小数目,如果无法完成该任务,则返回 -1 。

示例 1:

输入:clips = [[0,2],[4,6],[8,10],[1,9],[1,5],[5,9]], T = 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]], T = 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]], T = 9
输出:3
解释: 
我们选取片段 [0,4], [4,7] 和 [6,9] 。
示例 4:

输入:clips = [[0,4],[2,8]], T = 5
输出:2
解释:
注意,你可能录制超过比赛结束时间的视频。
 

提示:

1 <= clips.length <= 100
0 <= clips[i][0] <= clips[i][1] <= 100
0 <= T <= 100

分析

  • 首先想到一个有疏漏贪心的策略

    先排序,所有片段按照开始时间升序,若开始时间相同则按结束时间降序排列
    再遍历,用一个变量 last 记录已选区间所能达到的右边界:
      若当前视频段开始时间比 last 还大,可以确定无解;
      若当前视频段结束时间不大于 last,则当前视频段不入选,继续遍历后边的
      否则,把当前视频段加入结果
      最后返回结果,注意如果 last 最终小于 T,则无解,返回-1
    func videoStitching(clips [][]int, T int) int {
      n := len(clips)
      if n == 0 {return -1}
      sort.Slice(clips, func(i, j int) bool {
          if clips[i][0] == clips[j][0] {
              return clips[i][1] > clips[j][1]
          }
          return clips[i][0] < clips[j][0]
      })
      res := 0
      last := 0
      for _, v := range clips {
          if last < v[0] {
              return -1
          }
          if last >= v[1] {
              continue
          }
          res++
          last = v[1]
      }
      if last < T {
          return -1
      }
      return res
    }

    不过还有疏漏,比如这样的用例:

    [0 4],[2 6],[4 7],[6 9] 9

    显然,[2,6]或[4,7]只能选一个,但上边的策略把两个都选了;这个问题还没想到修改都办法。

  • 另一个贪心策略

    可以先把 clips 降为长度为 T 的一维数组,索引代表开始时间,值代表结束时间。
    在有相同开始时间的视频片段时,只保留结束时间大的片段。
    
    接下来遍历这个降维后的数组,用 maxEnd 维护能到达的最大右边界,pre 维护上一次选择的区间的右边界
      首先,如果当前视频片段结束时间大于 maxEnd,更新 maxEnd
      如果当前视频片段开始时间 == maxEnd,则无解,直接返回 -1
      如果当前视频片段开始时间 == pre, 则当前片段应该加入结果,pre 更新为 maxEnd
    func videoStitching(clips [][]int, T int) int {
        if T < 1 {
            return 0
        }
        ends := make([]int, T)
        for _, v := range clips {
            if v[0] < T && ends[v[0]] < v[1] {
                ends[v[0]] = v[1]
            }
        }
        maxEnd, pre, res := 0, 0, 0
        for start, end := range ends {
            if maxEnd < end {
                maxEnd = end
            }
            if start == maxEnd {
                return -1
            }
            if start == pre {
                res++
                pre = maxEnd
            }
        }
        return res
    }
  • 动态规划

    dp[i] 表示将区间 [0,i) 覆盖所需的最少子区间的数量

    func videoStitching(clips [][]int, t int) int {
      const inf = 200
      dp := make([]int, t+1)
      for i := range dp {
          dp[i] = inf
      }
      dp[0] = 0
      for i := 1; i <= t; i++ {
          for _, c := range clips {
              l, r := c[0], c[1]
              // 若能剪出子区间 [l,i],则可以从 dp[l] 转移到 dp[i]
              if l < i && i <= r && dp[l]+1 < dp[i] {
                  dp[i] = dp[l] + 1
              }
          }
      }
      if dp[t] == inf {
          return -1
      }
      return dp[t]
    }