你好呀!我是 山顶风景独好
欢迎踏入我的博客世界,能与您在此邂逅,真是缘分使然!
愿您在此停留的每一刻,都沐浴在轻松愉悦的氛围中。
这里不仅有丰富的知识和趣味横生的内容等您来探索,更是一个自由交流的平台,期待您留下独特的思考与见解。
让我们一起踏上这段探索与成长的旅程,携手挖掘更多可能,共同进步!✨

题目一:最大平均值和的分组(LeetCode 410,困难)

题目分析

给定数组 nums 和整数 k,将数组分成 k 个非空连续子数组,求这 k 个子数组平均值的总和最大值。数组长度 n 满足 1 ≤ k ≤ n ≤ 1000。例如:输入 nums = [9,1,2,3,9], k = 3,输出 20.0(分组为 [9]、[1,2,3]、[9],平均值和为 9 + 2 + 9 = 20);输入 nums = [1,2,3,4,5,6,7], k = 4,输出 20.5(分组为 [1,2,3,4]、[5]、[6]、[7],平均值和为 2.5 + 5 + 6 + 7 = 20.5)。

解题思路(动态规划+前缀和优化)

核心是通过前缀和快速计算子数组和,结合动态规划状态转移求解“前 i 个元素分 j 组的最大平均值和”:

  1. 前缀和预处理
    计算前缀和数组 pre_sum,其中 pre_sum[i] = sum(nums[0..i-1]),则子数组 nums[l..r] 的和为 pre_sum[r+1] - pre_sum[l],查询时间复杂度降至 O(1)
  2. 动态规划状态定义
    • dp[i][j]:表示将前 i 个元素分成 j 组的最大平均值和;
    • 初始状态:dp[i][1] = pre_sum[i]/i(前 i 个元素分1组,平均值为总和除以长度)。
  3. 状态转移逻辑
    要计算 dp[i][j],需枚举上一组的结束位置 mj-1 ≤ m < i),则:
    dp[i][j] = max(dp[m][j-1] + (pre_sum[i] - pre_sum[m])/(i - m))
    即“上一组的最大平均值和 + 当前组的平均值”的最大值。

示例代码

def largestSumOfAverages(nums, k) -> float:
n = len(nums)
# 前缀和数组:pre_sum[i] = sum(nums[0..i-1])
pre_sum = [0] * (n + 1)
for i in range(n):
pre_sum[i + 1] = pre_sum[i] + nums[i]
# dp[i][j]:前i个元素分j组的最大平均值和
dp = [[0.0] * (k + 1) for _ in range(n + 1)]
# 初始化:分1组的情况
for i in range(1, n + 1):
dp[i][1] = pre_sum[i] / i
# 填充dp表:j从2到k,i从j到n(至少j个元素才能分j组)
for j in range(2, k + 1):
for i in range(j, n + 1):
# 枚举上一组结束位置m
for m in range(j - 1, i):
current = dp[m][j - 1] + (pre_sum[i] - pre_sum[m]) / (i - m)
if current > dp[i][j]:
dp[i][j] = current
return dp[n][k]
# 测试示例
print("最大平均值和1:", largestSumOfAverages([9,1,2,3,9], 3))  # 输出:20.0
print("最大平均值和2:", largestSumOfAverages([1,2,3,4,5,6,7], 4))  # 输出:20.5
print("最大平均值和3:", largestSumOfAverages([1,2], 2))  # 输出:3.0

代码解析

  • 前缀和的高效性:将子数组和计算从 O(n) 优化到 O(1),大幅提升状态转移效率;
  • 状态转移的合理性:通过枚举上一组结束位置,确保每组均为连续子数组,符合题目要求;
  • 复杂度说明:时间复杂度 O(kn²),空间复杂度 O(nk),在 n=1000k=100 场景下可高效运行,是该问题的经典解法。

题目二:拼接最大数(LeetCode 321,困难)

题目分析

给定两个数组 nums1(长度 m)和 nums2(长度 n),返回由两数组元素组成的长度为 k最小字典序序列(元素需保持原数组相对顺序)。例如:输入 nums1 = [3,4,6,5], nums2 = [9,1,2,5,8,3], k = 5,输出 [1,2,3,3,4];输入 nums1 = [2,5,6,4,4,0], nums2 = [7,3,8,0,6,5,7,6], k = 10,输出 [0,0,2,3,4,4,5,6,6,7]

解题思路(贪心+单调栈+双指针合并)

核心是分三步实现:先选子序列、再枚举拆分、最后合并,每步均以“最小字典序”为目标:

  1. 单调栈选子序列
    定义 pick_min(nums, t) 函数:从 nums 中选 t 个元素组成最小字典序子序列。通过“贪心丢弃较大元素”维护栈的递增性,最终取栈前 t 个元素。
  2. 枚举选数拆分
    确定 nums1t 个元素的范围:t_min = max(0, k - n)nums2 最多提供 n 个元素),t_max = min(m, k)nums1 最多提供 m 个元素)。对每个 tnums2k-t 个元素,得到两个子序列 sub1sub2
  3. 双指针合并子序列
    合并时每次选择“剩余子序列字典序更小”的当前元素(比较 sub1[i:]sub2[j:]),确保合并结果字典序最小,记录所有合并结果中的最小值。

示例代码

