区间问题(leetcode45)

文章介绍了几种基于区间的问题,如跳跃游戏II,要求找到最小跳跃次数,以及用最少数量的箭引爆气球和无重叠区间问题,这些问题都涉及到寻找重叠部分和优化步数。通过排序和贪心算法,可以有效地解决这些问题。此外,还讨论了如何处理特殊情况,如数字溢出。
摘要由CSDN通过智能技术生成

区间问题

45. 跳跃游戏 II

本题在55. 跳跃游戏的基础上又增加了一个”跳跃次数最少“的限制。使得本题在跳跃游戏的基础上难度增加了不少。

跳跃游戏只需要一个接一个的顺序遍历,只要能遍历到最后一个就可以返回。

但是本题需要时刻记录着当前跳跃可以覆盖的区间里,下一次可以覆盖到的最右边的区间的最值。

在逻辑判断上增加了不少东西。我们来举个例子

nums = [2,3,1,1,4]的答案应该是2。我们来模拟下跳跃的过程。

所以我们发现在一次跳跃的过程中。我们需要时刻更新记录着下一次跳跃能够覆盖的最大索引值。

结束条件:

  • 下一次跳跃的最大索引下标 >= 数组的最大索引:nextMaxIndex >= nums.length-1

  • 本次跳跃到了最数组的最大索引:curMaxIndex == nums.length-1

class Solution {
    public int jump(int[] nums) {
        int curMaxIndex = 0;//当前可以覆盖的的最远下标
        int nextMaxIndex = 0;//下一步可以覆盖的最远下标
        int res = 0;
        for (int i = 0; i < nums.length; i++) {
            nextMaxIndex = Math.max(i+nums[i], nextMaxIndex);
            //遍历到当前覆盖的最远下标
            if(i == curMaxIndex) {
                //还没有走到头
                if(i < nums.length-1) {
                    curMaxIndex = nextMaxIndex;
                    res++;
                    //如果下一次跳跃可以直接跳到最后
                    if (nextMaxIndex >= nums.length-1) break;;
                }else break;//如果本次跳跃到了数组的最大索引
            }
        }
        return res;
    }
}

452. 用最少数量的箭引爆气球

本题从逻辑上来判断其实是比较容易的,用贪心法的局部最优的思想:尽可能的打多重叠的部分。

那怎么用代码实现呢,代码中有一个比较巧妙的部分:更新最小右边界

步骤:

  • 对数组的第一个维度升序排序

  • 按照第一个维度对数组循环遍历

  • 进行逻辑判断

    • 如果当前气球的左边界 > 上一个气球的右边界,说明不重叠,res++(注意:res要从1开始,因为气球的个数肯定大于1)

    • 否则,把当前气球的右边界更新为Math.min(points[i][1], points[i-1][1]);,即上一个气球的右边界和本气球的右边界的最小值。

  • 循环结束,得出res结果

public int findMinArrowShots(int[][] points) {
        //排序,从小到大排
         Arrays.sort(points, (a, b) -> {
             return a[0] - b[0];
         });
        int res = 1;
        //和前一个的最小右边界对比
        for (int i = 1; i < points.length; i++) {
            //不重合:左边界大于上一个的右边界
            if (points[i][0] > points[i-1][1]) res++;
            else {//重合:更新最小右边界
                points[i][1] = Math.min(points[i][1], points[i-1][1]);
            }
        }
        return res;
    }
}

但是以上代码还有个小问题,[[-2147483646,-2147483645],[2147483646,2147483647]],用lambda表达式会导致这个例子输出异常。所以要改为Integer自带的compare方法进行判断。

class Solution {
    public int findMinArrowShots(int[][] points) {
        //排序,从小到大排
        //有可能会溢出[[-2147483646,-2147483645],[2147483646,2147483647]]
        Arrays.sort(points, (a, b) -> Integer.compare(a[0], b[0]));
        int res = 1;
        //和前一个的最小右边界对比
        for (int i = 1; i < points.length; i++) {
            //不重合:左边界大于上一个的右边界
            if (points[i][0] > points[i-1][1]) res++;
            else {//重合:更新最小右边界
                points[i][1] = Math.min(points[i][1], points[i-1][1]);
            }
        }
        return res;
    }
}

435. 无重叠区间

