6.6. 区间覆盖问题

题目描述

给定一个长度为 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

算法思路

这是一个经典的区间覆盖问题,可以使用动态规划结合贪心策略来解决。

核心思想

  1. 排序策略:按照区间的右端点进行排序,这样可以保证我们总是优先考虑结束早的区间
  2. 动态规划状态dp[i] 表示能够覆盖到位置 i 的最少区间数
  3. 状态转移:对于每个区间 [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)

算法步骤

  1. 按右端点对所有区间进行排序
  2. 初始化 dp 数组,dp[1] = 0
  3. 遍历排序后的区间:
    • 检查是否能到达区间的左端点
    • 如果能到达,更新右端点的 dp 值
  4. 返回 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. 区间[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. 区间[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]
  3. 区间[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]
  4. 区间[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)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值