def maxNumber(nums1, nums2, k):
def pick_min(nums, t):
"""从nums选t个元素,组成最小字典序子序列"""
stack = []
drop = len(nums) - t  # 需丢弃的元素数
for num in nums:
while drop > 0 and stack and stack[-1] > num:
stack.pop()
drop -= 1
stack.append(num)
return stack[:t]
def merge(a, b):
"""合并两个子序列为最小字典序"""
res = []
i = j = 0
while i < len(a) and j < len(b):
if a[i:] < b[j:]:  # 比较剩余子序列整体字典序
res.append(a[i])
i += 1
else:
res.append(b[j])
j += 1
res.extend(a[i:])
res.extend(b[j:])
return res
m, n = len(nums1), len(nums2)
min_seq = []
t_min = max(0, k - n)
t_max = min(m, k)
for t in range(t_min, t_max + 1):
t2 = k - t
if t2 < 0 or t2 > n:
  continue
  sub1 = pick_min(nums1, t)
  sub2 = pick_min(nums2, t2)
  current = merge(sub1, sub2)
  if not min_seq or current < min_seq:
  min_seq = current
  return min_seq
  # 测试示例
  nums1_1 = [3,4,6,5]
  nums2_1 = [9,1,2,5,8,3]
  k1 = 5
  print("拼接最小字典序1:", maxNumber(nums1_1, nums2_1, k1))  # 输出:[1,2,3,3,4]
  nums1_2 = [2,5,6,4,4,0]
  nums2_2 = [7,3,8,0,6,5,7,6]
  k2 = 10
  print("拼接最小字典序2:", maxNumber(nums1_2, nums2_2, k2))  # 输出:[0,0,2,3,4,4,5,6,6,7]

代码解析

  • 单调栈的核心作用:快速筛选出指定长度的最小字典序子序列,时间复杂度 O(n) per 调用;
  • 合并逻辑的关键:比较剩余子序列而非单个元素,避免“局部最优但整体不优”的问题(如 a=[1,3]b=[1,2],需优先选 b[0]);
  • 枚举范围优化t_mint_max 限定合理拆分,减少无效计算,整体时间复杂度 O(k(m + n))

题目三:矩形区域不超过K的最大和(LeetCode 363,困难)

题目分析

给定 m x n 矩阵 matrix 和整数 k,找出矩阵中不超过 k 的最大矩形和。题目保证存在这样的矩形。例如:输入 matrix = [[1,0,1],[0,-2,3]], k = 2,输出 2(矩形 [[1,0,1]] 和为 2);输入 matrix = [[2,2,-1]], k = 3,输出 3(矩形 [[2,2,-1]] 和为 3)。

解题思路(二维转一维+前缀和+有序数组二分)

核心是将二维矩形和问题转化为一维子数组和问题,结合二分查找高效求解:

  1. 二维转一维
    遍历所有可能的左列 left,初始化“行和数组” row_sum(每行从 left 到当前右列的和)。遍历右列 rightright ≥ left)时,更新 row_sum[i] += matrix[i][right],此时问题转化为“在 row_sum 中找不超过 k 的最大子数组和”。
  2. 一维前缀和+二分查找
    • 计算 row_sum 的前缀和 prefix,维护有序数组 sorted_prefix 存储已遍历的前缀和;
    • 对每个 prefix[j],在 sorted_prefix 中二分查找 prefix[j] - k 的插入位置 idx,若 idx < len(sorted_prefix),则 prefix[j] - sorted_prefix[idx] 是候选值;
    • prefix[j] 插入 sorted_prefix(保持有序),实时更新最大候选值。

示例代码

import bisect
def maxSumSubmatrix(matrix, k) -> int:
m, n = len(matrix), len(matrix[0])
max_sum = float('-inf')
# 遍历所有左列
for left in range(n):
row_sum = [0] * m  # 每行从left到right的和
# 遍历所有右列
for right in range(left, n):
# 更新行和
for i in range(m):
row_sum[i] += matrix[i][right]
# 找row_sum中不超过k的最大子数组和
prefix = 0
sorted_prefix = [0]  # 存储已遍历前缀和(有序)
for num in row_sum:
prefix += num
# 二分查找prefix - k的插入位置
idx = bisect.bisect_left(sorted_prefix, prefix - k)
if idx < len(sorted_prefix):
current_sum = prefix - sorted_prefix[idx]
if current_sum > max_sum and current_sum <= k:
max_sum = current_sum
# 插入当前前缀和
bisect.insort(sorted_prefix, prefix)
return max_sum
# 测试示例
matrix1 = [[1,0,1],[0,-2,3]]
k1 = 2
print("不超过k的最大和1:", maxSumSubmatrix(matrix1, k1))  # 输出:2
matrix2 = [[2,2,-1]]
k2 = 3
print("不超过k的最大和2:", maxSumSubmatrix(matrix2, k2))  # 输出:3

代码解析

  • 维度转化的价值:将复杂二维问题拆解为“固定列+一维子数组”,降低问题复杂度;
  • 二分查找的效率:有序数组维护前缀和,每次二分和插入时间 O(m log m),整体时间复杂度 O(n² m log m),在 n 较小的矩阵中高效可行;
  • 边界处理细节sorted_prefix = [0] 覆盖“子数组从第一个元素开始”的场景,确保结果无遗漏。

✨ 本次分享的3道题覆盖“动态规划+前缀和”“贪心+单调栈”“二维转一维+二分”三大创新思维方向,均为LeetCode中“思维转折点多、工程实现细节丰富”的冷门困难题。若对某题的拓展场景(如带权重的分组平均值、三维区域和问题)或其他冷门困难题有需求,随时告诉我!
更多算法解析欢迎到CSDN主页交流:山顶风景独好