452. 用最少数量的箭引爆气球的思路是一模一样的,只不过把逻辑判断反过来就好。

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
        int res = 0;
        //和前一个的最小右边界对比
        for (int i = 1; i < intervals.length; i++) {
            //重合:更新最小右边界
            if (intervals[i][0] < intervals[i-1][1]){
                intervals[i][1] = Math.min(intervals[i][1], intervals[i-1][1]);
                res++;
            }            
        }
        return res;
    }
}

435. 无重叠区间

452. 用最少数量的箭引爆气球的思路是一模一样的,只不过把逻辑判断反过来就好。

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
        int res = 0;
        //和前一个的最小右边界对比
        for (int i = 1; i < intervals.length; i++) {
            //重合:更新最小右边界
            if (intervals[i][0] < intervals[i-1][1]){
                intervals[i][1] = Math.min(intervals[i][1], intervals[i-1][1]);
                res++;
            }            
        }
        return res;
    }
}

763. 划分字母区间

本题属于是逻辑和代码都不是很好写的那种类型了。

首先我们先从逻辑出发,需要找出符合题目要求的子串。可以理解为:

  • 找到每个字母的最远边界

  • 该字母前的所有字母也都是已经到达最远边界

比如子串ababcbaca其中a,b,c三个字母的最远边界分别为8,5,7。

在最后一个字母a之前出现过的所有字母(a,b)都已经到达最远边界。

所以我们首先需要一个数组来装每个字母的最远边界

int[] edge = new int[26];
char[] chars = s.toCharArray();
//存储每个字母的最后一次出现所在的索引
for (int i = 0; i < chars.length; i++) {
    int index = chars[i] - 'a';
    edge[index] = i;
}

其次,要遍历chars数组,看符合条件的位置。

int start = 0;//每个子串的起始索引
int idx = 0;//当前子串的最后一位索引
for (int i = 0; i < chars.length; i++) {
    int index = chars[i] - 'a';
    idx = Math.max(idx, edge[index]);//记录当前出现过的字母的最大索引位置
    if (idx == i) {
        res.add(i-start+1);
        start = i+1;
    }
}

记录当前出现过的字母的最大索引位置,相对来说比较难理解,建议多看看,找个例子推一遍。

class Solution {
    public List<Integer> partitionLabels(String s) {
        List<Integer> res = new ArrayList<>();
        int[] edge = new int[26];
        char[] chars = s.toCharArray();
        //存储每个字母的最后一次出现所在的索引
        for (int i = 0; i < chars.length; i++) {
            int index = chars[i] - 'a';
            edge[index] = i;
        }
        int start = 0;//每个子串的起始索引
        int idx = 0;//当前子串的最后一位索引
        for (int i = 0; i < chars.length; i++) {
            int index = chars[i] - 'a';
            idx = Math.max(idx, edge[index]);
            if (idx == i) {
                res.add(i-start+1);
                start = i+1;
            }
        }
        return res;
    }
}

本题课可以按照435. 无重叠区间452. 用最少数量的箭引爆气球的思路来写,但是过程就会麻烦很多。

56. 合并区间

本题从逻辑上来说,可以说是很简单了,重合了就给合并了。代码上和前几题基本一致,就是有一些细节上的东西需要一点点的改。

class Solution {
    public int[][] merge(int[][] intervals) {
        //偶排序
        Arrays.sort(intervals, (a,b) -> {
            if (a[0] == b[0]) return a[1] - b[1];
            return a[0] - b[0];
        });
        //因为第二维度是固定为2,而第一维度是会随着重合区间越多而变少的,所以写成这种类型
        List<int[]> res = new ArrayList<>();
        int start = intervals[0][0];//区间的起始位置
        for (int i = 1; i < intervals.length; i++) {
            //不重叠,把前面重叠的区间合成一个大区间加入结果集
            if (intervals[i][0] > intervals[i-1][1]) {
                int[] interval = new int[]{start, intervals[i-1][1]};
                res.add(interval);
                start = intervals[i][0];//设置下一个区间的起点
            }else {
                intervals[i][1] = Math.max(intervals[i-1][1], intervals[i][1]);
            }
        }
        res.add(new int[]{start,intervals[intervals.length-1][1]});
        return res.toArray(new int[res.size()][]);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值