算法与数据结构(十一):Intervals 题型总结

Intervals 问题总结

Intervals 作为一种在面试中被问过两次的面试题型,现总结于此,该博文仅用于学习交流;

1. LeetCode 56. Merge Intervals

CategoryDifficultyLikesDislikes
algorithmsMedium (35.86%)2192166

Given a collection of intervals, merge all overlapping intervals.

Example 1:

Input: [[1,3],[2,6],[8,10],[15,18]]
Output: [[1,6],[8,10],[15,18]]
Explanation: Since intervals [1,3] and [2,6] overlaps, merge them into [1,6].

Example 2:

Input: [[1,4],[4,5]]
Output: [[1,5]]
Explanation: Intervals [1,4] and [4,5] are considered overlapping.

NOTE: input types have been changed on April 15, 2019. Please reset to default code definition to get new method signature.

解决方案

解法一:

这道和之前那道 LeetCode 57. Insert Interval 很类似,这次题目要求我们合并区间,之前那题明确了输入区间集是有序的,而这题没有,所以我们首先要做的就是给区间集排序;

  • 我们以 start 的值从小到大来排序,排完序我们就可以开始合并了,首先把第一个区间存入结果中;

  • 然后从第二个开始遍历区间集,如果结果中最后一个区间和遍历的当前区间无重叠,直接将当前区间存入结果中;

  • 如果有重叠,将结果中最后一个区间的 end 值更新为结果中最后一个区间的 end 和当前 end 值之中的较大值,然后继续遍历区间集,以此类推可以得到最终结果。

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        int length = intervals.size();
        if (length < 1) return {};
        sort(intervals.begin(), intervals.end());
        vector<vector<int>> res{intervals[0]};
        for (int i = 1; i < length; ++i) {
            if (res.back()[1] < intervals[i][0]) {
                res.push_back(intervals[i]);
            }
            else {
                res.back()[1] = max(res.back()[1], intervals[i][1]);
            }
        }
        return res;
    }
};

解法二:

这个解法直接调用了之前那道题 LeetCode 57. Insert Interval 的函数,由于插入的过程中也有合并的操作,所以我们可以建立一个空的集合,然后把区间集的每一个区间当做一个新的区间插入结果中,也可以得到合并后的结果,那道题中的四种解法都可以在这里使用,但是没必要都列出来,这里只选了那道题中的解法二放到这里,代码如下:

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        int length = intervals.size();
        if (length < 1) return {};
        vector<vector<int>> res;
        for (int i = 0; i < length; ++i) {
            res = insert(res, intervals[i]);
        }
        return res;
    }


    vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& match) {
        int length = intervals.size();
        int cur = 0;
        vector<vector<int>> res;
        for (int i = 0; i < length; i++) {
            if (intervals[i][1] < match[0]) {
                res.push_back(intervals[i]);
                cur ++;
            }
            else if (intervals[i][0] > match[1]) {
                res.push_back(intervals[i]);
            }
            else {
                match[0] = min(match[0], intervals[i][0]);
                match[1] = max(match[1], intervals[i][1]);
            }
        }
        res.insert(res.begin() + cur, match);
        return res;
    }
};

2. LeetCode 57. Insert Interval

CategoryDifficultyLikesDislikes
algorithmsHard (31.35%)884111

Given a set of non-overlapping intervals, insert a new interval into the intervals (merge if necessary).

You may assume that the intervals were initially sorted according to their start times.

Example 1:

Input: intervals = [[1,3],[6,9]], newInterval = [2,5]
Output: [[1,5],[6,9]]

Example 2:

Input: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
Output: [[1,2],[3,10],[12,16]]
Explanation: Because the new interval [4,8] overlaps with [3,5],[6,7],[8,10].

NOTE: input types have been changed on April 15, 2019. Please reset to default code definition to get new method signature.

解决方案:

这道题让我们在一系列非重叠的区间中插入一个新的区间,可能还需要和原有的区间合并;

我们可以对给定的区间集进行一个一个的遍历比较,那么会有两种情况,重叠或是不重叠;

不重叠的情况最好,直接将新区间插入到对应的位置即可;

重叠的情况比较复杂,有时候会有多个重叠,我们需要更新新区间的范围以便包含所有重叠,之后将新区间加入结果 res,最后将后面的区间再加入结果 res 即可。

具体思路是,

  • 我们用一个变量 cur 来遍历区间,如果当前 cur 区间的结束位置小于要插入的区间的起始位置的话,说明没有重叠,则将 cur 区间加入结果 res 中,然后 cur 自增1。
  • 直到有 cur 越界或有重叠 while 循环退出,然后再用一个 while 循环处理所有重叠的区间,每次用取两个区间起始位置的较小值,和结束位置的较大值来更新要插入的区间,然后 cur 自增1。直到 cur 越界或者没有重叠时 while 循环退出。
  • 之后将更新好的新区间加入结果 res,然后将 cur 之后的区间再加入结果 res 中即可,参见代码如下:
class Solution {
public:
    vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) {
        vector<vector<int>> res;
        int length = intervals.size(),  cur = 0;
        while (cur < length && intervals[cur][1] < newInterval[0]) {
            res.push_back(intervals[cur++]);
        }
        while (cur < length && intervals[cur][0] <= newInterval[1]) {
            newInterval[0] = min(newInterval[0], intervals[cur][0]);
            newInterval[1] = max(newInterval[1], intervals[cur][1]);
            ++cur;
        }
        res.push_back(newInterval);
        while (cur < length) {
            res.push_back(intervals[cur++]);
        }
        return res;
    }

};

将 while 循环改成 for 循环,代码如下:

