leetcode刷题记录——区间

228 汇总矩阵

给定一个 无重复元素有序 整数数组 nums

返回 恰好覆盖数组中所有数字最小有序 区间范围列表 。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums 的数字 x

列表中的每个区间范围 [a,b] 应该按如下格式输出:

a->b" ,如果 a != b
a“ ,如果 a == b

示例 1:

输入:nums = [0,1,2,4,5,7]

输出:[“0->2”,”4->5”,”7”]

解释:区间范围是:

[0,2] –> “0->2”

[4,5] –> “4->5”

[7,7] –> “7”

示例 2:

输入:nums = [0,2,3,4,6,8,9]

输出:[“0”,”2->4”,”6”,”8->9”]

解释:区间范围是:

[0,0] –> “0”

[2,4] –> “2->4”

[6,6] –> “6”

[8,9] –> “8->9”

提示:

0 <= nums.length <= 20

-231 <= nums[i] <= 231 - 1

nums 中的所有值都 互不相同

nums 按升序排列

思路

看到题目第一时间想到两种情况

  1. 如果当前指针和下一个指针两个位置的数相等,将当前指针位置存储到数组中并后移两个指针。
  2. 如果当前指针和下一个指针位置不相等,则下一个指针再往前一个位置。
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def summaryRanges(self, nums: List[int]) -> List[str]:
def help_func(i: int, j: int) -> str:
return str(nums[i]) if i == j else str(nums[i]) + '->' + str(nums[j])

i, n, ans = 0, len(nums), []
while i < n:
j = i
while j + 1 < n and nums[j + 1] == nums[j] + 1:
j += 1
ans.append(help_func(i, j))
i = j + 1
return ans

时间复杂度

时间复杂度O(n),执行时间30ms,消耗内存16.2MB

反思

刚开始想到的是三指针,但这种情况下,i和j同步往前移,k在i一开始的位置。但是这样后来一想完全没必要,反而浪费了时间。所以双指针直接就能解决,需要检测动的只有一个指针。

然后看到一个运行速度更快的算法,思路都是一样的,但是这个的执行时间更少。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution:
def summaryRanges(self, nums: List[int]) -> List[str]:
'''i最多执行n次,O(n)'''
n = len(nums)
ans = []
i = 0
while i <n: # i从左到右遍历
start = i
while i <n -1 and nums[i] + 1== nums[i+1]: # 一直遍历下去直到不连续
i += 1
s = str(nums[start])
if start < i:
s += '->'
s += str(nums[i])
ans.append(s)
i += 1
return ans

56 合并数组

以数组 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] 可被视为重叠区间。

提示:

1 <= intervals.length <= 104

intervals[i].length == 2

0 <= starti <= endi <= 104

思路

这道题一拿到肯定会有点懵,因为第一眼看肯定不知道这俩玩意咋能合并。

仔细拆解后会发现,题目给出的示例已经给我们怎么去写条件了,当我们当前区间的第一个元素比钱一个区间的第二个元素小或者相等,这时我们就满足了条件。

然后我们需要找到谁作为新区间的第二个元素,这里很明显就是前一个区间的第二个元素和当前区间的第二个元素较大的那个。

最后需要强调一个事情就是[-1][1]指的是列表中[-1]指最后一个区间,[1]指第二个元素。代码中ans[-1][1]就是ans列表中的最后一个区间(也就是离当前区间最近的)中第二个元素。

1
2
3
4
5
6
7
8
9
10
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort()
ans = []
for i in intervals:
if ans and i[0] <= ans[-1][1]:
ans[-1][1] = max(ans[-1][1], i[1])
else:
ans.append(i)
return ans

时间复杂度

时间复杂度O(n log n),执行时间54ms,消耗内存19.5MB

反思

后来查看了一下其他人的代码,后面的内容大差不差但在排序上使用了lambda来写条件,可以节约时间。但思路上看了几个其他人的都是一样的,先排序后循环比大小。

57 插入区间

