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

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

作者推荐

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

涉及知识点

单调栈 动态规划 map

题目

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

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

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

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

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

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

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

示例 1:

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

输出:2

解释:

从起始索引 i = 0 出发,我们可以跳到 i = 2,(因为 A[2] 是 A[1],A[2],A[3],A[4] 中大于或等于 A[0] 的最小值),然后我们就无法继续跳下去了。

从起始索引 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,因为 A[1] 是(A[1],A[2],A[3],A[4])中大于或等于 A[0] 的最小值。

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

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

我们不能从 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 <= A[i] < 100000

代码

单调栈

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

变量函数解析

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

Next奇数跳为例

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

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

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

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

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

核心代码

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;
};

测试用例

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的键对应arr[i],map的值对应i。如果arr[i],i小的(后加入的)覆盖前面的。

时间复杂度:O(nlogn)。

map

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

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;
   }
 };


测试环境

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

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

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

相关文章
|
4月前
|
索引 Python
【Leetcode刷题Python】328. 奇偶链表
在不使用额外空间的情况下,将链表中的奇数和偶数索引节点重新排序的方法,并提供了相应的Python实现代码。
43 0
|
2月前
【LeetCode 24】225.用队列实现栈
【LeetCode 24】225.用队列实现栈
15 0
|
2月前
|
算法
【LeetCode 23】232.用栈实现队列
【LeetCode 23】232.用栈实现队列
26 0
|
4月前
|
Python
【Leetcode刷题Python】946. 验证栈序列
LeetCode题目“946. 验证栈序列”的Python解决方案,通过模拟栈的压入和弹出操作来验证给定的两个序列是否能通过合法的栈操作得到。
35 6
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 30. 包含min函数的栈
本文提供了实现一个包含min函数的栈的Python代码,确保min、push和pop操作的时间复杂度为O(1)。
35 4
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 09. 用两个栈实现队列
使用两个栈实现队列的Python解决方案,包括初始化两个栈、实现在队列尾部添加整数的appendTail方法和在队列头部删除整数的deleteHead方法,以及相应的示例操作。
41 2
|
4月前
|
Python
【Leetcode刷题Python】232. 用栈实现队列
如何使用Python语言通过两个栈来实现队列的所有基本操作,包括入队(push)、出队(pop)、查看队首元素(peek)和判断队列是否为空(empty),并提供了相应的代码实现。
25 0
|
5月前
|
Python
155. 最小栈 力扣 python 空间换时间 o(1) 腾讯面试题
155. 最小栈 力扣 python 空间换时间 o(1) 腾讯面试题
|
3月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
4月前
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
128 2