前缀和+单调双队列+贪心:LeetCode2945:找到最大非递减数组的长度

简介: 前缀和+单调双队列+贪心:LeetCode2945:找到最大非递减数组的长度

题目

给你一个下标从 0 开始的整数数组 nums 。

你可以执行任意次操作。每次操作中,你需要选择一个 子数组 ,并将这个子数组用它所包含元素的 和 替换。比方说,给定数组是 [1,3,5,6] ,你可以选择子数组 [3,5] ,用子数组的和 8 替换掉子数组,然后数组会变为 [1,8,6] 。

请你返回执行任意次操作以后,可以得到的 最长非递减 数组的长度。

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

示例 1:

输入:nums = [5,2,2]

输出:1

解释:这个长度为 3 的数组不是非递减的。

我们有 2 种方案使数组长度为 2 。

第一种,选择子数组 [2,2] ,对数组执行操作后得到 [5,4] 。

第二种,选择子数组 [5,2] ,对数组执行操作后得到 [7,2] 。

这两种方案中,数组最后都不是 非递减 的,所以不是可行的答案。

如果我们选择子数组 [5,2,2] ,并将它替换为 [9] ,数组变成非递减的。

所以答案为 1 。

示例 2:

输入:nums = [1,2,3,4]

输出:4

解释:数组已经是非递减的。所以答案为 4 。

示例 3:

输入:nums = [4,3,2,6]

输出:3

解释:将 [3,2] 替换为 [5] ,得到数组 [4,5,6] ,它是非递减的。

最大可能的答案为 3 。

参数范围

1 <= nums.length <= 105

1 <= nums[i] <= 105

枚举最后一个子数组nums(j,i]

假定结果向量为vRet

nums[0,j] 需要记录两个子状态:最有一个子数组的和,vRet的长度(操作前的子数组数量)。枚举这些状态时间复杂度O(n),枚举i时间复杂度O(n),枚举j时间复杂度O(n)。故总时间复杂度o(n3)。合并nums[0,j]和nums(j,i]有两种操作:

操作一:nums(j,i]全部合并到vRet[j]的最后一个元素。无前提条件。

操作二:nums(j,i]成为新元素。前天条件nums(j,i]大于vRet[j]的最后一个元素。

贪心:不存在len > len2且v1 > v2

令nums[0,i)合法分成n段,最后一段的值为fin,即分成一段,最后的值为fi1,分成两段,最后的值为fi2。

令nums[0,j)…fjn。

证明:对于任意数组(子数组),fxn 一定 大与等于fxn+1。x是j,j等…

n==1: fx1 为整个数组的和,fx2为数组第二部分的和。显然成立。

n >1 用反证法:

假定可以合法分成n段,分别为{a1,a2…an}

假定可以合法分成n+1段:分别为(b1,b2…bn,bn+1}。bn+1可能有多个值,取最小值

假定an < bn+1,则{a1,a2,…an-1} 和大于{b1…bn}的和 => 假定前者包括nums[0,i)后者包括nums[0,j) => i >j

则{b1,b2…bn+nums[j,i)}是nums[0,i)的一个合法分成n段,{a1…an-1} 是nums[0,i)一个合法n-1段。

bn+nums[j,i)就是fin, an-1,就是fn-1 fxn-1 >= fnx =>an-1 > bn+nums[j,i)

因为an >= an-1 => {b1,b2…bn+nums[j,i),an} 是一个nums的n+1段划分。

结合假设:an

优化后时间复杂度O(n)

如果nums[0,i)存在长度len,则nums[0,i]一定存在长度为len的结果:将nums[i]追加到最后。

判断nums[0,i]能否则在nums[0,j] 增加一个元素(nums(j,i]的和),需要判断

vPreSum[i+1] - vPreSum[j+1] >= Last[j]

即vPreSump[i+1] >= Last[j]+vPreSum[j+1]

Last[j] 是最后元素的值

Last[j]+vPreSum[j+1] 可以合并一个变量llPre。已知状态只需要保留最大长度,长度相同保留llPre最小。

此解法错误,还在摸索中。

class Solution {
public:
  int findMaximumLength(vector<int>& nums) {
    m_c = nums.size();
    auto vPreSum = CreatePreSum(nums);
    int len = 0;
    long long llPre = 0;
    int preIndex = -1;
    for (int i = 0; i < m_c; i++)
    {
      if (vPreSum[i + 1] >= llPre)
      {
        len++;
        llPre = vPreSum[i+1] + (vPreSum[i + 1] - vPreSum[preIndex + 1]);
        preIndex = i;
      }
      else
      {
        const long long curAdd  = vPreSum[i+1] - vPreSum[preIndex+1] + (vPreSum[i + 1] - vPreSum[preIndex + 1]);
        if (curAdd < 0)
        {
          llPre += curAdd;
          preIndex = i;
        }
      }
    }
    return len;
  }
  int m_c;
};

正确解法

下标从小到大遍历nums[i],queIndexs记录[0,i),淘汰以下:

一,j1 < j2,也就(j1,i]的和大于(j2,i]。也就是j1的最后一个数大于j2的最后一个数。llPre1 大于llPre2,也就llPre1更难匹配,淘汰j1。淘汰后:下标升序 llPre 降序。

二,j找到第一个i后,淘汰。假定j的长度为len,i1 < i2。i1可以选择{… (j,i1],(i1,i2]} 和{…{j,i2]},而i2只能选择后者,所以i1不劣与i2。