给你一个 无重叠的 ,按照区间起始端点排序的区间列表 intervals,其中 intervals[i] = [starti, endi] 表示第 i 个区间的开始和结束,并且 intervals 按照 starti 升序排列。同样给定一个区间 newInterval = [start, end] 表示另一个区间的开始和结束。

intervals 中插入区间 newInterval,使得 intervals 依然按照 starti 升序排列,且区间之间不重叠(如果有必要的话,可以合并区间)。

返回插入之后的 intervals

注意 你不需要原地修改 intervals。你可以创建一个新数组然后返回它。

示例 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] 重叠。

提示:

0 <= intervals.length <= 104

intervals[i].length == 2

0 <= starti <= endi <= 105

intervals 根据 starti升序 排列

newInterval.length == 2

0 <= start <= end <= 105

思路

这道题和前面一道题的唯二不同就在于我们需要先将新区间插入到列表,然后在循环阶段需要引入start和end,其余和前一题一摸一样。

1
2
3
4
5
6
7
8
9
10
11
class Solution:
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
intervals.append(newInterval)
intervals.sort()
ans = [intervals[0]]
for s, e in intervals[1:]:
if ans[-1][1] < s:
ans.append([s, e])
else:
ans[-1][1] = max(ans[-1][1], e)
return ans

时间复杂度

时间复杂度O(n log n),执行时间49ms,消耗内存18.5MB

反思

这里给出我第一次做的时候的错误代码

1
2
3
4
5
6
7
8
9
10
11
class Solution:
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
intervals.append(newInterval)
intervals.sort()
ans = []
for i in intervals:
if ans and i[0] <= ans[-1][1]:
ans[-1] = max(ans[-1][1], i[1])
else:
ans.append(i)
return ans

没有修改循环部分的代码,导致更新了结束的时间忽略了开始时间导致区间格式错误。

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

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [x_start, x_end] 表示水平直径在 xstartxend 之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstartxend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。

示例 1:

输入:points = [[10,16],[2,8],[1,6],[7,12]]

输出:2

解释:气球可以用2支箭来爆破:

-在x = 6处射出箭,击破气球[2,8]和[1,6]。

-在x = 11处发射箭,击破气球[10,16]和[7,12]。

示例 2:

输入:points = [[1,2],[3,4],[5,6],[7,8]]

输出:4

解释:每个气球需要射出一支箭,总共需要4支箭。

示例 3:

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

输出:2

解释:气球可以用2支箭来爆破:

  • 在x = 2处发射箭,击破气球[1,2]和[2,3]。

  • 在x = 4处射出箭,击破气球[3,4]和[4,5]。

提示:

1 <= points.length <= 105

points[i].length == 2

-231 <= xstart < xend <= 231 - 1

这里找了张评论区一个大哥画的示意图

示意图

思路

  1. 首先对整个气球架的点进行排序,并且把第一个start和end初始化。
  2. 循环中从第二个气球开始,然后思路和前一道题类似,更新最大最小值。
  3. 然后不满足条件了进入else,箭数量加1然后更新点坐标。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
points.sort()
arrow = 0
start, end = points[0][0], points[0][1]
for i in range(1, len(points)):
if start <= points[i][0] <= end:
start = max(start, points[i][0])
end = min(end, points[i][1])
else:
arrow += 1
start, end = points[i][0], points[i][1]
arrow += 1
return arrow

时间复杂度

时间复杂度O(n log n),执行时间370ms,消耗内存49.5MB

反思

我这个做法相对来说不是很常见的写法,太过直译。下面附上大多数人的做法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
if not points:
return 0

points.sort(key=lambda balloon: balloon[1])
pos = points[0][1]
ans = 1
for balloon in points:
if balloon[0] > pos:
pos = balloon[1]
ans += 1

return ans

大部分人的思路都是类似这个,内容可能有差距但思路大差不差。


leetcode刷题记录——区间
https://www.lx02918.ltd/2024/08/05/leetcode_区间/
作者
Seth
发布于
2024年8月5日
许可协议