【LeetCode】区间(合并、插入、重叠区间、最小区间······)

落日映苍穹つ 2023-01-06 15:52 271阅读 0赞

文章目录

      • 汇总区间★
      • 无重叠区间★★
      • 合并区间★★
      • 插入区间★★★
      • 最小区间★★★
      • 区间和的个数★★★

汇总区间★

LeetCode228. 汇总区间

题目】给定一个无重复元素的有序整数数组 nums 。

返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums 的数字 x 。

列表中的每个区间范围 [a,b] 应该按如下格式输出:

  • “a->b” ,如果 a != b
  • “a” ,如果 a == b

示例

  1. 输入:nums = [0,2,3,4,6,8,9]
  2. 输出:["0","2->4","6","8->9"]
  3. 解释:区间范围是:
  4. [0,0] --> "0"
  5. [2,4] --> "2->4"
  6. [6,6] --> "6"
  7. [8,9] --> "8->9"

解题思路

  • 无连续的数字单独作为区间
  • 有连续数字取区间两端

    class Solution {

    1. public List<String> summaryRanges(int[] nums) {
    2. List<String> list = new ArrayList<>();
    3. for(int i = 0; i < nums.length; i++) {
    4. StringBuffer sb = new StringBuffer();
    5. sb.append(nums[i]);
    6. if(i + 1 < nums.length && nums[i + 1] - nums[i] == 1) {
    7. int j = i + 1;
    8. while(j + 1 < nums.length && nums[j + 1] - nums[j] == 1) {
    9. j++;
    10. }
    11. i = j;
    12. sb.append("->");
    13. sb.append(nums[j]);
    14. }
    15. list.add(sb.toString());
    16. }
    17. return list;
    18. }

    }


无重叠区间★★

LeetCode435. 无重叠区间

题目】给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。

注意:

  • 可以认为区间的终点总是大于它的起点。
  • 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。

示例

  1. 输入: [ [1,2], [2,3], [3,4], [1,3] ]
  2. 输出: 1
  3. 解释: 移除 [1,3] 后,剩下的区间没有重叠。

解题思路】贪心法

方法一:按左端点排序

  1. class Solution {
  2. public int eraseOverlapIntervals(int[][] intervals) {
  3. if(intervals == null || intervals.length == 0) return 0;
  4. Arrays.sort(intervals, (o1, o2) -> (o1[0] - o2[0]));
  5. int count = 0; //记录移除区间个数
  6. int end = intervals[0][1]; //记录区间右端
  7. for(int i = 1; i < intervals.length; i++) {
  8. if(intervals[i][0] < end) {
  9. count++;
  10. //取右端比较小的,这样才能容纳更多的区间
  11. end = Math.min(end, intervals[i][1]);
  12. }else {
  13. end = intervals[i][1];
  14. }
  15. }
  16. return count;
  17. }
  18. }

方法二:按右端点排序

  1. class Solution {
  2. public int eraseOverlapIntervals(int[][] intervals) {
  3. if(intervals == null || intervals.length == 0) return 0;
  4. Arrays.sort(intervals, (o1, o2) -> (o1[1] - o2[1]));
  5. int count = 0; //记录移除区间数
  6. int end = intervals[0][1]; //记录区间最右端
  7. for(int i = 1; i < intervals.length; i++) {
  8. if(intervals[i][0] < end) {
  9. count++;
  10. }else {
  11. end = intervals[i][1];
  12. }
  13. }
  14. return count;
  15. }
  16. }

合并区间★★

LeetCode 56. 合并区间

题目】给出一个区间的集合,请合并所有重叠的区间。

示例

  1. 输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
  2. 输出: [[1,6],[8,10],[15,18]]
  3. 解释: 区间 [1,3] [2,6] 重叠, 将它们合并为 [1,6]

解题思路

方法一:自定义排序

先按照左端点排序,之后顺序遍历,若左右元素相交,则更新区间大小,否则加入区间结果集

  1. class Solution {
  2. public int[][] merge(int[][] intervals) {
  3. if(intervals == null || intervals.length == 0) return new int[][]{
  4. {
  5. }};
  6. Arrays.sort(intervals, (o1, o2) -> o1[0] - o2[0]);
  7. List<int[]> list = new ArrayList<int[]>();
  8. int le = intervals[0][0], ri = intervals[0][1];
  9. for(int i = 1; i < intervals.length; i++) {
  10. if(intervals[i][0] > ri) {
  11. list.add(new int[]{
  12. le, ri});
  13. le = intervals[i][0];
  14. ri = intervals[i][1];
  15. }else {
  16. ri = Math.max(ri, intervals[i][1]);
  17. }
  18. }
  19. list.add(new int[]{
  20. le, ri});
  21. return list.toArray(new int[list.size()][2]);
  22. }
  23. }

