map|动态规划|单调栈|LeetCode975:奇偶跳

作者推荐

【贪心算法】【中位贪心】.执行操作使频率分数最大

涉及知识点

单调栈 动态规划 map

题目

给定一个整数数组 A,你可以从某一起始索引出发,跳跃一定次数。在你跳跃的过程中,第 1、3、5... 次跳跃称为奇数跳跃,而第 2、4、6... 次跳跃称为偶数跳跃。

你可以按以下方式从索引 i 向后跳转到索引 j(其中 i < j):

在进行奇数跳跃时(如,第 1,3,5... 次跳跃),你将会跳到索引 j,使得 Ai <= Aj,Aj 是可能的最小值。如果存在多个这样的索引 j,你只能跳到满足要求的最小索引 j 上。

在进行偶数跳跃时(如,第 2,4,6... 次跳跃),你将会跳到索引 j,使得 Ai >= Aj,Aj 是可能的最大值。如果存在多个这样的索引 j,你只能跳到满足要求的最小索引 j 上。

(对于某些索引 i,可能无法进行合乎要求的跳跃。)

如果从某一索引开始跳跃一定次数(可能是 0 次或多次),就可以到达数组的末尾(索引 A.length - 1),那么该索引就会被认为是好的起始索引。

返回好的起始索引的数量。

示例 1:

输入:10,13,12,14,15

输出:2

解释:

从起始索引 i = 0 出发,我们可以跳到 i = 2,(因为 A2 是 A1,A2,A3,A4 中大于或等于 A0 的最小值),然后我们就无法继续跳下去了。

从起始索引 i = 1 和 i = 2 出发,我们可以跳到 i = 3,然后我们就无法继续跳下去了。

从起始索引 i = 3 出发,我们可以跳到 i = 4,到达数组末尾。

从起始索引 i = 4 出发,我们已经到达数组末尾。

总之,我们可以从 2 个不同的起始索引(i = 3, i = 4)出发,通过一定数量的跳跃到达数组末尾。

示例 2:

输入:2,3,1,1,4

输出:3

解释:

从起始索引 i=0 出发,我们依次可以跳到 i = 1,i = 2,i = 3:

在我们的第一次跳跃(奇数)中,我们先跳到 i = 1,因为 A1 是(A1,A2,A3,A4)中大于或等于 A0 的最小值。

在我们的第二次跳跃(偶数)中,我们从 i = 1 跳到 i = 2,因为 A2 是(A2,A3,A4)中小于或等于 A1 的最大值。A3 也是最大的值,但 2 是一个较小的索引,所以我们只能跳到 i = 2,而不能跳到 i = 3。

在我们的第三次跳跃(奇数)中,我们从 i = 2 跳到 i = 3,因为 A3 是(A3,A4)中大于或等于 A2 的最小值。

我们不能从 i = 3 跳到 i = 4,所以起始索引 i = 0 不是好的起始索引。

类似地,我们可以推断:

从起始索引 i = 1 出发, 我们跳到 i = 4,这样我们就到达数组末尾。

从起始索引 i = 2 出发, 我们跳到 i = 3,然后我们就不能再跳了。

从起始索引 i = 3 出发, 我们跳到 i = 4,这样我们就到达数组末尾。

从起始索引 i = 4 出发,我们已经到达数组末尾。

总之,我们可以从 3 个不同的起始索引(i = 1, i = 3, i = 4)出发,通过一定数量的跳跃到达数组末尾。

示例 3:

输入:5,1,3,4,2

输出:3

解释:

我们可以从起始索引 1,2,4 出发到达数组末尾。

提示:

1 <= A.length <= 20000

0 <= Ai < 100000

代码

单调栈

此方法比map 巧妙,性能差不多,值得学习。时间复杂度:O(nlogn)。

变量函数解析

indexs 计算奇数跳时,arrindex\[i] 升序,且相等的元素,相对顺序不变。计算偶数跳时,arrindex\[i] 降序,且相等的元素,相对顺序不变。
Next 计算奇(偶)数跳的下一个位置,如果无法跳,则值为m_c
vStatus 记录偶数(奇数)跳能否跳到队尾。vStatus0m_c和vStatus0m_c为false,避免处理边界条件

Next奇数跳为例

令j=indexjj,按jj从小到的顺序,将j入栈,由于arrindex\[jj]是升序,所以:规则一:arr栈中元素 <=arrj

