算法通关村--盘点面试大热门之区间问题

区间问题

判断区间是否重叠

LeetCode252:

给定一个会议时间安排的数组intervals,每个会议时间都会包括开始和结束的时间intervals[i] = [starti,endi],请你判断一个是是否能够参加这里面的全部会议。

示例1:

输入:intervals = [[0,30],[15,20],[5,10]]

解释:存在一个重叠区间,一个人在同一时刻只能参加一个会议。

思路:

因为一个人在同一时刻只能参加一个会议,因此只需要判断后面会议开始的时候,前面的是否结束即可。

public static boolean canAttendMeetings(int[][] intervals){
        //升序排列
        Arrays.sort(intervals,(v1,v2) -> v1[0] - v2[0]);
        for (int i = 1; i < intervals.length; i++){
            //后一个会议开始小于前一个会议结束,返回false
            if (intervals[i][0] < intervals[i - 1][1]){
                return false;
            }
        }
        return true;
    }

合并区间

LeetCode56:

以数组intervals表示若干个区间的集合,其中单个区间为intervals[i] = [starti,endi]。请你合并所有的重叠区间,并返回一个不重叠的数组,求该数组恰好覆盖输入中的所有区间。

示例1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]

输出:[[1,6],[8,10],[15,18]]

解释:区间[1,3]和[2,6]重叠,将他们合并为[1,6]

示例2:

输入:intervals = [[1,4],[4,5],[8,10]]

输出:[[1,5]]

解释:区间[1,4]和[4,5]可被视为重叠区间

思路:

与上面一题一样,将区间按照升序排列,然后判断当前区间是否与前面一个重叠,不重叠将当前区间加入集合,如果重叠,将当前区间和前一个区间合并。

public static int[][] merge(int[][] intervals){
        //升序排列
        Arrays.sort(intervals,(v1,v2) -> v1[0] - v2[0]);

        int[][] res = new int[intervals.length][2];
        int idx = -1;
        for (int[] interval:intervals){
            //如果数组为空,或者当前区间的起始位置 > 结束数组中最后区间的终止位置,说明不重叠
            if (idx == -1 || interval[0] > res[idx][1]){
                res[++idx] = interval;
            }else {
                //反之,重叠,将当前区间合并至结果数组的最后区间
                res[idx][1] = Math.max(res[idx][1],interval[1]);
            }
        }
        return Arrays.copyOf(res,idx+1);
    }

插入区间

LeetCode57:

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

示例1:

输入:intervals = [[1,3],[6,9]], newInterval = [2,5]

输出:[[1,5],[6,9]]

解释:新区间[2,5] 与[1,3]重叠,因此合并成为[1,5]

示例2:

输入:intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]

输出:[[1,2],[3,10],[12,16]]

解释:新区间[4,8] 与[3,5],[6,7],[8,10]重叠,因此合并成为[3,10]

public static int[][] insert(int[][] intervals,int[] newInterval){
        int[][] res = new int[intervals.length+1][2];
        int idx= 0;
        int i = 0;
        //有交集,计算后左右边界存入newInterval中
        while (i < intervals.length && intervals[i][0] <= newInterval[1]){
            newInterval[0] = Math.min(intervals[i][0],newInterval[0]);
            newInterval[1] = Math.max(intervals[i][1],newInterval[1]);
            i++;
        }
        //将求好的newInterval放入到res中
        res[idx++] = newInterval;
        //没有重叠的加入到res
        while (i < intervals.length){
            res[idx] = intervals[i++];
        }
        return Arrays.copyOf(res,idx);
    }

字符串分割

LeetCode763:

给你一个字符串s,我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。注意,划分的结果需要满足:将所有划分结果按照顺序连接,得到的字符串仍然是s。

返回一个表示每个字符串片段的长度的列表。

示例1:

输入:s = “ababcbacadefegdehijhklij”

输出:[9,7,8]

