第433场周赛:变长子数组求和、最多 K 个元素的子序列的最值之和、粉刷房子 Ⅳ、最多 K 个元素的子数组的最值之和

devtools/2025/2/13 1:10:55/

Q1、leetcode.cn/problems/sum-of-variable-length-subarrays/description/" rel="nofollow">变长子数组求和

1、题目描述

给你一个长度为 n 的整数数组 nums 。对于 每个 下标 i0 <= i < n),定义对应的子数组 nums[start ... i]start = max(0, i - nums[i]))。

返回为数组中每个下标定义的子数组中所有元素的总和。

子数组 是数组中的一个连续、非空 的元素序列。

2、解题思路

由于子数组的起点 start 取决于 nums[i],每次计算 nums[start ... i] 的和时,我们可以借助 前缀和 来快速计算子数组的和,从而避免重复计算。

前缀和的定义

前缀和数组 prefix_sum[i] 表示 从数组开始到索引 i-1 的元素之和,即:

prefix_sum [ i ] = ∑ j = 0 i − 1 nums [ j ] \text{prefix\_sum}[i] = \sum_{j=0}^{i-1} \text{nums}[j] prefix_sum[i]=j=0i1nums[j]

则某个区间 [start, i] 的子数组和可以通过前缀和快速计算:

∑ j = start i nums [ j ] = prefix_sum [ i + 1 ] − prefix_sum [ start ] \sum_{j=\text{start}}^{i} \text{nums}[j] = \text{prefix\_sum}[i+1] - \text{prefix\_sum}[\text{start}] j=startinums[j]=prefix_sum[i+1]prefix_sum[start]

这样,我们可以在 O(1) 时间内计算每个 i 对应的子数组的和,而不会重复累加相同的元素,整体时间复杂度 降为 O(n)

3、代码实现

class Solution {
public:int subarraySum(vector<int>& nums) {int n = nums.size();vector<int> prefix_sum(n + 1, 0); // 前缀和数组, prefix_sum[i] 表示 nums[0] 到 nums[i-1] 的和// 计算前缀和for (int i = 0; i < n; ++i) {prefix_sum[i + 1] = prefix_sum[i] + nums[i];}int result = 0; // 存储所有子数组的元素和for (int i = 0; i < n; ++i) {int start = max(0, i - nums[i]); // 计算子数组的起始位置result += prefix_sum[i + 1] - prefix_sum[start]; // 使用前缀和计算子数组和}return result;}
};

在这里插入图片描述

4、复杂度分析

前缀和计算 需要 O(n) 的时间。

子数组求和 通过前缀和的方式优化,每次 O(1),整体仍然是 O(n)

空间复杂度:使用额外的前缀和数组 O(n),因此 空间复杂度 O(n)


Q2、leetcode.cn/problems/maximum-and-minimum-sums-of-at-most-size-k-subsequences/description/" rel="nofollow">最多 K 个元素的子序列的最值之和

1、题目描述

给你一个整数数组 nums 和一个正整数 k,返回所有长度最多为 k子序列最大值最小值 之和的总和。

非空子序列 是指从另一个数组中删除一些或不删除任何元素(且不改变剩余元素的顺序)得到的数组。

由于答案可能非常大,请返回对 109 + 7 取余数的结果。

2、解题思路

思路:数学 + 组合数优化计算

(1) 排序数组,便于计算最小值和最大值

假设 nums 排序后为:nums = [a1, a2, ..., an]

  • nums[i] 作为子序列的 最小值 时,其前面的 i 个元素可以自由选择(或不选),最大长度不超过 k
  • nums[i] 作为子序列的 最大值 时,其后面的 n - i - 1 个元素可以自由选择,最大长度不超过 k

(2) 计算 nums[i] 作为最小值的贡献

nums[i] 作为 最小值 时,所有子序列来自 [i, n-1] 的元素。
对于长度 ≤ k 的子序列,其前 i 个元素可以选,也可以不选,形成的子序列数为:

∑ j = 0 min ⁡ ( k , i ) C ( i , j ) \sum_{j=0}^{\min(k, i)} C(i, j) j=0min(k,i)C(i,j)

其中 C(i, j) 是组合数,即:

C ( i , j ) = i ! j ! ( i − j ) ! C(i, j) = \frac{i!}{j! (i-j)!} C(i,j)=j!(ij)!i!

(3) 计算 nums[i] 作为最大值的贡献

由于 nums[i] 作为最大值时,其对称位置 nums[n - 1 - i] 作为最小值,因此我们直接计算 nums[i]nums[n - 1 - i] 的贡献之和。

(4) 预计算阶乘与逆元,加速组合数计算

由于 C(n, m) 的计算涉及 阶乘求逆,直接计算 C(n, m) 可能会超时,因此我们使用 预计算 + 逆元 快速求解:

