贪心高频问题(算法村第十七关白银挑战)

区间问题

会议室

252. 会议室 - 力扣(LeetCode)

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

示例 1:

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

示例 2:

输入:intervals = [[7,10],[2,4]]
输出:true

提示:

  • 0 <= intervals.length <= 104
  • intervals[i].length == 2
  • 0 <= starti < endi <= 10

理解问题的关键:一个人在同一时刻只能参加一场会议。

解题的切入点:判断会议区间是否重叠。

解题的关键:先排序再判断。

public boolean canAttendMeetings(int[][] intervals)
{
    //按开始时间对会议区间进行升序排序
    /*例如:
        [ [0,30],
          [5,10],
          [15,20] ]
    */
    Arrays.sort(intervals, (v1, v2) -> (v1[0] - v2[0]));

    for (int i = 1; i < intervals.length; i++)
        //下一个会议在上一个会议结束前开始
        if (intervals[i][0] < intervals[i - 1][1])
            return false;

    return true;
}

合并区间

56. 合并区间 - 力扣(LeetCode)

以数组 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]]
输出:[[1,5]]
解释:区间 [1,4][4,5] 可被视为重叠区间。

二维数组实际上是一个数组的数组,因此不能直接删除其中的元素。但是,我们可以通过创建一个新的二维数组,将原数组中除了要删除的元素之外的所有元素复制到新数组中,从而实现删除元素的效果。

public int[][] merge(int[][] intervals)
{
    //按左界对区间进行升序排序
    Arrays.sort(intervals, (v1, v2) -> (v1[0] - v2[0]));

    //merge储存处理过后的区间
    int[][] merge = new int[intervals.length][2];
    int index = -1; // merge的最大有效索引

    for (int[] interval : intervals)
    {
        //若merge为空,则直接放入;区间interval
        //若区间interval的左界 > 区间merge[index]的右界,则直接放入interval
        if (index == -1 || merge[index][1] < interval[0])
            merge[++index] = interval;

        // 若出现重叠,则将interval并入区间merge[index],并更新merge[index]的右界
        else
            merge[index][1] = Math.max(merge[index][1], interval[1]);
    }

    //截取合并后的有效区间长度
    return Arrays.copyOf(merge, index + 1);
}

插入区间

57. 插入区间 - 力扣(LeetCode)

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

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

示例 1:

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

示例 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] 重叠。
public int[][] insert(int[][] intervals, int[] newInterval)
{
    int[][] res = new int[intervals.length + 1][2];
    int index = 0;
    int i = 0;

    //将在新区间左侧且与新区间相离的区间放入结果集
    while (i < intervals.length && newInterval[0] > intervals[i][1])
        res[index++] = intervals[i++];

    //将与新区间重叠的区间逐一合并起来,再加入结果集
    while (i < intervals.length && newInterval[1] >= intervals[i][0])
    {
        newInterval[0] = Math.min(newInterval[0], intervals[i][0]);
        newInterval[1] = Math.max(newInterval[1], intervals[i][1]);
        i++;
    }
    res[index++] = newInterval;

    //将剩下的区间加入结果集
    while (i < intervals.length)
        res[index++] = intervals[i++];

    //返回有效的区间集
    return Arrays.copyOf(res, index);
}

字符串分割

763. 划分字母区间 - 力扣(LeetCode)

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。

注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s

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

示例 1:

输入:s = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca""defegde""hijhklij" 。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。 

示例 2:

输入:s = "eccbbbbdec"
输出:[10]

这道题的关键在于找到恰当分割点点的位置。分割点的位置由什么决定?某种字母最后出现的位置,我们可称之为边界。大致思路比较简单,但还需要一些比较细致巧妙的代码处理,比较边界的过程就体现了贪心的思想

public List<Integer> partitionLabels(String s)
{
    //寻找每种字母出现位置的边界
    int[] edges = new int[26];
    for (int i = 0; i < s.length(); i++)
        edges[s.charAt(i) - 'a'] = i;

    //寻找分割点
    int splitPoint = 0;
    int lastIndex = -1; //上一段的末尾索引
    ArrayList<Integer> ans = new ArrayList<>();

    for (int i = 0; i < s.length(); i++)
    {
        //为保证每种字母至多出现在一个分割段中,该段的分割点必须由遍历字母中最大边界值确定
        splitPoint = Math.max(splitPoint, edges[s.charAt(i) - 'a']);

        //遍历位置到达分割点
        if(i == splitPoint)
        {
            ans.add(splitPoint - lastIndex);
            lastIndex = splitPoint;
        }
    }

    return ans;
}

加油站

134. 加油站 - 力扣(LeetCode)

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

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

给定两个整数数组 gascost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -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(n)

和[最大子列和]的在线算法类似

public int canCsurplusompleteCircuit(int[] gas, int[] cost)
{
    int start = 0;  //选定的起点加油站
    int curGas = 0; //起点到当前加油站的剩余油量
    int totalGas = 0; //总的剩油量

    //一次遍历解决问题
    for (int i = 0; i < gas.length; i++)
    {
        curGas += gas[i] - cost[i]; //i号加油站的油量 - 去往i+1号加油站的耗油量
        totalGas += gas[i] - cost[i];

        //若 curGas < 0,则说明从 start 号加油站出发无法到达 i+1 号加油站;
        //少了从 start 到 start+1 号加油站的[正剩油量]的加持,从[start + 1, i]之间的加油站出发,更不可能达到第 i+1号加油站
        if(curGas < 0)
        {
            start = i + 1;  //重新选定起点
            curGas = 0;
        }
    }

    //从哪里开始都无法循环一圈回到起点
    if (totalGas < 0)
        return -1;

    return start;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值