注意:如果无法长度+1,则vLast[i] = vLast[i - 1] + nums[i]

队首元素的长度和队尾元素的长度相差不会超过1

双向队列中的下标升序,也就是长度升序。

假定新入队的长度是len,则被淘汰的队首长度为len-1。由于是升序,所以队中元素的长度都大于等于len-1,小于等于len

template<class T = long long >
vector<T> CreatePreSum(const vector<int>& nums)
{
  vector<T> preSum;
  preSum.push_back(0);
  for (int i = 0; i < nums.size(); i++)
  {
    preSum.push_back (preSum[i]+ nums[i]);
  }
  return preSum;
}
class Solution {
public:
  int findMaximumLength(vector<int>& nums) {
    m_c = nums.size();
    auto vPreSum = CreatePreSum(nums);
    vector<int> vRet(m_c,1),vLast(m_c,nums.front());
    std::deque<int> queIndexs;
    queIndexs.emplace_back(0);
    auto Pre = [&](const int index) {return vPreSum[index + 1] + vLast[index];  };
    for (int i = 1; i < m_c; i++)
    {
      vRet[i] = vRet[i - 1];
      vLast[i] = vLast[i - 1] + nums[i];
      while (queIndexs.size() && (vPreSum[i + 1] >= Pre(queIndexs.front())))
      {
        vRet[i] = vRet[queIndexs.front()]+1;
        vLast[i] = vPreSum[i + 1] - vPreSum[queIndexs.front() + 1];
        queIndexs.pop_front();
      }
      while (queIndexs.size() && (Pre(queIndexs.back()) >= Pre(i)))
      {
        queIndexs.pop_back();
      }
      queIndexs.emplace_back(i);
    }
    return vRet.back();
  }
  int m_c;
};

错误解法

class Solution {
public:
int findMaximumLength(vector& nums) {
stack sta;
int i = 0;
while (i < nums.size())
{
long long cur = 0;
while (sta.size() && (i < nums.size()) && (sta.top() > cur + nums[i]))
{
cur += nums[i++];
}
if (i >= nums.size())
{
break;//理论上需要栈顶的值,由于我需要的是数量,所以可以不修改
}
sta.emplace(cur+ nums[i++]);
}
return sta.size();
}
};


扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。

https://edu.csdn.net/course/detail/38771

如何你想快

速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程

https://edu.csdn.net/lecturer/6176

相关

下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版

https://download.csdn.net/download/he_zhidan/88348653

测试环境

操作系统:win7 开发环境: VS2019 C++17

或者 操作系统:win10 开发环境: VS2022 C++17

如无特殊说明,本算法C++ 实现。

相关文章
|
3月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
51 0
|
5月前
|
算法
LeetCode第53题最大子数组和
LeetCode第53题"最大子数组和"的解题方法,利用动态规划思想,通过一次遍历数组,维护到当前元素为止的最大子数组和,有效避免了复杂度更高的暴力解法。
LeetCode第53题最大子数组和
|
5月前
|
存储 Java API
LeetCode------合并两个有序数组(4)【数组】
这篇文章介绍了LeetCode上的"合并两个有序数组"问题,并提供了三种解法:第一种是使用Java的Arrays.sort()方法直接对合并后的数组进行排序;第二种是使用辅助数组和双指针技术进行合并;第三种则是从后向前的双指针方法,避免了使用额外的辅助数组。
LeetCode------合并两个有序数组(4)【数组】
|
5月前
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
133 2
|
5月前
|
Python
【Leetcode刷题Python】剑指 Offer 03. 数组中重复的数字
解决剑指Offer题目 "数组中重复的数字" 的Python实现方法,通过使用字典来记录数组中每个数字的出现次数,快速找出重复的数字。
45 1
LeetCode------找到所有数组中消失的数字(6)【数组】
这篇文章介绍了LeetCode上的"找到所有数组中消失的数字"问题,提供了一种解法,通过两次遍历来找出所有未在数组中出现的数字:第一次遍历将数组中的每个数字对应位置的值增加数组长度,第二次遍历找出所有未被增加的数字,即缺失的数字。
|
5月前
|
前端开发
LeetCode------移动零(5)【数组】
这篇文章介绍了LeetCode上的"移动零"问题,提出了一种使用双指针的原地操作解法,该方法首先将非零元素移动到数组前端并保持相对顺序,然后填充后续位置为零,以达到题目要求。
|
3月前
【LeetCode-每日一题】 删除排序数组中的重复项
【LeetCode-每日一题】 删除排序数组中的重复项
28 4
|
3月前
|
索引
Leetcode第三十三题(搜索旋转排序数组)
这篇文章介绍了解决LeetCode第33题“搜索旋转排序数组”的方法,该问题要求在旋转过的升序数组中找到给定目标值的索引,如果存在则返回索引,否则返回-1,文章提供了一个时间复杂度为O(logn)的二分搜索算法实现。
28 0
Leetcode第三十三题(搜索旋转排序数组)
|
3月前
|
算法 C++
Leetcode第53题(最大子数组和)
这篇文章介绍了LeetCode第53题“最大子数组和”的动态规划解法,提供了详细的状态转移方程和C++代码实现,并讨论了其他算法如贪心、分治、改进动态规划和分块累计法。
79 0