当前位置: 首页 > news >正文

11.6-11.14力扣前缀和刷题

前缀和相关知识

1. 基本概念

假设有一个原数组 arr,长度为 n,我们构建一个前缀和数组 pre,长度为 n+1,其中:

pre[0] = 0

pre[i] = arr[0] + arr[1] + ... + arr[i-1],对于 i 从 1 到 n

这样,原数组中区间 [l, r](下标从 0 开始)的和可以通过前缀和数组计算:

sum(l, r) = pre[r+1] - pre[l]

例子
假设 arr = [1, 2, 3, 4],则前缀和数组为:

pre[0] = 0

pre[1] = 1

pre[2] = 1 + 2 = 3

pre[3] = 1 + 2 + 3 = 6

pre[4] = 1 + 2 + 3 + 4 = 10

要计算区间 [1, 3] 的和(即元素 2、3、4):

sum(1, 3) = pre[4] - pre[1] = 10 - 1 = 9,正确。

2. 二维前缀和

前缀和可以扩展到二维数组(矩阵),用于快速计算子矩阵的和。设原矩阵 mat 大小为 m x n,我们构建一个二维前缀和数组 pre,大小为 (m+1) x (n+1),其中:

pre[i][j] 表示从 mat[0][0] 到 mat[i-1][j-1] 的所有元素之和。

构建公式:

pre[i][j] = pre[i-1][j] + pre[i][j-1] - pre[i-1][j-1] + mat[i-1][j-1]

查询子矩阵从 (r1, c1) 到 (r2, c2) 的和(下标从 0 开始):

sum = pre[r2+1][c2+1] - pre[r1][c2+1] - pre[r2+1][c1] + pre[r1][c1]

例子
假设 mat = [[1, 2, 3], [4, 5, 6], [7, 8, 9]],则二维前缀和数组为:

pre = [ [0, 0, 0, 0], [0, 1, 3, 6], [0, 5, 12, 21], [0, 12, 27, 45] ]

要计算子矩阵 [1,1] 到 [2,2](即元素 5, 6, 8, 9)的和:

  • sum = pre[3][3] - pre[1][3] - pre[3][1] + pre[1][1] = 45 - 6 - 12 + 1 = 28,正确。

【1】303. 区域和检索 - 数组不可变

日期:11.6

1.题目链接:303. 区域和检索 - 数组不可变 - 力扣(LeetCode)https://leetcode.cn/problems/range-sum-query-immutable/description/?envType=problem-list-v2&envId=prefix-sum

2.类型:设计,数组,前缀和

3.方法:前缀和(一次题解)

大小:n + 1(比原数组多一个元素)

含义:

sums[0] = 0

sums[1] = nums[0]

sums[2] = nums[0] + nums[1]

...

sums[i] = nums[0] + nums[1] + ... + nums[i-1]

关键代码:

    NumArray(vector<int>& nums) {int n=nums.size();sums.resize(n+1);for(int i=0;i<n;i++){sums[i+1]=sums[i]+nums[i];}}int sumRange(int i,int j){return sums[j+1]-sums[i];}

【2】528. 按权重随机选择

日期:11.7

1.题目链接:528. 按权重随机选择 - 力扣(LeetCode)https://leetcode.cn/problems/random-pick-with-weight/description/?envType=problem-list-v2&envId=prefix-sum

2.类型:前缀和,二分查找,数学

3.方法:前缀和+二分查找(官方题解)

计算权重的前缀和,将权重映射到数轴上的区间

生成一个在 [1, 总权重] 范围内的随机数

使用二分查找找到随机数落在哪个区间,对应的索引就是结果

关键代码:

private:mt19937 gen;  // 随机数生成器uniform_int_distribution<int> dis;  // 均匀分布vector<int> pre;  // 前缀和数组
public:// 初始化随机数生成器和前缀和数组Solution(vector<int>& w): gen(random_device{}()),dis(1, accumulate(w.begin(), w.end(), 0)) {partial_sum(w.begin(),w.end(),back_inserter(pre));}// 根据权重随机选择索引int pickIndex(){int x=dis(gen);  // 生成随机数return lower_bound(pre.begin(),pre.end(), x)-pre.begin();}

【3】713. 乘积小于 K 的子数组

日期:11.8

