目录
252. 会议室
https://leetcode-cn.com/problems/meeting-rooms/comments/
给定一个会议时间安排的数组,每个会议时间都会包括开始和结束的时间 [[s1,e1],[s2,e2],...] (si < ei),请你判断一个人是否能够参加这里面的全部会议。
示例 1:输入: [[0,30],[5,10],[15,20]],输出: false
示例 2:输入: [[7,10],[2,4]],输出: true
思路
一:参考leetcode数组中的问题(二)的56合并区间,该题还要更简单一点,直接按起点排序,区间是否重叠,可以看前一个区间的终点是否大于该区间的起点。附上leetcode的官方简答的图https://leetcode-cn.com/problems/meeting-rooms/solution/hui-yi-shi-by-leetcode/。
class Solution(object):
def canAttendMeetings(self, intervals):
"""
:type intervals: List[List[int]]
:rtype: bool
"""
if len(intervals) <= 1:
return True
# 按起点排序
sorted_intervals = sorted(intervals, key = lambda a: a[0])
for i in range(1, len(sorted_intervals)):
if sorted_intervals[i - 1][1] > sorted_intervals[i][0]:
return False
return True
253. 会议室 II
https://leetcode-cn.com/problems/meeting-rooms-ii/
给定一个会议时间安排的数组,每个会议时间都会包括开始和结束的时间 [[s1,e1],[s2,e2],...] (si < ei),为避免会议冲突,同时要考虑充分利用会议室资源,请你计算至少需要多少间会议室,才能满足这些会议安排。
示例 1:输入: [[0, 30],[5, 10],[15, 20]],输出: 2
示例 2:输入: [[7,10],[2,4]],输出: 1
思路
一:通常遇到区间相关问题,排序是很必要的,开辟一个数组res,用来记录某一个会议是否要征用新的办公室,征用新的为1,不要征用新的为0,则最后res数组求和就是所求解。如何判断是否征用新的会议室,若已开会议室中最后一个会议已结束,则可以接上。刚开始还本着节约资源的原则,接在最早结束的会议室,以减少会议室控制时间,即题解一。但是后面发现,其实只要会议结束,接上就行,无所谓接在哪个后面,因为所有能接受该会议的会议室,必然能接受他后面的所有会议(后面的开始的更晚)。
class Solution(object):
def minMeetingRooms(self, intervals):
"""
:type intervals: List[List[int]]
:rtype: int
"""
if not intervals:
return 0
if len(intervals) == 1:
return 1
intervals_sort = sorted(intervals, key=lambda a: a[0])
# res中0-表示该会议不用征用一个新的会议室,1-表示要征用新的会议室。
res= [0] * len(intervals_sort)
# 初始化,第一场会议必须征用一个新的会议室
res[0] = 1
# 记录最后一场会议的结束时间,方便后续会议的安排
rec_high = [item[1] for item in intervals_sort]
for i in range(1, len(intervals_sort)):
min_high = intervals_sort[i][1]
idx = -1
for j in range(0, i):
# 若有一个会议开辟了新的会议室,且i号会议在该会议是最后一场结束后才开始,安插在其后就好。
if res[j] == 1 and intervals_sort[i][0] >= rec_high[j]:
if min_high > rec_high[j]:
min_high = rec_high[j]
idx = j
if idx == -1:
res[i] = 1
else:
rec_high[idx] = intervals_sort[i][1]
return sum(res)
二:只要能接上的会议室随意找到一个就行,不拘哪个,找不到则该会议要征用一个新的会议室。让我们换个角度,从一组想要开会但还没有分配会议室的人员的角度来看这个问题。他们会怎么做?这组人会依次查看会议室,检查是否有任何会议室是空闲的。如果他们找到一间空闲的会议室,就会在那个房间里开始开会。否则,他们会等待一个会议室空出来。一旦会议室空出来,他们就会占用它。这就是我们将遵循的基本方法。可以说(但不确切),这是一个模拟的过程。在最坏的情况下,我们可以为每个会议分配一个新房间,但这显然不是最优的,除非它们相互冲突。我们需要高效地判断当前会议是否有房间可用,并只在分配的房间目前没有空闲时才分配新房间。一个朴素的方法是,每当有新会议时,就遍历所有房间,查看是否有空闲房间。O(n^2)的时间复杂度。
class Solution(object):
def minMeetingRooms(self, intervals):
if not intervals:
return 0
if len(intervals) == 1:
return 1
intervals_sort = sorted(intervals, key=lambda a: a[0])
# res中0-表示该会议不用征用一个新的会议室,1-表示要征用新的会议室。
res= [0] * len(intervals_sort)
# 初始化,第一场会议必须征用一个新的会议室
res[0] = 1
for i in range(1, len(intervals_sort)):
idx = -1
for j in range(0, i):
# 若有一个会议开辟了新的会议室,且i号会议在该会议是最后一场结束后才开始,安插在其后就好。
if res[j] == 1 and intervals_sort[i][0] >= intervals_sort[j][1]:
intervals_sort[j][1] = intervals_sort[i][1]
idx = j
break
if idx == -1:
res[i] = 1
return sum(res)
三:我们可以将所有房间保存在最小堆中,堆中的键值是会议的结束时间,而不用手动迭代已分配的每个房间并检查房间是否可用。这样,每当我们想要检查有没有 任何 房间是空的,只需要检查最小堆堆顶的元素,它是最先开完会腾出房间的。如果堆顶的元素的房间并不空闲,then 其他所有房间都不空闲。这样,我们就可以直接开一个新房间。最终最小堆的元素的长度即是解。我们可以理解为最小堆中的放的是占用的会议室,若有空闲,将该会议室释放,否则不做操作,但是一个会议必须开辟会议室,只不过有释放的逻辑上理解为用之前的会议室,没有释放的理解为开新会议室。
class Solution(object):
def minMeetingRooms(self, intervals):
if not intervals:
return 0
if len(intervals) == 1:
return 1
free_rooms = []
intervals.sort(key=lambda item: item[0])
# 借助堆,给第一个会议分配会议室
heapq.heappush(free_rooms, intervals[0][1])
for i in range(1, len(intervals)):
# 查看最早结束的会议是否已经结束,若结束则将会议室释放出来,可以用来给该次会议
if free_rooms[0] <= intervals[i][0]:
heapq.heappop(free_rooms)
# 无论是否有空房间,我们都必须给这次会议分配会议室
heapq.heappush(free_rooms, intervals[i][1])
return len(free_rooms)
1094. 拼车
https://leetcode-cn.com/problems/car-pooling/submissions/
假设你是一位顺风车司机,车上最初有 capacity 个空座位可以用来载客。由于道路的限制,车 只能 向一个方向行驶(也就是说,不允许掉头或改变方向,你可以将其想象为一个向量)。
这儿有一份行程计划表 trips[][],其中 trips[i] = [num_passengers, start_location, end_location] 包含了你的第 i 次行程信息必须接送的乘客数量;乘客的上车地点;以及乘客的下车地点。这些给出的地点位置是从你的 初始 出发位置向前行驶到这些地点所需的距离(它们一定在你的行驶方向上)。请你根据给出的行程计划表和车子的座位数,来判断你的车是否可以顺利完成接送所用乘客的任务(当且仅当你可以在所有给定的行程中接送所有乘客时,返回 true,否则请返回 false)。
示例 1:输入:trips = [[2,1,5],[3,3,7]], capacity = 4,输出:false
示例 2:输入:trips = [[2,1,5],[3,3,7]], capacity = 5,输出:true
示例 3:输入:trips = [[2,1,5],[3,5,7]], capacity = 3,输出:true
示例 4:输入:trips = [[3,2,7],[3,7,9],[8,3,9]], capacity = 11,输出:true
提示:你可以假设乘客会自觉遵守 “先下后上” 的良好素质
trips.length <= 1000
trips[i].length == 3
1 <= trips[i][0] <= 100
0 <= trips[i][1] < trips[i][2] <= 1000
1 <= capacity <= 100000
思路
一:用上题的方法一,也可以做,res[i]为True表示i行程的乘客还在车上,False表示已经下车,乘客上车要检查空座c是否能满足此行程的乘客数,要遍历上一个行程乘客上车后还在车上的所有乘客,看看是否这一段路途中有乘客下车释放空座位。
class Solution(object):
def carPooling(self, trips, capacity):
"""
:type trips: List[List[int]]
:type capacity: int
:rtype: bool
"""
if not trips:
return True
trips.sort(key=lambda item: item[1])
res = [False] * len(trips)
res[0], c = True, capacity - trips[0][0]
if c < 0:
return False
for i in range(1, len(trips)):
for j in range(0, i):
if res[j] and trips[i][1] >= trips[j][2]:
c += trips[j][0]
res[j] = False
if c - trips[i][0] < 0:
return False
c -= trips[i][0]
res[i] = True
return True
二:借用上题思路,和上题不同的是,在该站点前所有下车的乘客都会释放空位,故所以用循环来找出所有的空位量,而上题只要有一个空会议室就可以了,顾用if判断。
class Solution(object):
def carPooling(self, trips, capacity):
"""
:type trips: List[List[int]]
:type capacity: int
:rtype: bool
"""
if not trips:
return True
trips.sort(key=lambda item: item[1])
free_seats = []
heapq.heappush(free_seats, [trips[0][2], trips[0][0]])
c = capacity - trips[0][0]
if c < 0:
return False
for i in range(1, len(trips)):
while free_seats and trips[i][1] >= free_seats[0][0]:
c += heapq.heappop(free_seats)[1]
if c - trips[i][0] < 0:
return False
heapq.heappush(free_seats, [trips[i][2], trips[i][0]])
c -= trips[i][0]
return True
三:转leetcode上一位大佬的题解https://leetcode-cn.com/problems/car-pooling/solution/tong-ji-by-jerring/。
本题直接解答比较复杂,可以采用统计学的方法进行加加减减操作,可以很容易解答本题。
因为 0 <= trips[i][1] < trips[i][2] <= 1000,可以开一个大小为 1001 的数组 cnt 来代表每个地点的人数。
遍历 trips,在上车点加上对应人数,在下车点减去对应人数。
最终数组 cnt 每个位置的前缀和就是对应地点上的乘客数量,判断是否满足条件就比较简单了。
配上这位大神的就更好理解了,https://leetcode-cn.com/problems/car-pooling/solution/chao-jian-dan-si-lu-by-chen-wen-jie/,这道题关键是只要人数不超出座位数就行,然后我们就用一个数组来存储每个点的人数就是了,我真是个小机灵鬼^_^。
这道题有限制终点不会大于1000,我们就搞一个数组a[1001],用来存储每一个点的人数就行了。我举个栗子:(2,3,6)由于终点都是先下后上的所以需要终点人就下车,起点和终点前一个点都在车上,在a[3]到a[6-1]都需要+2,全部行程加完后,然后判断数组有没有出现超过座位的数字就行,时间复杂度O(n),是不是简单到爆又容易理解!!!
这一段的解释,因为只要人数不超出座位数就行,人数:我们数该路段上的车上的人数就行,因为前面在上车点加上对应人数,在下车点减去对应人数,故累加就是每个点上的人的数量。
for i in range(1, 1002):
cnt[i] += cnt[i-1]
if cnt[i] > capacity:
return False
class Solution(object):
def carPooling(self, trips, capacity):
if not trips:
return True
cnt = [0] * 10001
for trip in trips:
cnt[trip[1]] += trip[0]
cnt[trip[2]] -= trip[0]
if cnt[0] > capacity:
return False
for i in range(1, 1002):
cnt[i] += cnt[i-1]
if cnt[i] > capacity:
return False
# for trip in trips:
# if cnt[trip[1]] > capacity:
# return False
return True
452. 用最少数量的箭引爆气球
https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/submissions/
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。
一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
Example:输入:[[10,16], [2,8], [1,6], [7,12]],输出:2,解释:对于该样例,我们可以在x = 6(射爆[2,8],[1,6]两个气球)和 x = 11(射爆另外两个气球)。
思路
一:该题可以理解为,箭只能穿过终点,不能穿过起点,在有重叠的区域(交集)放箭,可以穿越所有重叠的气球的终点。其实与合并区间(并集)几乎完全一样,只不过合并区间,有重叠的区间,随着合并越来越大,此处,有重叠的区间(交集)随着遍历越来越小。
class Solution(object):
def findMinArrowShots(self, points):
"""
:type points: List[List[int]]
:rtype: int
"""
if not points:
return 0
points.sort(key=lambda item: item[0])
print(points)
cnt, start, end = 0, points[0][0], points[0][1]
for i in range(1, len(points)):
if points[i][0] <= end:
start = points[i][0]
end = min(end, points[i][1])
else:
cnt += 1
start, end = points[i]
return cnt + 1
二:参照合并区间的方法二
class Solution(object):
def findMinArrowShots(self, points):
if not points:
return 0
points.sort(key=lambda item: item[0])
cnt, start, end = 0, points[0][0], points[0][1]
for i in range(1, len(points)):
start = max(start, points[i][0])
end = min(end, points[i][1])
if start > end:
cnt += 1
start, end = points[i]
return cnt + 1
435. 无重叠区间
https://leetcode-cn.com/problems/non-overlapping-intervals/submissions/
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:可以认为区间的终点总是大于它的起点。区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
示例 1:输入: [ [1,2], [2,3], [3,4], [1,3] ],输出: 1,解释: 移除 [1,3] 后,剩下的区间没有重叠。
示例 2:输入: [ [1,2], [1,2], [1,2] ],输出: 2,解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
示例 3:输入: [ [1,2], [2,3] ],输出: 0
思路
一:变换一下思路,把最少删除变为最多保留多少个区间。采用动态规划,retain[i]表示以i区间结尾最多保留多少个区间,则需要再遍历[0,i)中与i不重合的区间,即i可以添加在这些区间之后,这些区间中取最大,再加1即可,最多保留区间个数是retain中的最大值。时间复杂度O(n^2)。
class Solution(object):
def eraseOverlapIntervals(self, intervals):
"""
:type intervals: List[List[int]]
:rtype: int
"""
if len(intervals) <= 1:
return 0
intervals.sort(key=lambda item: item[1])
retain = [1] * len(intervals)
for i in range(1, len(intervals)):
for j in range(0, i):
if intervals[i][0] >= intervals[j][1]:
retain[i] = max(retain[i], retain[j] + 1)
res = len(intervals) - max(retain)
return res
二:贪心算法,时间复杂度O(nlgn),主要是排序的时间复杂度。思路,按结束时间排序,结束时间早的在不与前面的冲突的情况下保留,若冲突不保留。合理性,反证法,若贪心法寻找的不是最终解,则必定存在一个最终解,且该最终解必定在贪心法所求解后结束,则能接上该解的必能接上贪心的解,故不影响后面,矛盾,前面的话,都是目前的可行解,也不影响,矛盾,故合理。
class Solution(object):
def eraseOverlapIntervals(self, intervals):
if len(intervals) <= 1:
return 0
intervals.sort(key=lambda item: item[1])
retain, pre = 1, 0
for i in range(1, len(intervals)):
if intervals[i][0] >= intervals[pre][1]:
retain += 1
pre = i
res = len(intervals) - retain
return res
125. 验证回文串
https://leetcode-cn.com/problems/valid-palindrome/
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。说明:本题中,我们将空字符串定义为有效的回文串。
示例 1:输入: "A man, a plan, a canal: Panama",输出: true
示例 2:输入: "race a car",输出: false
思路
一:没有预先处理字符串,利用对撞指针,每次从左往右l,从右往左r,寻找第一个字母或数字字符,看他们是否相等。
class Solution(object):
def isPalindrome(self, s):
"""
:type s: str
:rtype: bool
"""
if len(s) <= 1:
return True
l, r = 0, len(s) - 1
while l < r:
while l < r and not s[l].isalnum():
l += 1
while l < r and not s[r].isalnum():
r -= 1
if s[l].lower() != s[r].lower():
return False
l += 1
r -= 1
return True
二:先对字符串做了预处理,leetcode上提交貌似快了点。
import re
class Solution(object):
def isPalindrome(self, s):
if len(s) <= 1:
return True
s = re.sub("[^a-zA-Z0-9]", "", s)
l, r = 0, len(s) - 1
while l < r:
if s[l].lower() != s[r].lower():
return False
l += 1
r -= 1
return True
680. 验证回文字符串 Ⅱ
https://leetcode-cn.com/problems/valid-palindrome-ii/submissions/
给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。
示例 1:输入: "aba",输出: True
示例 2:输入: "abca",输出: True,解释: 你可以删除c字符。
注意:字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。
思路
一:暴力解法,对于给定字符串中的每个索 引i,我们删除该字符,然后检查结果字符串是否是回文。若是(或原始字符串是回文),那么我们将返回 true
。时间复杂度O(n^2)。
二:转自https://leetcode-cn.com/problems/valid-palindrome-ii/solution/yan-zheng-hui-wen-zi-fu-chuan-ii-by-leetcode/,shileetcode的官方题解,如果字符串的起始字符和结束字符相同(即 s[0]==s[s.length-1]),则内部字符是否为回文(s[1], s[2], ..., s[s.length - 2])将唯一地确定整个字符串是否为回文。
算法:
假设我们想知道 s[l],s[l+1],...,s[r] 是否形成回文。如果 l>= r,就结束判断。如果 s[l]=s[r],那么我们可以取 l++;r--。否则,回文必须是 s[l+1], s[l+2], ..., s[r] 或 s[l], s[l+1], ..., s[r-1] 这两种情况。
class Solution(object):
def validPalindrome(self, s):
"""
:type s: str
:rtype: bool
"""
if len(s) <= 2:
return True
l, r = 0, len(s) - 1
while l < r:
if s[l] == s[r]:
l += 1
r -= 1
else:
return self.isPalindrome(s, l + 1, r) or self.isPalindrome(s, l, r - 1)
return True
def isPalindrome(self, s, l, r):
if len(s) <= 1:
return True
while l < r:
if s[l].lower() != s[r].lower():
return False
l += 1
r -= 1
return True
344. 反转字符串
https://leetcode-cn.com/problems/reverse-string/
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
示例 1:输入:["h","e","l","l","o"],输出:["o","l","l","e","h"]
示例 2:输入:["H","a","n","n","a","h"],输出:["h","a","n","n","a","H"]
思路
一:双指针法,使用两个指针,一个左指针 l,右指针 r,开始工作时 l指向首元素,r 指向尾元素。交换两个指针指向的元素,并向中间移动,直到两个指针相遇。
class Solution(object):
def reverseString(self, s):
"""
:type s: List[str]
:rtype: None Do not return anything, modify s in-place instead.
"""
if len(s) <= 1:
return s
i, j = 0, len(s) - 1
while i < j:
s[i], s[j] = s[j], s[i]
i += 1
j -= 1
return s
345. 反转字符串中的元音字母
https://leetcode-cn.com/problems/reverse-vowels-of-a-string/
编写一个函数,以字符串作为输入,反转该字符串中的元音字母。
示例 1:输入: "hello",输出: "holle"
示例 2:输入: "leetcode",输出: "leotcede"
说明:元音字母不包含字母"y"。
思路
一:依旧是双指针法,遇到元音字母就交换
class Solution(object):
def reverseVowels(self, s):
"""
:type s: str
:rtype: str
"""
vowel = "aeiouAEIOU"
s_l = list(s)
l, r =0, len(s_l) - 1
while l < r:
while l < r and s_l[l] not in vowel:
l += 1
while l < r and s_l[r] not in vowel:
r -= 1
s_l[l], s_l[r] = s_l[r], s_l[l]
l += 1
r -= 1
return "".join(s_l)
1119. 删去字符串中的元音
https://leetcode-cn.com/problems/remove-vowels-from-a-string/submissions/
给你一个字符串 S,请你删去其中的所有元音字母( 'a','e','i','o','u'),并返回这个新字符串。
示例 1:输入:"leetcodeisacommunityforcoders",输出:"ltcdscmmntyfrcdrs"
示例 2:输入:"aeiou",输出:""
思路
一:一次遍历,遇到元音跳过
class Solution(object):
def removeVowels(self, S):
"""
:type S: str
:rtype: str
"""
vowel ="aeiou"
s = ""
for letter in S:
if letter not in vowel:
s += letter
return s
二:正则
class Solution(object):
def removeVowels(self, S):
s = re.sub("[aeiou]", "", S)
return s