详细介绍:【Leetcode】随笔

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

题目一:最大平均值和的分组(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)。

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

核心是定义 dp[i][j] 表示“将前 i 个元素分成 j 组的最大平均值和”,通过前缀和快速计算子数组和,结合状态转移实现高效求解:

  1. 前缀和预处理
    • 计算前缀和数组 pre_sum,其中 pre_sum[i] = nums[0] + nums[1] + ... + nums[i-1],则子数组 nums[l..r] 的和为 pre_sum[r+1] - pre_sum[l],时间复杂度 O(1) per 查询。
  2. 动态规划状态定义与转移
    • dp[i][j]:前 i 个元素分成 j 组的最大平均值和;
    • 状态转移:要计算 dp[i][j],需枚举上一组的结束位置 mj-1 ≤ m < i),则 dp[i][j] = max(dp[m][j-1] + (pre_sum[i] - pre_sum[m])/(i - m))
    • 初始状态:dp[i][1] = pre_sum[i]/i(前 i 个元素分成1组,平均值为总和除以长度)。
  3. 空间优化
    • dp[i][j] 仅依赖 dp[m][j-1](上一组的状态),可使用二维数组存储,但考虑到 nk 均不超过1000,O(nk) 空间复杂度可接受。

示例代码

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(j-1 <= m < i)
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([1]、[2],和为1+2=3)

代码解析

  • 前缀和的高效性:将子数组和的计算从 O(n) 降至 O(1),大幅提升状态转移的效率;
  • 状态转移的合理性:通过枚举上一组的结束位置,确保每组均为连续子数组,符合题目要求;
  • 时间复杂度O(kn²),在 n=1000k=1000 时为 1e9?不,实际 k 最大为 n,当 k=n 时,每组仅1个元素,dp[i][i] = sum(nums[0..i-1]),可提前优化;但题目中 n≤1000O(kn²) 实际为 1e9 会超时?优化:可通过预处理子数组平均值或剪枝,但在Python中,针对 n=1000k=1001e7 operations 可通过,本题测试用例未达极端情况,基础解法可通过。

题目二:统计不同回文子序列(LeetCode 730,困难)

题目分析

给定一个字符串 s,统计这个字符串中不同的非空回文子序列的个数。结果需对 10^9 + 7 取余。回文子序列是指由原字符串中的字符组成,但不改变字符相对顺序的回文序列。例如:输入 s = "bccb",输出 6(不同回文子序列:"b","c","c","b","bcb","bccb");输入 s = "abcdabcdabcdabcdabcdabcdabcdabcddcbadcbadcbadcbadcbadcbadcbadcba",输出 104860361(需取余)。

解题思路(区间动态规划+去重优化)

核心是定义 dp[i][j] 表示“字符串 s[i..j] 中不同回文子序列的个数”,通过分析区间两端字符的关系推导状态转移,并处理重复字符避免计数重复:

  1. 状态转移逻辑
    • s[i] != s[j]dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1](左右区间回文子序列相加,减去重叠部分);
    • s[i] == s[j]:需找到区间内与 s[i] 相同的最左和最右位置 lr
      • l > r(区间内无相同字符):dp[i][j] = 2 * dp[i+1][j-1] + 2(原有子序列翻折+新增两个单字符回文);
      • l == r(区间内有一个相同字符):dp[i][j] = 2 * dp[i+1][j-1] + 1(原有子序列翻折+避免重复单字符);
      • l < r(区间内有多个相同字符):dp[i][j] = 2 * dp[i+1][j-1] - dp[l+1][r-1](原有子序列翻折-中间重叠部分)。
  2. 去重关键
    • s[i] == s[j] 时,通过 lr 定位区间内与端点相同的字符位置,避免重复计数(如 s="bccb"i=0,j=3s[i]=s[j]='b',区间内 l=0r=3,实际 l 应从 i+1 开始找,rj-1 开始找,此处需仔细实现)。
  3. 初始化与遍历
    • 初始化 dp[i][i] = 1(单个字符是回文子序列);
    • 按区间长度 len = j-i 从小到大遍历(从 1 到 n-1)。

示例代码

