区间问题
会议室
给定一个会议时间安排的数组 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;
}
合并区间
以数组 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);
}
插入区间
给你一个 无重叠的 *,*按照区间起始端点排序的区间列表。
在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。
示例 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);
}
字符串分割
给你一个字符串 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;
}
加油站
在一条环路上有 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(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;
}