week14:
文章目录
- week14:
- Easy:
- Medium:
- [435. 无重叠区间](https://leetcode.cn/problems/non-overlapping-intervals/)
- [452. 用最少数量的箭引爆气球](https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/)
- [763. 划分字母区间](https://leetcode.cn/problems/partition-labels/)
- [406. 根据身高重建队列](https://leetcode.cn/problems/queue-reconstruction-by-height/)
- [665. 非递减数列](https://leetcode.cn/problems/non-decreasing-array/)
- [167. 两数之和 II - 输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/)
- [142. 环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/)
- Hard:
Easy:
455. 分发饼干
题目描述:
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i
,都有一个胃口值 g[i]
,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j
,都有一个尺寸 s[j]
。如果 s[j] >= g[i]
,我们可以将这个饼干 j
分配给孩子 i
,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例 1:
输入: g = [1,2,3], s = [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
题解:贪心
这里的贪心策略是,给剩余孩子里最小饥饿度的孩子分配最小的能饱腹的饼干。至于具体实现,因为我们需要获得大小关系,一个便捷的方法就是把孩子和饼干分别排序。这样我们就可以从饥饿度最小的孩子和大小最小的饼干出发,计算有多少个对子可以满足条件。
class Solution:
def findContentChildren(self, g: List[int], s: List[int]) -> int:
g.sort() # 对小孩的胃口值进行排序
s.sort() # 对饼干的大小进行排序
child = cookie = 0 # 初始化小孩数和饼干数为0
while child < len(g) and cookie < len(s): # 循环直到遍历完所有小孩或饼干
if g[child] <= s[cookie]: # 如果当前饼干能满足当前小孩的胃口
child += 1 # 小孩数加一
cookie += 1 # 饼干数加一
return child # 返回能满足的小孩数
605. 种花问题
题目描述:
假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。
给你一个整数数组 flowerbed
表示花坛,由若干 0
和 1
组成,其中 0
表示没种植花,1
表示种植了花。另有一个数 n
,能否在不打破种植规则的情况下种入 n
朵花?能则返回 true
,不能则返回 false
。
示例 1:
输入:flowerbed = [1,0,0,0,1], n = 1
输出:true
题解:贪心
采取什么样的贪心策略,可以种植最多的花朵呢?
判断能否在不打破种植规则的情况下在花坛内种入 n
朵花,从贪心的角度考虑,应该在不打破种植规则的情况下种入尽可能多的花,然后判断可以种入的花的最多数量是否大于或等于 n
。
class Solution:
def canPlaceFlowers(self, flowerbed: List[int], n: int) -> bool:
# 获取花坛长度
n_l = len(flowerbed)
# 如果花坛长度小于3且全部为0,并且需要种植的花朵数量小于花坛长度,那么可以种植
if n_l < 3 and sum(flowerbed) == 0 and n < n_l:
return True
# 如果需要种植的花朵数量为0,那么可以种植
if n == 0:
return True
# 遍历花坛
for i in range(0, n_l):
if i == 0:
# 如果是花坛的第一个位置,判断该位置和下一个位置是否都为0,如果是,则可以种植花朵
if sum(flowerbed[0:2]) == 0:
n -= 1
flowerbed[i] = 1
elif i == n_l - 1:
# 如果是花坛的最后一个位置,判断该位置和前一个位置是否都为0,如果是,则可以种植花朵
if sum(flowerbed[n_l - 2:n_l]) == 0:
n -= 1
elif flowerbed[i] + flowerbed[i-1] + flowerbed[i+1] == 0:
# 如果当前位置及其相邻位置都为0,那么可以种植花朵
n -= 1
flowerbed[i] = 1
# 如果已经种植足够数量的花朵,返回True
if n == 0:
return True
# 如果遍历结束后还没有种植足够数量的花朵,返回False
return False
class Solution:
def canPlaceFlowers(self, flowerbed: List[int], n: int) -> bool:
n_l = len(flowerbed)
if n_l < 3 and sum(flowerbed) == 0 and n < n_l:
return True
if n == 0:
return True
# 从左往右遍历花坛。
for i in range(n_l):
# 如果当前位置可以种花,则种下花并更新 n。
if flowerbed[i] == 0 and (i == 0 or flowerbed[i - 1] == 0) and (i == n_l - 1 or flowerbed[i + 1] == 0):
flowerbed[i] = 1
n -= 1
if n == 0:
return True
return False
1572. 矩阵对角线元素的和
题目描述:
给你一个正方形矩阵 mat
,请你返回矩阵对角线元素的和。
请你返回在矩阵主对角线上的元素和副对角线上且不在主对角线上元素的和。
示例 1:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yh4gMD39-1691935693035)(https://assets.leetcode.com/uploads/2020/08/14/sample_1911.png)]
输入:mat = [[1,2,3],
[4,5,6],
[7,8,9]]
输出:25
解释:对角线的和为:1 + 5 + 9 + 3 + 7 = 25
请注意,元素 mat[1][1] = 5 只会被计算一次。
题解:全遍历
class Solution:
def diagonalSum(self, mat: List[List[int]]) -> int:
n = len(mat)
res = 0
# 遍历所有元素。
for i in range(n):
for j in range(n):
# 如果元素在对角线上,则将其加入结果中。
if i + j == n - 1 or i == j:
res += mat[i][j]
return res
满足条件的i + j == n - 1 or i == j
题解:枚举行
class Solution:
def diagonalSum(self, mat: List[List[int]]) -> int:
n = len(mat)
res = 0
mid = n // 2
# 遍历对角线。
for i in range(mid + 1):
res += mat[i][i] + mat[n - i - 1][n - i - 1]
# 如果矩阵为奇数,则中间元素只会被加一次,需要减去一次。
return res - mat[mid][mid] * (n % 2)
满足条件的是 [ i , i ] , [ i , n − 1 − i ] [i, i],[i, n-1-i] [i,i],[i,n−1−i]
141. 环形链表
题目描述:
给你一个链表的头节点 head
,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true
。 否则,返回 false
。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
题解:快慢指针
对于链表找环路的问题,有一个通用的解法——快慢指针(Floyd 判圈法)。给定两个指针,分别命名为slow 和fast,起始位置在链表的开头。每次fast 前进两步,slow 前进一步。如果fast可以走到尽头,那么说明没有环路;如果fast 可以无限走下去,那么说明一定有环路,且一定存在一个时刻slow 和fast 相遇。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
Medium:
435. 无重叠区间
题目描述:
给定一个区间的集合 intervals
,其中 intervals[i] = [starti, endi]
。返回 需要移除区间的最小数量,使剩余区间互不重叠 。
示例 1:
输入: intervals = [[1,2],[2,3],[3,4],[1,3]]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。
题解:贪心
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
n = len(intervals)
# 如果区间数量小于等于0,无需删除任何区间
if n <= 0:
return 0
# 按照区间的末尾元素进行排序
intervals.sort(key=lambda x: x[-1])
total = 0
prev = intervals[0][1] # 记录当前区间的末尾
# 从第二个区间开始遍历
for i in range(1, n):
if intervals[i][0] < prev:
# 如果当前区间的起始元素小于上一个区间的末尾元素,则需要删除当前区间
total += 1
else:
# 否则更新prev为当前区间的末尾元素
prev = intervals[i][1]
return total
在选择要保留区间时,区间的结尾十分重要:选择的区间结尾越小,余留给其它区间的空间就越大,就越能保留更多的区间。因此,我们采取的贪心策略为,优先保留结尾小且不相交的区间。
452. 用最少数量的箭引爆气球
题目描述:
有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points
,其中
p
o
i
n
t
s
[
i
]
=
[
X
s
t
a
r
t
,
X
e
n
d
]
points[i] = [X_{start}, X_{end}]
points[i]=[Xstart,Xend] 表示水平直径在
X
s
t
a
r
t
X_{start}
Xstart和之
X
e
n
d
X_{end}
Xend间的气球。你不知道气球的确切 y 坐标。
一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x
处射出一支箭,若有一个气球的直径的开始和结束坐标为
X
s
t
a
r
t
X_{start}
Xstart,
X
e
n
d
X_{end}
Xend, 且满足
X
s
t
a
r
t
≤
X
≤
x
e
n
d
X_{start}\leq X \leq x_{end}
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]。
题解:贪心
按照排序后,当前气球的最右端位置大于下一个气球的最左端,则两个气球含有重叠的区域。
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
n = len(points)
points.sort(key=lambda x: x[1])
total = 1
prev = points[0][1]
for i in range(1, n):
# 如果当前区间与前一个区间没有重叠,则需要增加一个箭矢。
if prev >= points[i][0]:
continue
else:
prev = points[i][1]
total += 1
return total
763. 划分字母区间
题目描述:
给你一个字符串 s
。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s
。
返回一个表示每个字符串片段的长度的列表。
示例 1:
输入:s = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。
题解:贪心
当需要将字符串划分为尽可能多的不重叠子串,使得每个子串内的字符都只在该子串内出现时,我们可以使用贪心算法来解决这个问题。
观察事例,前两个子串的结尾字符都是该子串的最多字符且是该字符出现的最后一个位置,第三个字串的结尾时该字符出现的最后一个位置,无法再分。
所以在进行贪心策略前要记录所有字符最后出现的位置。
class Solution:
def partitionLabels(self, s: str) -> List[int]:
# 创建一个字典,记录每个字符在字符串中最后出现的位置
last_occurrence = {ch: i for i, ch in enumerate(s)}
result = [] # 存储每个子串的长度
start = end = 0 # 初始化指针
# 遍历字符串
for i, ch in enumerate(s):
end = max(end, last_occurrence[ch]) # 更新 end 指针
if i == end:
result.append(end - start + 1) # 找到一个满足条件的子串,添加到结果列表中
start = end + 1 # 更新 start 指针以查找下一个子串
return result
406. 根据身高重建队列
题目描述:
假设有打乱顺序的一群人站成一个队列,数组 people
表示队列中一些人的属性(不一定按顺序)。每个$ people[i] = [h_i, k_i$] 表示第 i
个人的身高为
h
i
h_i
hi ,前面 正好 有
k
i
k_i
ki 个身高大于或等于
h
i
h_i
hi 的人。
请你重新构造并返回输入数组 people
所表示的队列。返回的队列应该格式化为数组 queue
,其中
q
u
e
u
e
[
j
]
=
[
h
j
,
k
j
]
queue[j] = [h_j, k_j]
queue[j]=[hj,kj] 是队列中第 j
个人的属性(queue[0]
是排在队列前面的人)。
示例 1:
输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。
题解:贪心
这个问题可以用贪心算法来解决。首先按照身高从小到大排序,身高相同的人按照前面有多少人比他高从小到大排序。然后从后往前遍历队列。对于每个人,前方有pe[1]个比pe[0]高的人。从前往后扫描数组,如果该位置为空,可以插入比pe[0]高的人。重复这个过程,直到遍历完所有人。
class Solution:
def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
# 按照身高从小到大排序,身高相同的人按照前面有多少人比他高从小到大排序。
people.sort(key=lambda x: (x[0], -x[1]))
# 构建结果数组。
ans = [[] for i in range(len(people))]
# 从后往前遍历队列。
for pe in people:
# 前方有pe[1]个比pe[0]高的人。
spaces = pe[1] + 1
# 从前往后扫描数组。
for i in range(len(ans)):
# 如果该位置为空,可以插入比pe[0]高的人。
if not ans[i]:
spaces -= 1
if spaces == 0:
# 找到位置,插入pe。
ans[i] = pe
break
return ans
665. 非递减数列
题目描述:
给你一个长度为 n
的整数数组 nums
,请你判断在 最多 改变 1
个元素的情况下,该数组能否变成一个非递减数列。
我们是这样定义一个非递减数列的: 对于数组中任意的 i
(0 <= i <= n-2)
,总满足 nums[i] <= nums[i + 1]
。
示例 1:
输入: nums = [4,2,3]
输出: true
解释: 你可以通过把第一个 4 变成 1 来使得它成为一个非递减数列。
题解:数组处理
class Solution:
def checkPossibility(self, nums: List[int]) -> bool:
n = len(nums)
# 记录改变数的次数。
cnt = 0
for i in range(1, n):
# 保存当前数字和前一个数字。
x = nums[i - 1]
y = nums[i]
# 如果当前数字小于前一个数字,则需要修改数字。
if x > y:
# 修改次数加 1。
cnt += 1
# 如果修改次数已经超过 1,则返回 False。
if cnt > 1:
return False
# 如果当前数字小于前两个数字,则将当前数字修改为前一个数字。
if i > 1 and y < nums[i - 2]:
nums[i] = x
return True
成立的应该是 n u m s [ i − 2 ] ≤ n u m s [ i − 1 ] ≤ n u m s [ i ] nums[i-2]\leq nums[i-1]\leq nums[i] nums[i−2]≤nums[i−1]≤nums[i]
当
n
u
m
s
[
i
−
2
]
>
n
u
m
s
[
i
]
nums[i-2] > nums[i]
nums[i−2]>nums[i]比如:
[
4
,
9
,
2
]
→
[
4
,
9
,
9
]
[4, 9, 2]\rightarrow[4,9,9]
[4,9,2]→[4,9,9] 看了一下大佬的解题思路,不是这种情况的就直接cnt += 1
, 肯定是不过的。
167. 两数之和 II - 输入有序数组
题目描述:
给你一个下标从 1 开始的整数数组 numbers
,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target
的两个数。如果设这两个数分别是 numbers[index1]
和 numbers[index2]
,则 1 <= index1 < index2 <= numbers.length
。
以长度为 2 的整数数组 [index1, index2]
的形式返回这两个整数的下标 index1
和 index2
。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
示例 1:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
题解:双指针
很基础的双指针应用
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
n = len(numbers) # 获取长度
left, right = 0, n - 1 # 左右指针
while left < right:
num = numbers[left] + numbers[right]
if num == target:
return [left+1, right+1]
elif num < target: # 过小
left += 1
else: # 过大
right -= 1
142. 环形链表 II
题目描述:
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
题解:快慢指针
对于链表找环路的问题,有一个通用的解法——快慢指针(Floyd 判圈法)。给定两个指针,分别命名为slow 和fast,起始位置在链表的开头。每次fast 前进两步,slow 前进一步。如果fast可以走到尽头,那么说明没有环路;如果fast 可以无限走下去,那么说明一定有环路,且一定存在一个时刻slow 和fast 相遇。当slow 和fast 第一次相遇时,我们将fast 重新移动到链表开头,并让slow 和fast 每次都前进一步。当slow 和fast 第二次相遇时,相遇的节点即为环路的开始点。
while语句用于循环执行一系列语句,直到满足某个条件。else语句用于执行在while循环结束时执行的语句。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
fast,slow = head, head # 双指针
while fast and fast.next: # 第一次跑
slow = slow.next
fast = fast.next.next
if slow == fast: # 相遇
break
else: # 无环
return None
# 有环,找入口
fast = head
while fast != slow:
fast = fast.next
slow = slow.next
return fast
Hard:
135. 分发糖果
题目描述:
n
个孩子站成一排。给你一个整数数组 ratings
表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到
1
个糖果。 - 相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
示例 1:
输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。
题解:贪心
首先,根据给定人的评分,初始化每个人的初始糖果数为1。
从左到右遍历一次,如果当前人的评分比前一个人高,则分配更多的糖果给当前人,数量为前一个人的糖果数加一。
然后,从右到左遍历一次,如果前一个人的评分比当前人高,则分配更多的糖果给前一个人,数量取当前情况和加一的情况中的最大值。
最后,返回所需糖果的总数,即所有人的糖果数之和。
这样,就能得到最少需要多少个糖果。
class Solution:
def candy(self, ratings: List[int]) -> int:
n = len(ratings) # 人数
if n < 2: # 特殊情况处理:只有一个人或没有人
return n
num = [1] * n # 初始化每个人的初始糖果数为1
for i in range(1, n):
if ratings[i] > ratings[i - 1]: # 如果当前人的评分比前一个人高
num[i] = num[i-1] + 1 # 分配更多的糖果给当前人
for i in range(n-1, 0, -1):
if ratings[i - 1] > ratings[i]: # 如果前一个人的评分比当前人高
num[i - 1] = max(num[i-1], num[i] + 1) # 分配更多的糖果给前一个人,取当前情况和加一的情况中最大的值
return sum(num) # 返回所需糖果的总数
。
这样,就能得到最少需要多少个糖果。
class Solution:
def candy(self, ratings: List[int]) -> int:
n = len(ratings) # 人数
if n < 2: # 特殊情况处理:只有一个人或没有人
return n
num = [1] * n # 初始化每个人的初始糖果数为1
for i in range(1, n):
if ratings[i] > ratings[i - 1]: # 如果当前人的评分比前一个人高
num[i] = num[i-1] + 1 # 分配更多的糖果给当前人
for i in range(n-1, 0, -1):
if ratings[i - 1] > ratings[i]: # 如果前一个人的评分比当前人高
num[i - 1] = max(num[i-1], num[i] + 1) # 分配更多的糖果给前一个人,取当前情况和加一的情况中最大的值
return sum(num) # 返回所需糖果的总数