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。
示例 2:
- 输入: g = [1,2], s = [1,2,3]
- 输出: 2
- 解释:你有两个孩子和三块小饼干,2 个孩子的胃口值分别是 1,2。你拥有的饼干数量和尺寸都足以让所有孩子满足。所以你应该输出 2.
提示:
- 1 <= g.length <= 3 * 10^4
- 0 <= s.length <= 3 * 10^4
- 1 <= g[i], s[j] <= 2^31 - 1
这个问题的核心是:如何有效地将饼干分配给孩子,以使尽可能多的孩子满足。
贪心策略:
我们可以采用贪心策略来解决这个问题。具体来说,我们希望每次都用最小的饼干去满足最小胃口的孩子。这样,更大的饼干就可以留给胃口更大的孩子。这种策略可以确保我们不会浪费饼干(例如,用一个大饼干去满足一个小胃口的孩子)。
具体步骤:
-
排序:首先,我们需要对孩子的胃口值数组
g
和饼干尺寸数组s
进行升序排序。这样我们可以从最小的胃口和最小的饼干开始匹配。 -
双指针遍历:使用两个指针,一个指向孩子数组
g
,另一个指向饼干数组s
。 -
匹配饼干:遍历饼干数组
s
,对于每一块饼干,检查它是否可以满足当前指向的孩子的胃口值:- 如果可以(即
s[cookie] >= g[child]
),则将这块饼干分给这个孩子,并将孩子的指针向后移动一位,表示这个孩子已经得到了饼干并满足了。 - 无论这块饼干是否满足了当前孩子,我们都将饼干的指针向后移动一位,继续尝试下一块饼干。
- 如果可以(即
-
结束条件:当饼干数组
s
遍历完或孩子数组g
遍历完时,结束循环。 -
返回结果:最后,返回满足的孩子数量,即孩子指针当前的位置。
这种方法的优点是它可以确保我们不会浪费任何饼干,并且可以使尽可能多的孩子满足。时间复杂度主要取决于排序,为 O(nlogn),其中 n 是 g
和 s
中较长的那个数组的长度。空间复杂度为 O(1),因为我们只使用了常数级别的额外空间。
from typing import List
class Solution:
def findContentChildren(self, g: List[int], s: List[int]) -> int:
# 1. 对孩子的胃口值和饼干尺寸进行排序
g.sort()
s.sort()
# 2. 初始化两个指针:child 指向孩子数组,cookie 指向饼干数组
child, cookie = 0, 0
# 3. 遍历饼干数组
while child < len(g) and cookie < len(s):
# 如果当前饼干可以满足当前孩子的胃口
if s[cookie] >= g[child]:
# 将这块饼干分给这个孩子
child += 1
# 无论这块饼干是否满足了当前孩子,都尝试下一块饼干
cookie += 1
# 4. 返回满足的孩子数量
return child
# 测试
solution = Solution()
print(solution.findContentChildren([1,2,3], [1,1])) # 输出: 1
print(solution.findContentChildren([1,2], [1,2,3])) # 输出: 2
376. 摆动序列
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
示例 1:
- 输入: [1,7,4,9,2,5]
- 输出: 6
- 解释: 整个序列均为摆动序列。
示例 2:
- 输入: [1,17,5,10,13,15,10,5,16,8]
- 输出: 7
- 解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为[1,17,10,13,10,16,8]。
示例 3:
- 输入: [1,2,3,4,5,6,7,8,9]
- 输出: 2
核心问题:
给定一个数字序列,我们要找出其中的最长摆动子序列。摆动子序列的特点是它的数字是上升和下降交替的。
思路:
-
初始化摆动序列长度:我们首先假设整个序列至少有一个数字,所以摆动序列的初始长度为1。
-
遍历序列:从第二个数字开始,我们遍历整个序列,比较当前数字和前一个数字的关系。
-
寻找转折点:转折点是摆动序列中的关键。每当我们从上升趋势转到下降趋势,或从下降趋势转到上升趋势时,我们找到了一个转折点。
-
如果当前数字大于前一个数字,并且前一个趋势是下降或者是序列的开始,那么我们找到了一个转折点。摆动序列的长度增加1,并将当前趋势设置为上升。
-
如果当前数字小于前一个数字,并且前一个趋势是上升或者是序列的开始,那么我们同样找到了一个转折点。摆动序列的长度增加1,并将当前趋势设置为下降。
-
-
跳过平坦区域:如果当前数字和前一个数字相等,我们不需要做任何操作,直接继续遍历。
-
返回结果:遍历完整个序列后,返回摆动序列的长度。
这个思路有效的原因是我们只关心摆动序列中的转折点。每个转折点都代表了一个摆动的机会,使摆动序列变得更长。而连续的上升或下降并不增加摆动序列的长度,所以我们可以忽略它们,只关心转折点。
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
# 如果序列长度小于2,直接返回序列长度
if len(nums) < 2:
return len(nums)
# 定义三种可能的状态:开始、上升、下降
BEGIN = 0
UP = 1
DOWN = 2
# 初始状态为 BEGIN
state = BEGIN
# 摆动序列的初始长度为1
maxLength = 1
# 从第二个数字开始遍历整个序列
for i in range(1, len(nums)):
# 如果当前状态是 BEGIN
if state == BEGIN:
# 根据与前一个数字的比较,确定下一个状态
if nums[i-1] < nums[i]:
state = UP
maxLength += 1
elif nums[i-1] > nums[i]:
state = DOWN
maxLength += 1
# 如果当前状态是 UP
elif state == UP:
# 寻找下一个小于当前数字的数字,这是一个转折点
if nums[i-1] > nums[i]:
state = DOWN
maxLength += 1
# 如果当前状态是 DOWN
elif state == DOWN:
# 寻找下一个大于当前数字的数字,这是一个转折点
if nums[i-1] < nums[i]:
state = UP
maxLength += 1
# 返回摆动序列的长度
return maxLength
53. 最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
- 输入: [-2,1,-3,4,-1,2,1,-5,4]
- 输出: 6
- 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
这个问题的关键是找到一个连续的子数组,使得它的和最大。我们可以通过动态规划的方法来解决这个问题。
首先,我们初始化两个变量,curSum
和 maxSum
,都设置为数组的第一个元素。curSum
用来记录当前连续子数组的和,maxSum
用来记录最大的连续子数组的和。
然后,我们遍历数组中剩余的元素。对于每一个元素 num
,我们比较 num
和 curSum + num
,取其中较大的一个作为新的 curSum
。这样做的目的是看看我们是否应该开始一个新的连续子数组(如果 num > curSum + num
),还是应该将 num
加入到当前的连续子数组中(如果 num <= curSum + num
)。
在每一步中,我们都会更新 maxSum
,使其始终保持为我们见过的最大连续子数组的和。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
if not nums:
return 0
curSum = maxSum = nums[0]
for num in nums[1:]:
curSum = max(num, curSum + num)
maxSum = max(maxSum, curSum)
return maxSum