方法二:位图

使用BitSet模拟坐标轴

  1. class Solution {
  2. public int[][] merge(int[][] intervals) {
  3. BitSet bitSet = new BitSet();
  4. int max = 0;
  5. for(int[] e : intervals) {
  6. int t = e[1] * 2 + 1; //乘以2是为了标记类似[a, a]的区间
  7. bitSet.set(e[0] * 2 , t, true);
  8. max = Math.max(max, t);
  9. }
  10. List<int[]> list = new ArrayList<int[]>();
  11. int index = 0;
  12. while(index < max) {
  13. int start = bitSet.nextSetBit(index);
  14. int end = bitSet.nextClearBit(start);
  15. list.add(new int[]{
  16. start / 2, (end - 1) / 2});
  17. index = end;
  18. }
  19. return list.toArray(new int[list.size()][]);
  20. }
  21. }

插入区间★★★

LeetCode 57. 插入区间

题目】给出一个*无重叠的 ,*按照区间起始端点排序的区间列表。

在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。

示例

  1. 输入:intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
  2. 输出:[[1,2],[3,10],[12,16]]
  3. 解释:这是因为新的区间 [4,8] [3,5],[6,7],[8,10] 重叠

解题思路

分三步走:

  • 小于插入区间左端点的不用合并
  • 合并区间时,左端点取最小值,右端点取最大值
  • 大于插入区间右端点的不用合并

    class Solution {

    1. public int[][] insert(int[][] intervals, int[] newInterval) {
    2. int t = 0;
    3. List<int[]> list = new ArrayList<int[]>();
    4. //小于newInterval[0](左端点)的不用合并
    5. while(t < intervals.length && intervals[t][1] < newInterval[0]){
    6. list.add(intervals[t]);
    7. ++t;
    8. }
    9. //合并区间 左端点取最小,右端点取最大
    10. while(t < intervals.length && intervals[t][0] <= newInterval[1]){
    11. newInterval[0] = Math.min(newInterval[0], intervals[t][0]);
    12. newInterval[1] = Math.max(newInterval[1], intervals[t][1]);
    13. ++t;
    14. }
    15. list.add(newInterval);
    16. //大于newInterval[1](右端点)的不用合并
    17. while(t < intervals.length){
    18. list.add(intervals[t]);
    19. ++t;
    20. }
    21. return list.toArray(new int[0][]);
    22. }

    }


最小区间★★★

LeetCode 632. 最小区间

题目】你有 k 个 非递减排列 的整数列表。找到一个 最小 区间,使得 k 个列表中的每个列表至少有一个数包含在其中。

我们定义如果 b-a < d-c 或者在 b-a == d-ca < c,则区间 [a,b][c,d] 小。

提示

  • nums.length == k
  • 1 <= k <= 3500
  • 1 <= nums[i].length <= 50
  • -105 <= nums[i][j] <= 105
  • nums[i] 按非递减顺序排列