1.题目链接:713. 乘积小于 K 的子数组 - 力扣(LeetCode)https://leetcode.cn/problems/subarray-product-less-than-k/description/?envType=problem-list-v2&envId=prefix-sum

2.类型:前缀和,二分查找,滑动窗口

3.方法一:二分查找(官方题解)

核心洞察:利用对数的性质将乘积问题转换为求和问题

乘积:nums[i] × nums[i+1] × ... × nums[j] < k

取对数:log(nums[i]) + log(nums[i+1]) + ... + log(nums[j]) < log(k)

这样就将乘积小于k的问题转换成了对数和小于log(k)的问题

关键代码:

        vector<double> logPrefix(n+1);for(int i=0;i<n;i++){logPrefix[i+1]=logPrefix[i]+log(nums[i]);}double logk=log(k);  // k的对数值int ret=0;        // 遍历每个可能的子数组结束位置jfor(int j=0;j<n;j++){// 使用二分查找找到满足条件的最小起始位置lint l=upper_bound(logPrefix.begin(), logPrefix.begin()+j+1, logPrefix[j+1]-logk+1e-10)-logPrefix.begin();ret +=j+1-l;  // 统计以j结尾的满足条件的子数组数量}

4.方法二:滑动窗口(一次题解)

维护一个窗口 [i, j],保证窗口内所有元素的乘积 < k

当加入新元素导致乘积 >= k 时,从左侧移除元素直到乘积 < k

对于每个位置 j,以 j 结尾的满足条件的子数组数量为 j - i + 1

