978.最长湍流子数组

[TOC]

一、题目描述

当 的子数组 满足下列条件时,我们称其为湍流子数组:

  • 若 ,当 为奇数时, ,且当 为偶数时,;
  • 若 ,当 为偶数时, ,且当 为奇数时, 。

也就是说,如果比较符号在子数组中的每个相邻元素对之间翻转,则该子数组是湍流子数组。
返回 A 的最大湍流子数组的长度

示例 1:

输入:[9,4,2,10,7,8,8,1,9]
输出:5
解释:(A[1] > A[2] < A[3] > A[4] < A[5])

示例 2:

输入:[4,8,12,16]
输出:2

示例 3:

输入:[100]
输出:1

提示:

1 <= A.length <= 40000
0 <= A[i] <= 10^9

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-turbulent-subarray
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

二、解题方法

1. 动态规划


思路:

八个月前的坑了,当时拿到这题除了莽也没想到有啥手段,现在第一反应就应该是动态规划啊。

令表示以结尾的湍流子数组的最大长度,

根据湍流子数组的定义:

  • 当时,显然必须作为一个新的湍流子数组的第一个元素,因而有;
  • 当时,显然是一个新的湍流子数组的第一个元素,
    若,我们就可以用和组成一个湍流子数组,因而有;
  • 当时(同理),显然是某个湍流子数组的结尾,
    若,我们可以用续接这个湍流子数组,因而有,
    反之,若,则只能由和构成长度为的湍流子数组,因而有。

这样的描述写成if-else的结构应该挺方便的,但太多判断条件看着难受啊,怎么办?
或许我们可以定义一个新的数组,令表示的符号,依次使用表示。

重新整理一下上面的分类讨论:

  • 若,显然必须作为一个新的湍流子数组的第一个元素,因而有;
  • 若且,我们就可以用和组成一个湍流子数组,因而有;
  • 若且,则只能由和构成长度为的湍流子数组,因而有;
  • 若且,我们可以用续接这个湍流子数组,因而有。

嗯……虽然看起来比第一种分类讨论里面那么一大串来的好了一点,但其实看着也不是很舒服?
要不我们再看看能不能简化一下吧?
这么说起来,我们定义了,但第二种分类讨论里只显式的使用了的情况,而和并没有显式的利用起来。

再整理一下上面的分类讨论:

  • 若且,显然必须作为一个新的湍流子数组的第一个元素,因而有;
  • 若且,只能由和构成长度为的湍流子数组,因而有;
  • 若,我们可以用续接这个湍流子数组,因而有。

来了来了!就是这个!我们终于得到了状态转移方程:
\begin{equation} dp[i]= \begin{cases} 1, & sign[i-1] \times sign[i] \ne -1 \quad \text{and} \quad sign[i] = 0\\ 2, & sign[i-1] \times sign[i] \ne -1 \quad \text{and} \quad sign[i] \ne 0\\ dp[i-1] + 1, & sign[i-1] \times sign[i] = -1 \end{cases} \end{equation}

一个典型的计算过程例子:

同时我们注意到,求解仅需要、和,因此我们可以利用状态压缩来节约内存
具体到下述代码,我们使用表示,表示,表示。

时间复杂度:,空间复杂度:。


代码:

class Solution {
public:
    int maxTurbulenceSize(vector& A) {
        auto ans = 0, len = 1, prev = 0, cur = 0;
        for (auto i = 1; i < A.size(); ++i) {
            auto diff = A[i - 1] - A[i];
            cur = (diff > 0) ? 1 : (diff == 0) ? 0 : -1;
            if (prev * cur != -1) {
                ans = max(ans, len);
                len = cur == 0 ? 1 : 2;
            }
            else
                len = len + 1;
            prev = cur;
        }
        return max(ans, len);
    }
};

2. 滑动窗口


思路:

我们可以将每一个湍流子数组看做一个个小的窗口,分割效果如下图所示:


其中,定义同方法1中的。

滑动窗口的思路很简单,
当我们确定了窗口的左端点以后,不断从左端点往右扫描,直到确定当前窗口的右端点(即湍流子数组无法再扩张),再将当前窗口的右端点当做下一个窗口的左端点即可。

最初的左端点显然是毋庸置疑,设为当前正在扫描的元素,我们如何知道是否为当前窗口的右端点呢?
其实很简单,
当时,只要,就说明已经无法再满足湍流子数组的定义了,而我们是从左往右扫描的,则必然是当前窗口的右端点;
反之,若,则有,它并不满足湍流子数组,因此在这种情况下我们不能将看作是一个有效的窗口的右端点。

唯一需要注意的是因为在计算时,需要使用,所以数组右边界需要单独讨论。

时间复杂度:,空间复杂度:。


代码:

class Solution {
public:
    int maxTurbulenceSize(vector& A) {
        if (A.size() < 2)      return A.size();

        auto ans = 1, begin = 0;
        for (auto i = 1; i < A.size() - 1; i++) {
            auto lhs = compare(A[i - 1], A[i]);
            auto rhs = compare(A[i], A[i + 1]);
            if (lhs * rhs != -1) {
                if (lhs != 0)   ans = max(ans, i - begin + 1);
                begin = i;
            }
        }
        auto lhs = compare(A[A.size() - 2], A[A.size() - 1]);
        if (lhs != 0)   ans = max(ans, int(A.size()) - 1 - begin + 1);
        return ans;
    }

    int inline compare(int lhs, int rhs) {
        return lhs < rhs ? -1 : lhs == rhs ? 0 : 1;
    }
};

你可能感兴趣的:(978.最长湍流子数组)