题目二:最大子序列交替和
给你一个下标从 0 开始的整数数组 nums ,返回该数组中 最大 的子序列交替和。
子序列指的是从原数组中删除一些元素(可能一个也不删除)后剩余元素不改变顺序组成的序列。交替和指的是一个子序列中相邻元素之差的绝对值之和。
思路和算法
这道题可以用动态规划的方法来解决。我们可以定义两个状态 dp[i][0] 和 dp[i][1] ,分别表示以 nums[i] 结尾的子序列交替和的最大值,其中 dp[i][0] 表示该子序列最后两个元素是递增的,dp[i][1] 表示该子序列最后两个元素是递减的。初始时,dp[0][0] = dp[0][1] = nums[0] ,表示只有一个元素的子序列。
然后,我们可以从 i = 1 开始遍历数组,对于每个 i ,我们有以下两种情况:
- 如果 nums[i] > nums[i - 1] ,说明我们可以将 nums[i] 加入到以 nums[i - 1] 结尾且最后两个元素是递减的子序列中,使得交替和增加 nums[i] - nums[i - 1] ,即 dp[i][0] = dp[i - 1][1] + nums[i] - nums[i - 1] 。同时,我们也可以将 nums[i] 单独作为一个新的子序列,即 dp[i][0] = max(dp[i][0], nums[i]) 。
- 如果 nums[i] < nums[i - 1] ,说明我们可以将 nums[i] 加入到以 nums[i - 1] 结尾且最后两个元素是递增的子序列中,使得交替和增加 nums[i - 1] - nums[i] ,即 dp[i][1] = dp[i - 1][0] + nums[i - 1] - nums[i] 。同时,我们也可以将 nums[i] 单独作为一个新的子序列,即 dp[i][1] = max(dp[i][1], nums[i]) 。
- 如果 nums[i] == nums[i - 1] ,说明我们不能将 nums[i] 加入到任何一个以 nums[i - 1] 结尾的子序列中,因为这样会使得交替和不变。因此,我们只能将 nums[i] 单独作为一个新的子序列,即 dp[i][0] = dp[i][1] = max(dp[i][0], dp[i][1], nums[i]) 。
最终,我们可以返回所有状态中的最大值作为答案。
代码
class Solution:
def maxAlternatingSum(self, nums: List[int]) -> int:
# 初始化动态规划数组
n = len(nums)
dp = [[0, 0] for _ in range(n)]
dp[0][0] = dp[0][1] = nums[0]
# 遍历数组更新状态
for i in range(1, n):
if nums[i] > nums[i - 1]:
dp[i][0] = max(dp[i - 1][1] + nums[i] - nums[i - 1], nums[i])
dp[i][1] = dp[i - 1][1]
elif nums[i] < nums[i - 1]:
dp[i][0] = dp[i - 1][0]
dp[i][1] = max(dp[i - 1][0] + nums[i - 1] - nums[i], nums[i])
else:
dp[i][0] = max(dp[i - 1][0], dp [i - 1][1], nums[i])
dp[i][1] = max(dp[i - 1][0], dp [i - 1][1], nums[i])
# 返回最大的交替和
return max(max(row) for row in dp)
题目三:最大平均通过率
给你一个 school 数组,数组中包含 n 个对象,每个对象包含两个属性:id 和 pass 。id 表示学校的编号(从 0 到 n - 1),pass 表示在这所学校中通过考试的学生人数。数组中每个对象的 id 都是不同的。
同时给你一个参数 extraStudents ,表示额外有多少名学生可以去参加考试。你需要给这 extraStudents 名学生分配去参加考试的学校,使得 所有 学校的 平均 通过率 最大 。
如果学校的通过率等于通过人数除以总人数(即 pass / total ),那么平均通过率为所有学校的通过率之和除以学校数目(即 ∑passi/totali / n )。
请你返回在分配学生后,最大可能的平均通过率。与标准答案误差范围在 10-5 以内的结果都会视为正确结果。
思路和算法
这道题可以用贪心和最大堆的方法来解决。我们可以观察到,对于任意一所学校,如果有一名额外的学生去参加考试,那么它的通过率的增量为 (pass + 1) / (total + 1) - pass / total = 1 / (total * (total + 1)) 。这个增量只与 total 有关,而与 pass 无关。也就是说,对于 total 较小的学校,安排额外的学生去参加考试会使得通过率增加更多。
因此,我们可以用一个最大堆来维护所有学校的通过率增量,每次从堆中取出最大的增量,并将对应的学校的通过人数和总人数都加一,然后将更新后的增量重新放入堆中。我们重复这个过程 extraStudents 次,就可以得到分配后的所有学校的通过率。最后,我们将所有学校的通过率相加并除以 n ,就可以得到平均通过率。
代码
import heapq
class Solution:
def maxAverageRatio(self, classes: List[List[int]], extraStudents: int) -> float:
# 定义计算增量的函数
def getIncrement(passed, total):
return (passed + 1) / (total + 1) - passed / total
# 初始化最大堆
heap = []
for passed, total in classes:
increment = getIncrement(passed, total)
heapq.heappush(heap, (-increment, passed, total))
# 分配额外的学生
for _ in range(extraStudents):
increment, passed, total = heapq.heappop(heap)
passed += 1
total += 1
increment = getIncrement(passed, total)
heapq.heappush(heap, (-increment, passed, total))
# 计算平均通过率
average = 0
for increment, passed, total in heap:
average += passed / total
return average / len(classes)
题目四:最小化操作次数使数组和相等
给你一个长度为 n 的整数数组 nums ,每次操作你可以选择 nums 中的任意一个元素并将它改变为任意值。
如果 nums 中的所有元素都相等,那么数组和为 n * nums[0] 。给你一个整数 goal ,请你返回使数组和为 goal 所需的 最少 操作次数。
思路和算法
这道题可以用数学的方法来解决。我们可以观察到,对于任意一个元素 nums[i] ,如果我们将它改变为 x ,那么数组和的变化量为 x - nums[i] 。因此,如果我们想要使数组和变为 goal ,那么所有元素的变化量之和必须等于 goal - sum(nums) ,其中 sum(nums) 表示数组中所有元素的和。
我们可以将所有元素的变化量分为两类:正数和负数。正数表示将元素增大,负数表示将元素减小。我们可以发现,每次操作只能改变一个元素,因此操作次数等于所有元素的变化量的绝对值之和。也就是说,我们需要将所有正数的变化量之和加上所有负数的变化量之和的绝对值。
为了使操作次数最小,我们需要尽可能地平衡正数和负数的变化量,使得它们的差值最小。具体地,我们可以先求出所有元素的变化量之和 delta ,然后将 delta 除以 n 得到商 q 和余数 r 。这样,我们可以将 delta 均匀地分配给 n 个元素,使得其中 r 个元素的变化量为 q + 1 ,其余 n - r 个元素的变化量为 q 。这样做可以保证正数和负数的变化量之差最小,即 abs(r - (n - r)) 或 abs(2 * r - n) 。
最后,我们可以计算出操作次数为 abs(delta) + abs(2 * r - n) / 2 ,返回这个结果即可。
代码
class Solution:
def minOperations(self, nums: List[int], goal: int) -> int:
# 计算数组和
total = sum(nums)
# 计算所有元素的变化量之和
delta = goal - total
# 计算商和余数
q, r = divmod(delta, len(nums))
# 计算操作次数
return abs(delta) + abs(2 * r - len(nums)) // 2