📜  给定数组的最长 ZigZag 子数组的长度(1)

📅  最后修改于: 2023-12-03 15:27:35.584000             🧑  作者: Mango

给定数组的最长 ZigZag 子数组的长度

问题描述

给定一个整数数组 nums ,返回该数组中最长的 ZigZag 子数组的长度。一个 ZigZag 子数组可以通过交替的正负数构造,其中第一个数和最后一个数都是正数。

例子

输入: nums = [1,7,4,9,2,5] 输出: 6 解释: 整个数组都是 wiggle 序列,因为差分数组中的元素来回切换。

输入: nums = [1,17,5,10,13,15,10,5,16,8] 输出: 7 解释: 这个子数组是 wiggle 序列:[5,10,13,15,10,5,16]。

输入: nums = [1,2,3,4,5,6,7,8,9] 输出: 2

解决方案
动态规划

状态表示

我们可以用 $up[i]$ 表示第 $i$ 个数为结尾的最长的 ZigZag 子数组的长度,其中最后一个元素是个正数且上一个元素是个负数。我们还可以用 $down[i]$ 表示第 $i$ 个数为结尾的最长的 ZigZag 子数组的长度,其中最后一个元素是个负数且上一个元素是个正数。

状态转移

对于第 $i$ 个元素 $nums[i]$,我们可以分为以下两种情况:

  • $nums[i]>nums[i-1]$,即 $nums[i]$ 是正数

    • 如果此时上一个元素的状态是 $down[i-1]$,说明在以 $i-1$ 结尾的最长 ZigZag 子数组中,最后两个元素是负数 $nums[i-2]$ 和 $nums[i-1]$,当前加入的 $nums[i]$ 能够构成一个新的 ZigZag 子数组,因此我们应该更新 $up[i]=down[i-1]+1$。
    • 否则,$up[i]$ 与 $up[i-1]$ 相等,因为当前元素和上一个元素都是正数。
  • $nums[i]<nums[i-1]$,即 $nums[i]$ 是负数。与情况1类似,我们可以分以下两种情况:

    • 如果此时上一个元素的状态是 $up[i-1]$,说明在以 $i-1$ 结尾的最长 ZigZag 子数组中,最后两个元素是正数 $nums[i-2]$ 和 $nums[i-1]$,当前加入的 $nums[i]$ 能够构成一个新的 ZigZag 子数组,因此我们应该更新 $down[i]=up[i-1]+1$。
    • 否则,$down[i]$ 与 $down[i-1]$ 相等,因为当前元素和上一个元素都是负数。

最终我们的答案就是 $\max{up[i],down[i]}$。

时间复杂度

状态转移需要遍历整个数组,所以时间复杂度为 $O(n)$。

代码实现

class Solution:
    def wiggleMaxLength(self, nums: List[int]) -> int:
        n = len(nums)
        if n < 2:
            return n

        up, down = [0] * n, [0] * n
        up[0], down[0] = 1, 1

        for i in range(1, n):
            if nums[i] > nums[i-1]:
                up[i] = down[i-1] + 1
                down[i] = down[i-1]
            elif nums[i] < nums[i-1]:
                down[i] = up[i-1] + 1
                up[i] = up[i-1]
            else:
                up[i], down[i] = up[i-1], down[i-1]

        return max(up[-1], down[-1])
贪心算法

思路

我们可以通过观察数组中的数字序列来发现一些规律。具体来说,我们可以发现,如果当前数字和前一个数字构成的变化是符合ZigZag的定义的,即当前数字和前一个数字的差是正数,那么这个新数字可以加入到ZigZag子数组中。否则,如果当前数字和前一个数字的差是负数,那么这个新数字可以成为ZigZag子数组中的另一个数(因为它是正数),但是它不能与前一个数字构成一个新的ZigZag子数组。

算法描述

我们可以定义一个变量 flag ,来表示正在构建的子数组的方向,如果 flag=1 表示当前正在构建的子数组是up,即最后一个元素比上一个元素大,反之为down。

我们可以看一下下面的例子:

nums = [1, 13, 7, 5, 3, 1, 2, 4, 6, 8, 10]

假设我们已经找到了第一个up子数组 [1,13],即我们已经知道了子数组的方向,接下来我们需要找到下一个数字:

  • 如果下一个数字比最后一个数字小,那么下一个数字可以加入到子数组中,因为我们在构建ZigZag子数组时需要使用交替的正负数.

  • 如果下一个数字比最后一个数字大,我们需要终止当前的子数组,然后从当前数字开始构建一个新的down子数组,并把当前数字作为这个新子数组的第二个元素。注意,这里我们不能加入上一个数字,因为上一个数字是正数,不能与下一个数字构成新的zigzag子数组。

  • 如果下一个数字与上一个数字相等,则忽略,继续往下寻找。

然后我们就可以继续往下寻找,重复上述过程,直到我们找到了一个下降的数字并且我们需要构造一个新的up子数组。

代码实现

class Solution:
    def wiggleMaxLength(self, nums: List[int]) -> int:
        n = len(nums)
        if n < 2:
            return n

        flag, ans = 0, 1
        for i in range(1, n):
            if nums[i] > nums[i-1] and (flag == 0 or flag == -1):
                ans += 1
                flag = 1
            elif nums[i] < nums[i-1] and (flag == 0 or flag == 1):
                ans += 1
                flag = -1

        return ans

时间复杂度

时间复杂度为$O(n)$。