解释:划分结果为“ababcbaca”、“defegde”、“hijhklij”。每个字母最多出现在一个片段中。

像“ababcbacadefegde”、“hijhklij”这样划分是错误的,因为划分的片段数较少。

示例2:

输入:s = “eccbbbbdec”

输出:[10]

思路:

由于同一个字母只能出现在同一个片段,显然同一个字母的第一次出现的下标位置和最后一次出现的下标位置出现在同一个片段,所以先遍历字符串,得到每个字母出现的最后一次下标的位置。

遍历字符串,同时维护开始下标start和结束下标end,初始值都为0;

对每个能访问到的字母c,得到当前字母最后出现的下标位置endc,则当前片段结束的下标不会小于endc,因此end = Math.max(end,endc) ; 

当访问到下标end时,当前片段访问结束,当前片段的下标范围[start,end],长度为end-start+1,将当前片段的长度添加到返回值,然后令start = end + 1,继续寻找下一个片段。

public static List<Integer> partitionLabels(String s){
        int[] edge = new int[26];
        for (int i = 0; i < s.length(); i++){
            edge[s.charAt(i) - 'a'] = i;
        }
        List<Integer> list = new ArrayList<>();
        int start = 0;
        int end = 0;
        for (int i = 0; i < s.length(); i++){
            end = Math.max(end,edge[s.charAt(i) - 'a']);
            if (i == end){
                list.add(end-start+1);
                start = end + 1;
            }
        }
        return list;
    }

加油站问题

LeetCode134:

在一条环路上有n个加油站,其中第 i 个加油站有汽油gas[i]升。

你又一辆邮箱容量无限的汽车,从第 i 个加油站开往第 i + 1个加油站需要消耗cost[i]升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组gas和cost,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回-1.如果存在解,则保证它是唯一的。

示例1:

输入:gas = [1,2,3,4,5],cost = [3,4,5,1,2]

输出:3

解释:从3号加油站(索引为3处)出发,可获得4升汽油。此时油箱有 = 0 + 4 = 4升汽油。

开往4号加油站,此时邮箱有4 - 1 + 5 = 8升汽油

开往0号加油站,此时邮箱有8 - 2 + 1 = 7升汽油

开往1号加油站,此时邮箱有7 - 3 + 2 = 6升汽油

开往2号加油站,此时邮箱有6 - 4 + 3 = 5升汽油

开往3号加油站,你需要消耗5升汽油,正好足够你返回到3号加油站。

因此,3可为起始索引。

示例2:

输入:gas = [2,3,4],cost = [3,4,3]

输出:-1

解释:你不能从0号或者1号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。

我们从2号加油站出发,可以获得4升汽油。此时油箱有 = 0 + 4 = 4升汽油

开往0号加油站,此时油箱有 4 - 3 + 2 = 3升汽油

开往1号加油站,此时油箱有 3 - 3 + 3 = 3升汽油

你无法返回2号加油站,因为返程需要消耗4升汽油,但是你的油箱只有3升汽油。

因此,无论怎样,你都不可能绕环路行驶一周。

思路:

当总油量 大于等于 消耗量时,应该可以跑完一圈,在每一段时,各个加油站的剩油量相加一定是大于等于0的,每个加油站的剩余量 = 加油量 - 耗油量,i从0开始累加,一旦和小于0,说明[0,i]区间不能作为起始位置,必须从 i + 1开始。

public static int canCompleteCircuit(int[] gas,int[] cost){
        int curSum = 0;
        int totalSum = 0;
        int start = 0;
        for (int i = 0; i < gas.length; i++){
            curSum += gas[i] - cost[i];
            totalSum += gas[i] - cost[i];
            //当前累加rest[i]和curSum一旦小于0
            if (curSum < 0){
                //更新起始位置
                start = i + 1;
                //curSum从0开始
                curSum = 0;
            }
        }
        //油量不够跑一圈
        if (totalSum < 0){
            return -1;
        }
        return start;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值