关键代码:

        int n=nums.size(),ret=0;  int prod=1,i=0;          for(int j=0;j<n;j++){prod *=nums[j];  // 将当前元素加入窗口,更新乘积            // 当乘积超过等于k时,移动左边界缩小窗口while(i<=j&&prod>=k){prod/=nums[i];  // 移除左边界元素i++;  // 左边界右移}ret += j-i+1;}return ret;}

【4】724. 寻找数组的中心下标

日期:11.9

1.题目链接:724. 寻找数组的中心下标 - 力扣(LeetCode)https://leetcode.cn/problems/find-pivot-index/description/?envType=problem-list-v2&envId=prefix-sum

2.类型:前缀和,数组

3.方法一:前缀和(一次题解)

记数组的全部元素之和为 total,当遍历到第 i 个元素时,设其左侧元素之和为 sum,则其右侧元素之和为 total−nums i −sum。左右侧元素相等即为 sum=total−nums i−sum,2×sum+numsi=total。

当中心索引左侧或右侧没有元素时,即为零个项相加,这在数学上称作「空和」(empty sum)。在程序设计中约定「空和是零」

关键代码:

        int total=accumulate(nums.begin(),nums.end() 0);int sum=0;for (int i=0;i<nums.size();++i){if(2*sum+nums[i]==total){return i;}sum+=nums[i];}return -1;

【5】813. 最大平均值和的分组

 日期:11.10

1.题目链接:813. 最大平均值和的分组 - 力扣(LeetCode)https://leetcode.cn/problems/largest-sum-of-averages/description/?envType=problem-list-v2&envId=prefix-sum

2.类型:前缀和,数组,动态规划

3.方法一:前缀和(半解)

为了方便计算子数组的平均值,使用一个数组 prefix 来保存数组 nums 的前缀和。使用 dp[i][j] 表示 nums 在区间 [0,i−1] 被切分成 j 个子数组的最大平均值和,显然 i≥j,计算分两种情况讨论:

当 j=1 时,dp[i][j] 是对应区间 [0,i−1] 的平均值;

当 j>1 时,我们将可以将区间 [0,i−1] 分成 [0,x−1] 和 [x,i−1] 两个部分,其中 x≥j−1,那么 dp[i][j] 等于所有这些合法的切分方式的平均值和的最大值。

因此转移方程为:

关键代码:

        vector<double> prefix(n+1);for(int i=0;i<n;i++){prefix[i+1]=prefix[i]+nums[i];}       // 初始化DP数组vector<double> dp(n+1);for(int i=1;i<=n;i++){dp[i]=prefix[i]/i;  // 前i个元素的平均值(只分1组)}        // 动态规划,考虑分组数从2到kfor(int j=2;j<=k;j++){// 从后往前更新,避免覆盖当前轮次需要使用的值for(int i=n;i>=j;i--){for(int x=j-1;x<i;x++){// 状态转移:前x个元素分j-1组,剩下的元素作为第j组dp[i]=max(dp[i], dp[x]+(prefix[i]-prefix[x])/(i-x));}}}

【6】930. 和相同的二元子数组

 日期:11.11

1.题目链接:930. 和相同的二元子数组 - 力扣(LeetCode)https://leetcode.cn/problems/binary-subarrays-with-sum/description/?envType=problem-list-v2&envId=prefix-sum

2.类型:前缀和,数组,哈希表,滑动窗口

3.方法一:哈希表(一次题解)

使用前缀和记录从开始到当前位置的累加和

使用哈希表记录每个前缀和出现的次数

对于每个位置,检查是否存在前缀和等于 当前前缀和 - goal

关键代码:

       for(auto& num : nums){cnt[sum]++;  // 记录当前前缀和出现的次数sum+=num;  // 更新前缀和ret+=cnt[sum-goal];  // 统计满足条件的子数组数量}

4.方法二:滑动窗口(半解)

维护两个滑动窗口 [left1, right] 和 [left2, right]

第一个窗口保证和 <= goal,第二个窗口保证和 < goal

两个窗口左指针的差值就是和为 goal 的子数组数量

left1:保证 [left1, right] 区间和 <= goal 的最小左边界

left2:保证 [left2, right] 区间和 < goal 的最小左边界

right:当前右边界

所有以 right 结尾且和为 goal 的子数组,其左边界必须在 [left1, left2-1] 范围内

如果左边界 < left1,那么和 > goal

如果左边界 >= left2,那么和 < goal

所以左边界在 [left1, left2-1] 时,和正好等于 goal

关键代码:

     while(right<n){// 更新两个窗口的和(加入当前右指针元素)sum1+=nums[right];sum2+=nums[right];        // 调整第一个左指针:确保窗口[left1, right]的和 <= goalwhile(left1<=right&&sum1>goal){sum1-=nums[left1];left1++;}        // 调整第二个左指针:确保窗口[left2, right]的和 < goalwhile(left2<=right&&sum2>=goal){sum2-=nums[left2];left2++;}        // 关键:统计满足条件的子数组数量ret+=left2-left1;right++;}

【7】1109. 航班预订统计

  日期:11.12

1.题目链接:1109. 航班预订统计 - 力扣(LeetCode)https://leetcode.cn/problems/corporate-flight-bookings/description/?envType=problem-list-v2&envId=prefix-sum

2.类型:前缀和,数组

3.方法一:哈希表(半解)

差分数组的核心思想是:

要给区间 [l, r] 的所有元素加上值 val 时

只需要在 l 位置加上 val,在 r+1 位置减去 val

最后通过前缀和计算,这个区间的所有元素都会自动加上 val

差分数组技巧:

通过标记区间开始和结束的位置,避免对每个区间内的每个元素单独更新

最后通过一次前缀和计算得到最终结果

        // 第一步:处理每个预订,构建差分数组for(auto& booking : bookings){int first=booking[0]-1;  // 起始航班索引(转为0-based)int last=booking[1]-1;   // 结束航班索引(转为0-based)int seats=booking[2];              nums[first]+=seats;// 在结束位置的下一个位置减去座位数if(last+1<n){nums[last+1]-=seats;}}      // 第二步:通过前缀和计算每个航班的实际座位数for(int i=1;i<n;i++){nums[i]+=nums[i-1];}

【8】1413. 逐步求和得到正数的最小值

  日期:11.12

1.题目链接:1413. 逐步求和得到正数的最小值 - 力扣(LeetCode)https://leetcode.cn/problems/minimum-value-to-get-positive-step-by-step-sum/description/?envType=problem-list-v2&envId=prefix-sum

2.类型:前缀和,数组,贪心,二分查找

3.方法一:贪心(一次题解)

要保证所有的累加和 accSum 满足 accSum+startValue≥1,只需保证累加和的最小值 accSumMin 满足 accSumMin+startValue≥1,那么 startValue 的最小值即可取 −accSumMin+1。

        int accSum=0,accSumMin=0;for(int num : nums){accSum+=num;accSumMin=min(accSumMin,accSum);}return -accSumMin+1;

【9】1140. 石子游戏 II

  日期:11.13

1.题目链接:1140. 石子游戏 II - 力扣(LeetCode)https://leetcode.cn/problems/stone-game-ii/description/?envType=problem-list-v2&envId=prefix-sum

2.类型:前缀和,数组,记忆化搜索,动态规划

3.方法一:记忆化搜索(官方题解)

dp[i][m]:从位置 i 开始,当前 M = m 时,当前玩家能比对手多得的石头数量。

 状态转移方程:对于状态 (i, m),当前玩家可以取 x 堆石头(1 <= x <= 2m):

dp[i][m] = max( sum(i, i+x-1) - dp[i+x][max(m, x)] )

sum(i, i+x-1):当前玩家本轮得到的石头数量

dp[i+x][max(m, x)]:对手在剩余游戏中的净胜分数

当前玩家的净胜分 = 本轮得分 - 对手的净胜分

关键代码:

        // 从后往前填充DP表for(int i=n;i>=0;i--){for(int m=1;m<=n;m++){if(i==n){// 基础情况:没有石头可拿dp[i][m]=0;}else{int sum=0;// 尝试所有可能的取法:取1到2*m堆石头for(int x=1;x<=2*m;x++){if(i+x>n){break;  // 不能取超过剩余的石头}// 累加当前取的石头价值sum+=piles[i+x-1];// 状态转移:当前得分减去对手在剩余游戏中的最优得分dp[i][m]=max(dp[i][m],sum-dp[i+x][min(n, max(m, x))]);}}}}       // 计算最终得分:总分数加上净胜分数的一半return (dp[0][1]+accumulate(piles.begin(), piles.end(), 0))/2;

【10】1480. 一维数组的动态和

  日期:11.14

1.题目链接:1480. 一维数组的动态和 - 力扣(LeetCode)https://leetcode.cn/problems/running-sum-of-1d-array/description/?envType=problem-list-v2&envId=prefix-sum

2.类型:前缀和,数组

3.方法一:原地修改(一次题解)

这样我们可以从下标 1 开始遍历 nums 数组,每次让 nums[i] 变为 nums[i−1]+nums[i] 即可(因为此时的 nums[i−1] 即为 runningSum[i−1])。

关键代码:

        for(int i=1;i<n;i++){nums[i]+=nums[i-1];}

http://www.dtcms.com/a/609944.html

相关文章:

  • vue3.0数据驱动问题
  • Java 8+新特性实战:Lambda表达式、Stream API与函数式编程范式
  • Rust新手第一课:Mac环境搭建踩坑记录
  • 长沙品质网站建设优点wordpress导入插件下载
  • 男的和女的做那个视频网站国内网站建设代理
  • 操作教程 |JumpServer堡垒机数据脱敏功能的使用
  • Ⅰ人工智能学习的核心概念概述+线性回归(1)
  • KKT条件:对偶问题、KKT条件以及内点法
  • 基于ssm的数据标注系统cg4ft3c7(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • Go语言编译器设计器 | 探索Go语言编译器的工作原理与实现技巧
  • c# 集成激光雷达(以思岚A1为例)
  • 大数据运维面试题及答案
  • MySQL数据库索引详解
  • 多通道ADC数模转换器电池管理系统解决方案
  • Java 基础高频38问
  • 煤矿煤质分类数据集3406张5类别
  • MR30分布式I/O模块服务换热站项目,守护万家温暖
  • 唐山市城市建设规划局网站网站首页index.html
  • 大模型架构和原理二
  • 11.14 脚本网页 青蛙过河
  • 【算法专题训练】30、二叉树的应用
  • 深入解析IP, ICMP, OSPF, BGP四大核心网络协议
  • RAG系统中的文本分块技术:从基础策略到智能分块的深度解析
  • Bootstrap 4 Flex布局详解
  • APP网站建设开发企业发展网站建设增城
  • 我做网站了 圆通北京网站建设首选优达
  • Lidar调试记录Ⅲ之Ubuntu22.04+ROS2环境中安装colcon
  • 闵行网站制作设计公司wordpress wp polls
  • IntelliJ IDEA初始化指南
  • Unity ScriptedImporter 教程:自定义资源导入器