  • 预计算 阶乘 factorial[i] = i! % MOD
  • 预计算 阶乘逆元 invFactorial[i] = (i!)^{-1} % MOD
  • 通过 快速幂求逆元 (Fermat 小定理)

3、代码实现

const int MOD = 1000000007;
const int MAX_N = 100000;long long factorial[MAX_N]; // 存储阶乘 F[i] = i!
long long invFactorial[MAX_N];long long powerMod(long long x, int n) {long long res = 1;while (n > 0) {if (n & 1) // 如果 n 是奇数{res = res * x % MOD;}x = x * x % MOD;n >>= 1; // n 右移, 相当于除以 2}return res;
}// 预处理阶乘及其逆元
void precomputeFactorial() {factorial[0] = 1;for (int i = 1; i < MAX_N; ++i) {factorial[i] = factorial[i - 1] * i % MOD;}// 计算 (MAX_N - 1)! 的逆元invFactorial[MAX_N - 1] = powerMod(factorial[MAX_N - 1], MOD - 2);// 计算所有 (i!)^-1for (int i = MAX_N - 2; i >= 0; --i) {invFactorial[i] = invFactorial[i + 1] * (i + 1) % MOD;}
}// 计算组合数 C(n, m) = n! / (m! * (n-m)!)
long long comb(int n, int m) {if (m > n || m < 0) {return 0;}return factorial[n] * invFactorial[m] % MOD * invFactorial[n - m] % MOD;
}// 预处理组合数
auto _ = (precomputeFactorial(), 0);class Solution {
public:int minMaxSums(vector<int>& nums, int k) {sort(nums.begin(), nums.end()); // 先排序, 方便计算 min/maxint n = nums.size();long long totalSum = 0;// 计算所有长度 <= k 的子序列贡献for (int i = 0; i < n; ++i) {long long subsetCount = 0;// 计算 nums[i] 作为最小值的贡献for (int j = 0; j < min(k, i + 1); ++j) {subsetCount = (subsetCount + comb(i, j)) % MOD;}// 计算 nums[i] 作为最大值的对称贡献totalSum = (totalSum + subsetCount * (nums[i] + nums[n - 1 - i]) % MOD) % MOD;}return totalSum;}
};

在这里插入图片描述

4、复杂度分析

排序O(n log n)

预处理阶乘和逆元O(n)

计算所有 C(i, j):最坏情况下 O(nk),但一般远小于 O(n^2)

最终复杂度O(n log n + n + nk) ≈ O(n log n + nk)


Q3、leetcode.cn/problems/paint-house-iv/description/" rel="nofollow">粉刷房子 Ⅳ

1、题目描述

给你一个 偶数 整数 n,表示沿直线排列的房屋数量,以及一个大小为 n x 3 的二维数组 cost,其中 cost[i][j] 表示将第 i 个房屋涂成颜色 j + 1 的成本。

如果房屋满足以下条件,则认为它们看起来 漂亮

  • 不存在 两个 涂成相同颜色的相邻房屋。
  • 距离行两端 等距 的房屋不能涂成相同的颜色。例如,如果 n = 6,则位置 (0, 5)(1, 4)(2, 3) 的房屋被认为是等距的。

返回使房屋看起来 漂亮最低 涂色成本。

2、解题思路

题目转化

由于房屋成对对称,我们可以将问题转换为处理 n/2 对房屋:

  • (0, n-1), (1, n-2), …, (n/2 - 1, n/2)

定义 dp[i][prevLeft][prevRight]

  • 表示前 i+1 对房屋(0i),上一对房屋 (i-1) 选择了 prevLeftprevRight 颜色时,符合要求的最低涂色成本

动态规划状态定义

  • dp[i][leftColor][rightColor]:表示涂完第 i 对房屋,并且当前这对房屋的左房屋leftColor右房屋rightColor 的最小成本。
  • 递推关系:
    • 枚举上一个房屋对 (i-1) 的颜色 prevLeftprevRight
    • 确保 leftColor ≠ prevLeftrightColor ≠ prevRight,并且 leftColor ≠ rightColor
    • 计算新的 dp[i][leftColor][rightColor] 的最小值。

3、代码实现

class Solution {
public:long long minCost(int n, vector<vector<int>>& cost) {// 动态规划数组 dp[i][prevLeft][prevRight]vector<vector<vector<long long>>> dp(n / 2 + 1, vector<vector<long long>>(3, vector<long long>(3, LLONG_MAX)));// 初始化 dp[0], 即第 0 对房屋的所有可能涂色情况for (int leftColor = 0; leftColor < 3; leftColor++) {for (int rightColor = 0; rightColor < 3; rightColor++) {// 初始状态不能相同if (leftColor != rightColor) {dp[0][leftColor][rightColor] = cost[0][leftColor] + cost[n - 1][rightColor];}}}// 动态规划计算所有房屋对的最小涂色成本for (int i = 1; i < n / 2; i++) {for (int prevLeft = 0; prevLeft < 3; prevLeft++) {for (int prevRight = 0; prevRight < 3; prevRight++) {// 如果上一对房屋 (i-1) 没有有效涂色方案, 跳过if (dp[i - 1][prevLeft][prevRight] == LLONG_MAX) {continue;}for (int leftColor = 0; leftColor < 3; leftColor++) {// 左房颜色不能和上一对左房相同if (leftColor == prevLeft) {continue;}for (int rightColor = 0; rightColor < 3; rightColor++) {// 右房颜色不能和上一对右房或左房相同if (rightColor == prevRight || rightColor == leftColor) {continue;}// 计算当前房屋对的成本long long currCost = cost[i][leftColor] + cost[n - 1 - i][rightColor];// 更新最小成本dp[i][leftColor][rightColor] = min(dp[i][leftColor][rightColor], dp[i - 1][prevLeft][prevRight] + currCost);}}}}}// 计算最终答案: 取最后一对房屋的所有可能涂色方案中的最小值long long minCost = LLONG_MAX;for (int leftColor = 0; leftColor < 3; leftColor++) {for (int rightColor = 0; rightColor < 3; rightColor++) {minCost = min(minCost, dp[n / 2 - 1][leftColor][rightColor]);}}return minCost;}
};

在这里插入图片描述

4、复杂度分析

  • 状态数量O(n/2 * 3 * 3) ≈ O(n)
  • 转移复杂度:每个 dp[i] 依赖于 dp[i-1],共 O(3^4) = O(81) 种情况。

总体复杂度: O ( n × 81 ) = O ( n ) O(n \times 81) = O(n) O(n×81)=O(n) 对于 n ≤ 10^4 仍然可以接受。


Q4、leetcode.cn/problems/maximum-and-minimum-sums-of-at-most-size-k-subarrays/description/" rel="nofollow">最多 K 个元素的子数组的最值之和

1、题目描述

给你一个整数数组 nums 和一个 整数 k 。 返回 最多k 个元素的所有子数组的 最大最小 元素之和。

子数组 是数组中的一个连续、非空 的元素序列。

2、解题思路

我们采用 单调栈数学公式 进行高效计算:

  1. 如何计算所有子数组的最小值贡献?
    • 使用单调 递增 栈,维护子数组的最小值,并计算它们的贡献。
    • 当栈顶元素比当前元素 大于等于 时,说明它无法继续作为最小值,弹出栈并计算贡献。
    • 贡献计算的核心在于,一个元素在多少个子数组中作为最小值
  2. 如何计算所有子数组的最大值贡献?
    • 只需 取反 nums 数组,将最大值问题转化为最小值问题,再计算一次即可。
  3. 如何计算子数组个数?
    • 数学公式:
      设子数组的长度为 m,若 m ≤ k,则该长度的所有子数组个数为: m(m+1)2\frac{m(m+1)}{2}2m(m+1)​ 若 m > k,则受 k 限制,其计算公式如下: (m×2−k+1)×k2\frac{(m \times 2 - k + 1) \times k}{2}2(m×2−k+1)×k​
    • 这个公式的推导基于滑动窗口思想,保证所有长度最多为 k 的子数组都被正确计算。

3、代码实现

class Solution {
public:long long minMaxSubarraySum(vector<int>& nums, int k) {// 计算长度为 m 的所有子数组的个数 (最多 k 个元素)auto countSubarrays = [&](int m) -> long long {return (m > k) ? 1LL * (m * 2 - k + 1) * k / 2 : 1LL * (m + 1) * m / 2;};// 计算所有子数组的最小值贡献auto sumSubarrayMins = [&](vector<int>& arr) -> long long {long long totalSum = 0;stack<int> st;st.push(-1); // 哨兵,方便计算边界for (int r = 0; r < arr.size(); r++) {// 维护单调递增栈, 保证栈中元素递增while (st.size() > 1 && arr[st.top()] >= arr[r]) {int i = st.top(); // 取出栈顶元素st.pop();int l = st.top(); // 栈顶元素的左边界// 计算该元素 arr[i] 作为最小值的贡献long long count = countSubarrays(r - l - 1) - countSubarrays(i - l - 1) - countSubarrays(r - i - 1);totalSum += arr[i] * count;}st.push(r);}return totalSum;};// 在 nums 末尾添加一个特殊值, 使得所有元素都能出栈计算贡献nums.push_back(INT_MIN / 2);long long ret = sumSubarrayMins(nums);// 取反 nums, 将最大值问题转化为最小值问题for (int& num : nums) {num = -num;}nums.back() *= -1; // 修正末尾特殊值ret -= sumSubarrayMins(nums);return ret;}
};

在这里插入图片描述

4、复杂度分析

单调栈的时间复杂度:

  • 每个元素 仅入栈一次,出栈一次,所以时间复杂度为 O(n)

两次计算 (最小值 & 最大值):

  • sumSubarrayMins(nums) 执行两次,总时间复杂度 O(n)

额外空间复杂度:

  • 只用了一个栈 st,额外空间复杂度 O(n)



http://www.ppmy.cn/devtools/157779.html

相关文章

DeepSeek图解10页PDF

以前一直在关注国内外的一些AI工具&#xff0c;包括文本型、图像类的一些AI实践&#xff0c;最近DeepSeek突然爆火&#xff0c;从互联网收集一些资料与大家一起分享学习。 本章节分享的文件为网上流传的DeepSeek图解10页PDF&#xff0c;免费附件链接给出。 1 本地 1 本地部…

go-elasticsearch创建ik索引并进行查询操作

es-go client引入gomod go get github.com/elastic/go-elasticsearch/v8latest连接es服务器&#xff08;不经过安全校验) cfg : elasticsearch.Config{Addresses: []string{"http://localhost:9200",}, } es, err : elasticsearch.NewClient(cfg) if err ! nil {pa…

初窥强大,AI识别技术实现图像转文字(OCR技术)

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据、人工智能领域创作者。目前从事python全栈、爬虫和人工智能等相关工作&#xff0c;主要擅长领域有&#xff1a;python…

at coder ABC 392

A - Shuffled Equation 题意&#xff1a;给一个整数序列&#xff08;A1,A2,A3&#xff09;,这三个数进行排序后形成&#xff08;B1,B2,B3&#xff09;问是否存在排序使B1*B2B3&#xff1f; 思路&#xff1a;因为一共就三个数&#xff0c;只有三种排列方式&#xff0c;我直接全…

【Linux】Socket编程—UDP

&#x1f525; 个人主页&#xff1a;大耳朵土土垚 &#x1f525; 所属专栏&#xff1a;Linux系统编程 这里将会不定期更新有关Linux的内容&#xff0c;欢迎大家点赞&#xff0c;收藏&#xff0c;评论&#x1f973;&#x1f973;&#x1f389;&#x1f389;&#x1f389; 文章目…

设计模式-状态模式:让对象的行为随状态改变而清晰可控

🌟 引言 场景痛点: 你是否遇到过这样的代码? if (state == "待支付") {// 处理待支付逻辑 } else if (state == "已支付") {// 处理已支付逻辑 } else if (...) {// 无限的条件分支... }条件分支爆炸导致代码臃肿、难以维护?状态模式正是解决这类问…

python_json转yolo文件

文章内容 将labelme的内容转换成yolo需要的txt文件 import json import os import shutil import random# Convert label to idx # with open("labels.txt", "r") as f: # classes [c.strip() for c in f.readlines()] # idx_dict {c: str(i) f…

WebRTC 客户端与ZLMediaKit通讯

1 web浏览器js方式 要使用 WebRTC 客户端与 ZLMediaKit 通讯&#xff0c;您需要设置一个 WebRTC 客户端并与 ZLMediaKit 进行连接。以下是一个基本的步骤和示例代码&#xff0c;帮助您实现这一目标。 ### 步骤 1. **安装 ZLMediaKit**&#xff1a;确保您已经在服务器上安装并…