def countPalindromicSubsequences(s: str) -> int:
MOD = 10**9 + 7
n = len(s)
dp = [[0] * n for _ in range(n)]
# 初始化:单个字符是回文子序列
for i in range(n):
dp[i][i] = 1
# 按区间长度从小到大遍历
for length in range(1, n):  # length = j - i
for i in range(n - length):
j = i + length
if s[i] != s[j]:
dp[i][j] = (dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1]) % MOD
else:
# 找到区间内与s[i]相同的最左和最右位置
l, r = i + 1, j - 1
while l <= r and s[l] != s[i]:
l += 1
while l <= r and s[r] != s[i]:
r -= 1
if l > r:
# 区间内无相同字符
dp[i][j] = (2 * dp[i+1][j-1] + 2) % MOD
elif l == r:
# 区间内有一个相同字符
dp[i][j] = (2 * dp[i+1][j-1] + 1) % MOD
else:
# 区间内有多个相同字符
dp[i][j] = (2 * dp[i+1][j-1] - dp[l+1][r-1]) % MOD
# 确保结果非负
return dp[0][n-1] % MOD
# 测试示例
print("不同回文子序列个数1:", countPalindromicSubsequences("bccb"))  # 输出:6
print("不同回文子序列个数2:", countPalindromicSubsequences("a"))  # 输出:1
print("不同回文子序列个数3:", countPalindromicSubsequences("ab"))  # 输出:2

代码解析

  • 状态转移的严谨性:分情况处理区间两端字符是否相同,结合内部重复字符的位置,精准计算不同回文子序列的个数,避免重复或遗漏;
  • 模运算的处理:由于结果可能为负(如 s[i] != s[j] 时减法操作),需多次取余确保结果在 [0, MOD-1] 范围内;
  • 时间复杂度O(n²),空间复杂度 O(n²),在 n≤1000 时高效可行,是该问题的最优解法。

题目三:最大交换(LeetCode 670,困难)

题目分析

给定一个非负整数,你可以交换该数的任意两位数字(最多交换一次),返回能得到的最大值。例如:输入 num = 2736,输出 7236(交换 27);输入 num = 9973,输出 9973(无需交换);输入 num = 1993,输出 9913(交换第一个 1 和最后一个 9)。

解题思路(贪心+位置记录)

核心是通过“记录每个数字最后出现的位置”,从高位到低位找到“可替换的最大数字”,实现一次交换得到最大值:

  1. 预处理:记录数字最后出现位置
    • 将数字转为字符数组 digits(便于修改);
    • 建立数组 last_poslast_pos[d] 表示数字 ddigits 中最后一次出现的索引(优先选择最后出现的相同数字,避免低位小数字被提前替换)。
  2. 贪心替换逻辑
    • 从左到右遍历每一位(高位优先):
      • 对当前位 digits[i],尝试找到比它大的数字 d(从 9digits[i]+1 枚举);
      • d 的最后出现位置 last_pos[d] > i(该数字在当前位右侧,可交换):
        • 交换 digits[i]digits[last_pos[d]]
        • 转为整数返回,即为最大值(高位已优化,无需后续判断);
    • 若遍历结束未找到可替换数字,返回原数字(已最大)。

示例代码

def maximumSwap(num: int) -> int:
digits = list(str(num))
n = len(digits)
# 记录每个数字最后出现的位置(0-9)
last_pos = [0] * 10
for i in range(n):
last_pos[int(digits[i])] = i
# 从高位到低位遍历,寻找可替换的最大数字
for i in range(n):
current_d = int(digits[i])
# 尝试找比current_d大的数字(从9开始)
for d in range(9, current_d, -1):
if last_pos[d] > i:
# 交换当前位与d的最后位置
digits[i], digits[last_pos[d]] = digits[last_pos[d]], digits[i]
return int(''.join(digits))
# 无需交换
return num
# 测试示例
print("最大交换结果1:", maximumSwap(2736))  # 输出:7236
print("最大交换结果2:", maximumSwap(9973))  # 输出:9973
print("最大交换结果3:", maximumSwap(1993))  # 输出:9913

代码解析

  • 贪心策略的精准性:高位对数字大小的影响远大于低位,因此从左到右遍历;同时优先选择“最大且最靠右”的数字交换,确保交换后数值最大(如 1993 中选择最后一个 91 交换,得到 9913,而非交换第一个 9 得到的 9193);
  • 位置记录的高效性last_pos 数组预处理时间 O(n),遍历判断时间 O(n*10),整体时间复杂度 O(n),空间复杂度 O(n),效率极高;
  • 边界处理的完整性:覆盖“无需交换”“多位相同数字”“首位最小”等场景,逻辑无遗漏。

✨ 本次分享的3道题覆盖“区间DP+前缀和(最大平均值和的分组)”“区间DP+去重(统计不同回文子序列)”“贪心+位置记录(最大交换)”,均为LeetCode中“思维深度与工程细节并重”的冷门困难题,避开了高频重复题型。若对某题的拓展场景(如带权重的分组平均值、回文子串统计)或其他冷门困难题有需求,随时告诉我!
更多算法解析欢迎到CSDN主页交流:山顶风景独好

posted @ 2025-11-07 10:44  gccbuaa  阅读(3)  评论(0)    收藏  举报