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

子数组和 问题汇总

文章目录

  • 买卖股票的最佳时机
  • 最大子数组和
  • 拓展
    • 如果求解的是需要返回具体的数组(或者下标)
    • 如果子数组长度有下界
    • 如果子数组长度存在上界
    • 子数组的长度固定求解窗口最大值
      • 长度小总结
    • 子数组的长度必须是奇数

  • 子数组:也就是在原来的数组当中连续的元素,那么对应的解题方法也是有套路的:
    • 常用思路:使用前缀和+贪心动态规划(例如,dp[i]为以nums[i]结尾的最值情况)
    • 限制子数组的长度:使用窗口
    • 窗口内最值:使用单调队列

买卖股票的最佳时机

买卖股票的最佳时机
在这里插入图片描述

  • 思路分析
    • 贪心:最大的利益收益,那么对于某一天i来说,我们只需在它前面的那么多天当中,价格最低的哪一天买入,也就是维护一个变量leftmin ,我们就可以计算出当天i卖出的可以赚到的最优情况,将nums[i] - leftmin与我们的答案ans进行比较,然后进行更新(插播一句,所谓的优化,也只是优化这个遍历的情况的时间复杂度,减少无效多余的情况
class Solution {
public:int maxProfit(vector<int>& prices) {// 贪心问题int n = prices.size(),leftmin = prices[0],ans = 0;for (int i = 1;i < n; i++){int tmp = prices[i] - leftmin;ans = tmp > ans ? tmp : ans;leftmin = min(leftmin,prices[i]);}return ans;}
};

最大子数组和

在这里插入图片描述

  • 思路分析
    • 前缀和+贪心: 涉及到子数组和的问题,当我们求解出nums数组的前缀和的时候,就可以转化为上面的买卖股票的最佳时机的问题
    • 动态规划:我们规定dp[i]为以nums[i]结尾的最大子数组的和,就有递推公式dp[i] = max(dp[i-1]+nums[i],nums[i])

前缀和+贪心

class Solution {
public:int maxSubArray(vector<int>& nums) {// 最大子数组和 // 涉及到这个的都一般使用前缀和,然后使用贪心的买卖股票的思路int n = nums.size();vector<int> pre(n+1);for (int i = 0; i < n; i++){pre[i+1] = pre[i] + nums[i];}// 开始贪心int leftmin = 0,ans = -0x3f3f3f3f;for (int i = 0;i < n;i++){ans = max(ans,pre[i+1] - leftmin);leftmin = min(leftmin,pre[i+1]);}return ans;}
};

动态规划

class Solution {
public:int maxSubArray(vector<int>& nums) {// 最大子数组和 // 使用动态规划// dp[i]为以nums[i]结尾的最大子数组和int n = nums.size();vector<int> dp(n);dp[0] = nums[0];int ans = dp[0];for (int i = 1; i < n; i++){dp[i] = max(nums[i],dp[i-1] + nums[i]);ans = max(ans,dp[i]);}return ans;}
};

拓展

如果求解的是需要返回具体的数组(或者下标)

  • 思路分析:对于这种需要返回具体的数组的情况,当然得使用left、right、ansleft、ansright4个变量进行分别记录 当前情况的子数组的两端和最右情况的两端
  • 更新策略:(使用动态规划思路):当dp[i-1] + nums[i] > nums[i]的时候,有更新right = i,不用更新这个left;否则就是更新left = i,right = i。当最终的dp[i]>ans的时候,我们才有更新ansleft = left,ansright = right
class Solution {
public:int maxSubArray(vector<int>& nums) {// 如果需要返回这个子数组,那其实只要记录这个子数组的两个端点即可int n = nums.size(),left = 0,right = 0,ansleft = 0,ansright = 0;// 使用动态规划求解vector<int> dp(n);dp[0] = nums[0];int ans = dp[0];for (int i = 1;i < n ;i++){if (dp[i-1] + nums[i] > nums[i]){right = i;}else{left = i;right = i;}dp[i] = max(dp[i-1] + nums[i],nums[i]);if (dp[i] > ans){ansleft = left;ansright = right;}ans = max(ans,dp[i]);}cout << ansleft << " " << ansright ;return ans;}
};

如果子数组长度有下界

  • 题目要求:求解原数组当中,子数组的和的最大值,要求子数组的长度必须>=k

  • 思路分析

    • 滑动窗口:对于长度的限制,就是要设置一个窗口来实现,但是对于窗口内的最小值的选取,就需要使用到这个单调队列,优先队列的话,我们在加入新元素的时候,会把队列当中比它大的元素全部出队列,最终就可以实现,队列的元素是逐渐递增的,q.front是窗口最小的元素
class Solution {
public:int maxSubArray(vector<int>& nums) {// 如果求解的是有下界的最大子数组和的问题int n = nums.size();vector<int> pre(n+1);for (int i = 0;i < n;i++){pre[i+1] = pre[i] + nums[i];}int ans = -0x3f3f3f3f,leftmin = 0;// 使用优先队列,保持一个递增的队列deque<int>  q;for (int i = k; i <= n;i++){int left = i - k;// 将左端点放进去while ( !q.empty() && pre[q.back()] > pre[left]){q.pop_back();}q.push_back(left);// 更新答案if (!q.empty()){ans = max(ans, pre[i] - pre[q.front()]);}}return ans;}
};

如果子数组长度存在上界

  • 问题要求:求解原数组当中,求解最大子数组和,并且要求子数组的长度必须<=k

  • 思路分析

    • 确保窗口的最大值<=k:我们对于这个单调队列q.front<left就弹出(队列当中的值存储的是下标,并且下标是递增的)即可,就是在这个下界 基础上,增加了对这个队列首部元素的判断
class Solution {
public:int maxSubArray(vector<int>& nums) {// 如果求解的是有上界的最大子数组和的问题int n = nums.size();vector<int> pre(n+1);for (int i = 0;i < n;i++){pre[i+1] = pre[i] + nums[i];}int ans = -0x3f3f3f3f,leftmin = 0;// 使用优先队列,保持一个递增的队列deque<int>  q;for (int i = k; i <= n;i++){int left = i - k;while (!q.empty() && q.front < left){q.pop_front();}// 将左端点放进去while ( !q.empty() && pre[q.back()] > pre[left]){q.pop_back();}q.push_back(left);// 更新答案if (!q.empty()){ans = max(ans, pre[i] - pre[q.front()]);}}return ans;}
};

子数组的长度固定求解窗口最大值

滑动窗口最大值
在这里插入图片描述

  • 思路分析
    • 固定长度的区别:由于要保持这个单调性问题,所以在队列的末尾增加这个元素的时候,使用的是while也就是不满足的元素都会出队列,但是由于要保证这个子数组的长度是k,所以每次出队列只用出一个元素,所以使用的是if
class Solution {
public:vector<int> maxSlidingWindow(vector<int>& nums, int k) {vector<int> ans;int n = nums.size();// 使用一个双端队列来实现,保证每一个元素只进来一次deque<int> q;for (int i = 0; i < n;i++){// 右边进入while (!q.empty() && nums[q.back()] <= nums[i]){q.pop_back();}q.push_back(i);// 左边出int left = i - k + 1;if (q.front() < left){q.pop_front();}// 在窗口左端点记录答案if (left >=0 ){ans.push_back(nums[q.front()]);}}return ans;}
};

长度小总结

  • 一般思路:出队列,入队列,更新
  • 细节区别:入队列是必须的,出队列不一定(子数组有下界要求不用出队列);出队列的次数不一定(固定长度每次出一个即可)

子数组的长度必须是奇数

  • 题目要求:要求求解的子数组的最大和的同时这个子数组的长度必须是奇数
  • 思路分析:由于要求长度必须是奇数,也就是需要记录奇数和偶数的情况,所以我们采用动态规划的思想:
    • dp0[i]表示以nums[i]结尾的偶数长度的子数组的最大和
    • dp1[i]表示以nums[i]结尾的奇数长度的子数组的最大和
    • 递推公式dp0[i] = dp1[i-1] + nums[i]dp1[i] = max(dp0[i-1] + nums[i],nums[i])
class Solution {
public:int maxSubArray(vector<int>& nums) {// 如果求解的是长度必须是奇数的最大子数组和的问题int n = nums.size();// 使用动态规划// dp0[i]表示以nums[i]结尾长度为偶数的最大子数组和// dp1[i]表示以nums[i]结尾长度为奇数的最大子数组和vector<int> dp0(n),dp1(n);dp0[0] = -0x3f3f3f3f;dp1[0] = nums[0];int ans = dp1[0];for (int i = 1;i < n;i++){dp0[i] = dp1[i-1] + nums[i];dp1[i] = max(dp0[i-1] + nums[i],nums[i]);ans = max(ans,dp1[i]);}return ans;}
};
http://www.lryc.cn/news/604145.html

相关文章:

  • Mysql缓冲池和LRU
  • Accessibility Insights for Windows 使用教程
  • Adv. Sci. 前沿:非零高斯曲率3D结构可逆转换!液晶弹性体多级形变新策略
  • Javaweb————HTTP请求头属性讲解
  • [leetcode] 电话号码的排列组合
  • Vue El 基础
  • PyTorch 数据类型和使用
  • 第二课 P-MOS管应用
  • LRU(Least Recently Used)原理及算法实现
  • 【SQL】Windows MySQL 服务查询启动停止自启动(保姆级)
  • DAY27 函数专题2:装饰器
  • Android 解决键盘遮挡输入框
  • 老年护理实训室建设方案:打造安全、规范、高效的实践教学核心平台
  • C++ 编程问题记录
  • 对象的创建过程
  • Linux学习--C语言(指针4、结构体)
  • 【Spring】日志级别的分类和使用
  • Qt小技巧 QStandardPaths详解
  • C语言14-指针4-二维数组传参、指针数组传参、viod*指针
  • JAVA_TWENTY—ONE_单元测试+注解+反射
  • 在 Cloudflare 平台上完整部署 GitHub 项目 MoonTV 实现免费追剧流程
  • vite + chalk打印输出彩色命令行
  • 并查集介绍及典型应用和编程题
  • Python爬虫01_Requests第一血获取响应数据
  • __getattr__和 __getattribute__ 的用法
  • Docker学习相关视频笔记(二)
  • linux内核报错汇编分析
  • 云原生周刊:2025年的服务网格
  • JSON-RPC 2.0 规范
  • fastjson反序列化时_id的处理