(sta.top() < j 成立,说明:

规则二:j在sta.top()右边。

规则三:令indexjj2 为sta.top(),arrindex(jj2,j)中的数(即大于等于arrsta.top() 同时小于等于arrj的数)全部在sta.top()的左边,否则出栈了。

结合规则一二三,stat.top()的下一步就是j。

核心代码

cpp 复制代码
class Solution {
public:
	int oddEvenJumps(vector<int>& arr) {
		m_c = arr.size();
		vector<int> indexs(m_c);
		iota(indexs.begin(), indexs.end(), 0);
		sort(indexs.begin(), indexs.end(), [&](const int i1, const int i2) {return (arr[i1] < arr[i2]) || ((arr[i1] == arr[i2]) && (i1 < i2)); });
		const auto& v1 = Next(indexs);
		sort(indexs.begin(), indexs.end(), [&](const int i1, const int i2) {return (arr[i1] > arr[i2]) || ((arr[i1] == arr[i2]) && (i1 < i2)); });
		const auto& v2 = Next(indexs);

		vector<vector<bool>> vStatus(2, vector<bool>(m_c+1));
		int iRet = 1;
		vStatus[0][m_c-1] = true;
		vStatus[1][m_c - 1] = true;
		for (int i = m_c - 1 - 1; i >= 0; i--)
		{
			vStatus[0][i] = vStatus[1][v2[i]];//偶数跳
			vStatus[1][i] = vStatus[0][v1[i]];//奇数跳
			iRet +=  (int)vStatus[1][i];
		}
		return iRet;
	}
	vector<int> Next(const vector<int>& indexs)
	{
		vector<int> vNext(indexs.size(), indexs.size());
		stack<int> sta;
		for (int j : indexs)
		{
			while (sta.size() && (sta.top() < j))
			{
				vNext[sta.top()] = j;
				sta.pop();
			}
			sta.emplace(j);
		}
		return vNext;
	}
	int m_c;
};

测试用例

cpp 复制代码
template<class T>
void Assert(const T& t1, const T& t2)
{
	assert(t1 == t2);
}

template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
	if (v1.size() != v2.size())
	{
		assert(false);
		return;
	}
	for (int i = 0; i < v1.size(); i++)
	{
		Assert(v1[i], v2[i]);
	}
}

int main()
{

	vector<int> arr;
	{
		Solution slu;
		arr = { 10,13,12,14,15 };
		auto res = slu.oddEvenJumps(arr);
		Assert(2, res);
	}
	{
		Solution slu;
		arr = { 2,3,1,1,4 };
		auto res = slu.oddEvenJumps(arr);
		Assert(3, res);
	}
	{
		Solution slu;
		arr = { 5,1,3,4,2 };
		auto res = slu.oddEvenJumps(arr);
		Assert(3, res);
	}
	
	
	//CConsole::Out(res);
}

2023年3月版:map

利用map 性能和单调栈差不多,好理解。从后向前遍历各元素,map的键对应arri,map的值对应i。如果arri,i小的(后加入的)覆盖前面的。
时间复杂度:O(nlogn)。

map

map可以分成有序(单调)map和无序(哈希)map。还可分成单键map和多键map(允许重复的键)。

cpp 复制代码
 class Solution {
 public:
	 int oddEvenJumps(vector<int>& arr) {
		 vector<vector<bool>> result;
		 result.assign(arr.size(), vector<bool>(2));
		 result[arr.size() - 1][0] = true;
		 result[arr.size() - 1][1] = true;
		 std::map<int, int> mValueIndex;
		 mValueIndex[arr.back()] = arr.size()-1;
		 for (int i = arr.size() - 2; i >= 0; i--)
		 {
			 {//奇数跳跃
				 auto it = mValueIndex.lower_bound(arr[i]);
				 if (mValueIndex.end() != it)
				 {
					 result[i][0] = result[it->second][1];
				 }
			 }
			 {//偶数跳跃
			 auto it2 = mValueIndex.upper_bound(arr[i]);
			 if (mValueIndex.begin() != it2)
			 {
				 --it2;
				 result[i][1] = result[it2->second][0];
			 }
			 mValueIndex[arr[i]] = i;
		 }
		 }
		 int iNum = 0;
		 for (int i = 0; i < arr.size(); i++)
		 {
			 if (result[i][0])
			 {
				 iNum++;
			 }
		 }
		 return iNum;
	 }
 };

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步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++ 实现。

相关推荐
徐小夕15 分钟前
万字长文!千万级文档 RAG 知识库系统落地实践
前端·算法·github
akunkuntaimei30 分钟前
2026年高考数学各省真题及答案(完整版)
算法·高考
Hello:CodeWorld1 小时前
C 风格变参 vs C++ 变参模板:核心区别与选型指南
c语言·c++·算法
8Qi82 小时前
LeetCode 516:最长回文子序列
算法·leetcode·职场和发展·动态规划
搬砖魁首3 小时前
基础能力系列 - 多线程2 - 条件变量
c++·rust·条件变量·原子类型·线程同步互斥
youngerwang4 小时前
【从搬运工到协处理器:网卡芯片架构、算法、验证与边缘演进深度剖析】
网络·算法·架构·芯片
chase_my_dream4 小时前
C++ + SLAM 高频面试问题整理
开发语言·c++·面试
牛油果子哥q4 小时前
【C++ STL string 】C++ STL string 终极精讲:底层原理、内存机制、全套API、深浅拷贝、易错坑点与工程实战规范
数据库·c++
KaMeidebaby4 小时前
卡梅德生物技术快报|纯化重组蛋白实操详解
人工智能·python·tcp/ip·算法·机器学习
手写码匠5 小时前
从零实现 Prompt 工程引擎:结构化提示、自动优化与多轮自省体系
人工智能·深度学习·算法·aigc