题目描述
给定一个长度为 n 的二维数组 intervals,其中 intervals[i] = [l,r] 表示一个区间 [l,r)(左闭右开),求最多能选择多少个互不重叠的区间能够完全覆盖区间 [1,n),如果不能完全覆盖区间 [1,n) 返回 -1。
示例
示例 1:
输入:intervals = [[2,3],[1,3],[3,4],[1,2]]
输出:3
解释:[1,2) + [2,3) + [3,4) = [1,4)
约束条件
- 2 <= intervals.length <= 10^5
- 1 <= intervals[i][0] < intervals[i][1] <= intervals.length
算法思路
这是一个经典的区间覆盖问题,可以使用动态规划结合贪心策略来解决。
核心思想
- 排序策略:按照区间的右端点进行排序,这样可以保证我们总是优先考虑结束早的区间
- 动态规划状态:
dp[i]表示能够覆盖到位置 i 的最少区间数 - 状态转移:对于每个区间 [l,r],如果能够到达位置 l,则可以用这个区间延伸到位置 r
详细分析
为什么按右端点排序?
- 贪心策略:在能够覆盖相同范围的情况下,选择结束早的区间总是更优的
- 这样可以为后续的区间选择留出更多空间
动态规划状态设计
dp[i]= 覆盖到位置 i 的最少区间数- 初始化:
dp[1] = 0(起始位置不需要区间覆盖) - 目标:求
dp[n]
状态转移方程
对于区间 [l,r]:
- 如果
l != 1且dp[l] == 0,说明无法到达位置 l,跳过此区间 - 否则:
dp[r] = max(dp[r], dp[l] + 1)
算法步骤
- 按右端点对所有区间进行排序
- 初始化 dp 数组,dp[1] = 0
- 遍历排序后的区间:
- 检查是否能到达区间的左端点
- 如果能到达,更新右端点的 dp 值
- 返回 dp[n](如果为 0 则返回 -1)
算法执行过程演示
以示例 intervals = [[2,3],[1,3],[3,4],[1,2]] 为例:
步骤1:排序
按右端点排序后:[[1,2], [2,3], [1,3], [3,4]]
步骤2:初始化
dp = [0, 0, 0, 0, 0](索引0不使用)n = 4
步骤3:遍历区间
-
区间[1,2]:
- left=1, right=2
- left==1,可以处理
- dp[2] = max(dp[2], dp[1] + 1) = max(0, 0 + 1) = 1
- dp = [0, 0, 1, 0, 0]
-
区间[2,3]:
- left=2, right=3
- dp[2] = 1 ≠ 0,可以到达
- dp[3] = max(dp[3], dp[2] + 1) = max(0, 1 + 1) = 2
- dp = [0, 0, 1, 2, 0]
-
区间[1,3]:
- left=1, right=3
- left==1,可以处理
- dp[3] = max(dp[3], dp[1] + 1) = max(2, 0 + 1) = 2
- dp = [0, 0, 1, 2, 0]
-
区间[3,4]:
- left=3, right=4
- dp[3] = 2 ≠ 0,可以到达
- dp[4] = max(dp[4], dp[3] + 1) = max(0, 2 + 1) = 3
- dp = [0, 0, 1, 2, 3]
步骤4:返回结果
dp[4] = 3,返回 3
代码实现
Java 实现
import java.util.Arrays;
/**
* 6.6. 区间覆盖问题
* 动态规划
*/
class Solution {
/**
* 解决区间覆盖问题
* 给定一系列区间,计算覆盖从1到n的最少区间数如果无法覆盖,返回-1
*
* @param intervals 二维数组,每个一维数组表示一个区间,包含两个整数,分别是区间的左端点和右端点
* @return 返回覆盖从1到n的最少区间数如果无法覆盖,返回-1
*/
public int solve(int[][] intervals) {
// 获取区间总数
int n = intervals.length;
// dp[i] 表示覆盖到位置 i 的最少区间数
int[] dp = new int[n + 1];
// 按右端点排序,贪心策略
Arrays.sort(intervals, (a, b) -> a[1] - b[1]);
// 遍历每个区间
for (int[] interval : intervals) {
// 获取当前区间的左端点和右端点
int left = interval[0];
int right = interval[1];
// 如果不是从位置1开始,且无法到达左端点,跳过
if (left != 1 && dp[left] == 0) {
continue;
}
// 状态转移:用当前区间延伸覆盖范围
dp[right] = Math.max(dp[right], dp[left] + 1);
}
// 如果无法覆盖到位置n,返回-1
return dp[n] == 0 ? -1 : dp[n];
}
}
C# 实现
/**
* 6.6. 区间覆盖问题
* 动态规划
*/
public class Solution
{
/// <summary>
/// 解决区间覆盖问题
/// </summary>
/// <param name="intervals">二维数组,每个一维数组表示一个区间,包含两个整数,分别是区间的左端点和右端点</param>
/// <returns>返回覆盖到最远位置所需的最少区间数,如果无法覆盖到最远位置,则返回-1</returns>
public int Solve(int[][] intervals)
{
// n 表示区间的数量
int n = intervals.Length;
// dp[i] 表示覆盖到位置 i 的最少区间数
int[] dp = new int[n + 1];
// 按右端点排序,贪心策略
Array.Sort(intervals, (a, b) => a[1].CompareTo(b[1]));
foreach (int[] interval in intervals)
{
int left = interval[0];
int right = interval[1];
// 如果不是从位置1开始,且无法到达左端点,跳过
if (left != 1 && dp[left] == 0)
{
continue;
}
// 更新覆盖到右端点的最少区间数
dp[right] = Math.Max(dp[right], dp[left] + 1);
}
// 如果最后一个位置的最少区间数为0,则表示无法覆盖到最远位置,返回-1;否则返回最少区间数
return dp[n] == 0 ? -1 : dp[n];
}
}
复杂度分析
-
时间复杂度:O(n log n)
- 排序:O(n log n)
- 遍历区间:O(n)
- 总体:O(n log n)
-
空间复杂度:O(n)
- dp 数组:O(n)
- 排序额外空间:O(log n)
- 总体:O(n)
777

被折叠的 条评论
为什么被折叠?



