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

本文涉及知识点

C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频

单调双队列 贪心

题目

给你一个下标从 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 <= 10^5^

1 <= nums[i] <= 10^5^

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

假定结果向量为vRet

nums[0,j] 需要记录两个子状态:最有一个子数组的和,vRet的长度(操作前的子数组数量)。枚举这些状态时间复杂度O(n),枚举i时间复杂度O(n),枚举j时间复杂度O(n)。故总时间复杂度o(n^3^) 。合并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 <bn+1 和 bn+1是最小值矛盾

优化后时间复杂度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最小。

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

cpp 复制代码
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

cpp 复制代码
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++ 实现。

相关推荐
网易独家音乐人Mike Zhou3 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
‘’林花谢了春红‘’4 小时前
C++ list (链表)容器
c++·链表·list
机器视觉知识推荐、就业指导6 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
Swift社区7 小时前
LeetCode - #139 单词拆分
算法·leetcode·职场和发展
Kent_J_Truman7 小时前
greater<>() 、less<>()及运算符 < 重载在排序和堆中的使用
算法
IT 青年8 小时前
数据结构 (1)基本概念和术语
数据结构·算法
Yang.998 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
熬夜学编程的小王8 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
zz40_8 小时前
C++自己写类 和 运算符重载函数
c++