示例

  1. 输入:nums = [[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]
  2. 输出:[20,24]
  3. 解释:
  4. 列表 1:[4, 10, 15, 24, 26],24 在区间 [20,24] 中。
  5. 列表 2:[0, 9, 12, 20],20 在区间 [20,24] 中。
  6. 列表 3:[5, 18, 22, 30],22 在区间 [20,24] 中。

解题思路

滑动窗口

将每个列表中的数字与其列表位置映射为二维数组,并按大小排序,

接着若滑动窗口中包含每个分组中的数,就可以更新结果了

  1. class Solution {
  2. public int[] smallestRange(List<List<Integer>> nums) {
  3. int N = 0;
  4. for(List<Integer> num : nums) N += num.size();
  5. //将列表映射为一个二维数组{值, 分组}
  6. int[][] help = new int[N][2];
  7. int k = 0;
  8. for(int i = 0; i < nums.size(); i++) {
  9. List<Integer> num = nums.get(i);
  10. for(int j = 0; j < num.size(); j++) {
  11. help[k][0] = num.get(j);
  12. help[k][1] = i;
  13. k++;
  14. }
  15. }
  16. Arrays.sort(help, (o1, o2) -> o1[0] - o2[0]);
  17. int[] res = {
  18. 0, 0};
  19. int[] group = new int[nums.size()];
  20. int count = 0;
  21. k = 0;
  22. for(int i = 0; i < N; i++) {
  23. //记录滑动窗口内组数
  24. if(0 == group[help[i][1]]++) count++;
  25. if(count == group.length) {
  26. //收缩窗口
  27. while(group[help[k][1]] > 1) group[help[k++][1]]--;
  28. //更新结果
  29. if(res[0] == 0 && res[1] == 0 || help[i][0] - help[k][0] < res[1] - res[0]) {
  30. res[0] = help[k][0];
  31. res[1] = help[i][0];
  32. }
  33. }
  34. }
  35. return res;
  36. }
  37. }

区间和的个数★★★

LeetCode 327. 区间和的个数

题目】给定一个整数数组 nums,返回区间和在 [lower, upper] 之间的个数,包含 lowerupper
区间和 S(i, j) 表示在 nums 中,位置从 ij 的元素之和,包含 ij (i ≤ j)

说明:
最直观的算法复杂度是 O(n^2) ,请在此基础上优化你的算法。

示例

  1. 输入: nums = [-2,5,-1], lower = -2, upper = 2,
  2. 输出: 3
  3. 解释: 3个区间分别是: [0,0], [2,2], [0,2],它们表示的和分别为: -2, -1, 2

解题思路

方法一:暴力法

  1. class Solution {
  2. public int countRangeSum(int[] nums, int lower, int upper) {
  3. int count = 0;
  4. for(int i = 0; i < nums.length; i++){
  5. long sum = 0;
  6. for(int j = i; j < nums.length; j++){
  7. sum += nums[j];
  8. if(sum >= lower && sum <= upper){
  9. count++;
  10. }
  11. }
  12. }
  13. return count;
  14. }
  15. }

方法二:归并排序思想

解题方法来自官方题解
https://leetcode-cn.com/problems/count-of-range-sum/solution/qu-jian-he-de-ge-shu-by-leetcode-solution/

具体思路如下图所示

在这里插入图片描述

  1. class Solution {
  2. //归并复制辅助数组
  3. private static long[] aux;
  4. public int countRangeSum(int[] nums, int lower, int upper) {
  5. if(nums == null || nums.length == 0) return 0;
  6. aux = new long[nums.length + 1];
  7. //前缀和数组
  8. long[] pre = new long[nums.length + 1];
  9. for(int i = 0; i < nums.length; i++) {
  10. pre[i + 1] = pre[i] + nums[i];
  11. }
  12. return sort(pre, 0, pre.length - 1, lower, upper);
  13. }
  14. private int sort(long[] pre, int le, int ri, int lower, int upper) {
  15. if(le >= ri) return 0;
  16. int mid = le + (ri - le) / 2;
  17. int countleft = sort(pre, le, mid, lower, upper);
  18. int countright = sort(pre, mid + 1, ri, lower, upper);
  19. int countcur = merge(pre, le, mid, ri, lower, upper);
  20. return countleft + countright + countcur;
  21. }
  22. private int merge(long[] pre, int le, int mid, int ri, int lower, int upper) {
  23. //复制
  24. for(int k = le; k <= ri; k++) {
  25. aux[k] = pre[k];
  26. }
  27. //计算符合要求的区间和个数
  28. int count = 0;
  29. int k = le, i = mid + 1, j = mid + 1;
  30. while(k <= mid) {
  31. while(i <= ri && pre[i] - pre[k] < lower) i++;
  32. while(j <= ri && pre[j] - pre[k] <= upper) j++;
  33. count += j - i;
  34. k++;
  35. }
  36. //合并
  37. k = le;
  38. i = le;
  39. j = mid + 1;
  40. while(i <= mid || j <= ri) {
  41. if(i > mid) {
  42. pre[k++] = aux[j++];
  43. }else if(j > ri) {
  44. pre[k++] = aux[i++];
  45. }else if(aux[i] <= aux[j]){
  46. pre[k++] = aux[i++];
  47. }else {
  48. pre[k++] = aux[j++];
  49. }
  50. }
  51. return count;
  52. }
  53. }

发表评论

表情:
评论列表 (有 0 条评论,271人围观)

还没有评论,来说两句吧...

相关阅读

    相关 leetcode 重叠区间问题

    重叠区间问题可以总结为在坐标轴上若干个位置为 (start(i),end(i))的区间,要求求解这些区间中有多少个不重叠区间,或者合并重叠的区间。 leetcode有大神总结