class Solution {
public:
    vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) {
        vector<vector<int>> res;
        int n = intervals.size(), cur = 0;
        for (int i = 0; i < n; ++i) {
            if (intervals[i][1] < newInterval[0]) {
                res.push_back(intervals[i]);
                ++cur;
            } else if (intervals[i][0] > newInterval[1]) {
                res.push_back(intervals[i]);
            } else {
                newInterval[0] = min(newInterval[0], intervals[i][0]);
                newInterval[1] = max(newInterval[1], intervals[i][1]);
            }
        }
        res.insert(res.begin() + cur, newInterval);
        return res;
    }
};

3. LeetCode 495. Teemo Attacking

CategoryDifficultyLikesDislikes
algorithmsMedium (52.33%)248521

In LOL world, there is a hero called Teemo and his attacking can make his enemy Ashe be in poisoned condition. Now, given the Teemo’s attacking ascending time series towards Ashe and the poisoning time duration per Teemo’s attacking, you need to output the total time that Ashe is in poisoned condition.

You may assume that Teemo attacks at the very beginning of a specific time point, and makes Ashe be in poisoned condition immediately.

Example 1:

Input: [1,4], 2
Output: 4
Explanation: At time point 1, Teemo starts attacking Ashe and makes Ashe be poisoned immediately. 
This poisoned status will last 2 seconds until the end of time point 2. 
And at time point 4, Teemo attacks Ashe again, and causes Ashe to be in poisoned status for another 2 seconds. 
So you finally need to output 4.

Example 2:

Input: [1,2], 2
Output: 3
Explanation: At time point 1, Teemo starts attacking Ashe and makes Ashe be poisoned. 
This poisoned status will last 2 seconds until the end of time point 2. 
However, at the beginning of time point 2, Teemo attacks Ashe again who is already in poisoned status. 
Since the poisoned status won't add up together, though the second poisoning attack will still work at time point 2, it will stop at the end of time point 3. 
So you finally need to output 3.

Note:

  1. You may assume the length of given time series array won’t exceed 10000.
  2. You may assume the numbers in the Teemo’s attacking time series and his poisoning time duration per attacking are non-negative integers, which won’t exceed 10,000,000.
解决方案:

这道题题目很长,背景信息很丰富,但是题很简单,说白了就是一个 Interval merge 的计算,因为无法叠加中毒状态,故而求中毒时长时,直接取施法间隔与中毒持续状态中的较小值,此外,最后一次施法一定可以保证施法状态可以得到完整展现,因此,最终的 res 一定有一个 完整的 duration 状态。

代码如下:

class Solution {
public:
    int findPoisonedDuration(vector<int>& timeSeries, int duration) {
        if (timeSeries.empty()) return 0;
        int res = 0;
        for (int i = 1; i < timeSeries.size(); ++i) {
            res += min(timeSeries[i]  - timeSeries[i-1], duration);
        }
        res += duration;
        return res;
    }
};

4. Partition Labels

CategoryDifficultyLikesDislikes
algorithmsMedium (70.82%)102554

A string S of lowercase letters is given. We want to partition this string into as many parts as possible so that each letter appears in at most one part, and return a list of integers representing the size of these parts.

Example 1:

Input: S = "ababcbacadefegdehijhklij"
Output: [9,7,8]
Explanation:
The partition is "ababcbaca", "defegde", "hijhklij".
This is a partition so that each letter appears in at most one part.
A partition like "ababcbacadefegde", "hijhklij" is incorrect, because it splits S into less parts.

Note:

  1. S will have length in range [1, 500].
  2. S will consist of lowercase letters ('a' to 'z') only.
解决方案

这道题给了我们一个字符串S,然我们将其尽可能多的分割为子字符串,条件是每种字符最多只能出现在一个子串中。比如题目汇总的例子,字符串S中有多个a,这些a必须只能在第一个子串中,再比如所有的字母e值出现在了第二个子串中。

那么这道题的难点就是如何找到字符串的断点,即拆分成为子串的位置。

我们仔细观察题目中的例子,可以发现一旦某个字母多次出现了,那么其最后一个出现位置必须要在当前子串中,字母a,e,和j,分别是三个子串中的结束字母。

所以我们关注的是每个字母最后的出现位置,我们可以使用一个HashMap来建立字母和其最后出现位置之间的映射,那么对于题目中的例子来说,我们可以得到如下映射:

a -> 8
b -> 5
c -> 7
d -> 14
e -> 15
f -> 11
g -> 13
h -> 19
i -> 22
j -> 23
k -> 20
l -> 21

建立好映射之后,就需要开始遍历字符串S了。

我们维护一个start变量,是当前子串的起始位置,还有一个last变量,是当前子串的结束位置,每当我们遍历到一个字母,我们需要在HashMap中提取出其最后一个位置,因为一旦当前子串包含了一个字母,其必须包含所有的相同字母,所以我们要不停的用当前字母的最后一个位置来更新last变量,只有当i和last相同了,即当i = 8时,当前子串包含了所有已出现过的字母的最后一个位置,即之后的字符串里不会有之前出现过的字母了,此时就应该是断开的位置,我们将长度9加入结果res中,同理类推,我们可以找出之后的断开的位置,参见代码如下:

class Solution {
public:
    vector<int> partitionLabels(string S) {
        vector<int> res;
        int n = S.size(), start = 0, last = 0;
        unordered_map<char, int> m;
        for (int i = 0; i < n; ++i) m[S[i]] = i;
        for (int i = 0; i < n; ++i) {
            last = max(last, m[S[i]]);
            if (i == last) {
                res.push_back(i - start + 1);
                start = i + 1;
            }
        }
        return res;
    }
};
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值