【LeetCode 每日一题】3640. 三段式数组 II——(解法二)DP

Problem: 3640. 三段式数组 II

文章目录

  • [1. 整体思路](#1. 整体思路)
  • [2. 完整代码](#2. 完整代码)
  • [3. 时空复杂度](#3. 时空复杂度)
      • [时间复杂度: O ( N ) O(N) O(N)](#时间复杂度: O ( N ) O(N) O(N))
      • [空间复杂度: O ( 1 ) O(1) O(1)](#空间复杂度: O ( 1 ) O(1) O(1))

1. 整体思路

核心问题

我们需要找一个子数组,满足 升 -> 降 -> 升 的形状,并使元素和最大。

状态定义

我们可以将这个波形结构拆解为三个阶段(状态机):

  1. 阶段 1 (Rise 1) :正在经历第一段上升。
    • 状态 f1:表示以当前元素 nums[i] 结尾的、处于第一段上升阶段的最大子数组和。
  2. 阶段 2 (Fall) :正在经历中间的下降。
    • 状态 f2:表示以当前元素 nums[i] 结尾的、处于下降阶段的最大子数组和。
    • 它可以从 f1 转移而来(刚开始下降,即峰值转折),也可以从之前的 f2 延续而来(继续下降)。
  3. 阶段 3 (Rise 2) :正在经历第二段上升。
    • 状态 f3:表示以当前元素 nums[i] 结尾的、处于第二段上升阶段的最大子数组和。
    • 它可以从 f2 转移而来(刚开始上升,即谷值转折),也可以从之前的 f3 延续而来(继续上升)。

状态转移

遍历数组,对于每一对相邻元素 x = nums[i-1] 和 y = nums[i]:

  1. 更新 f3 (第二段上升):

    • 只有当 x < y (上升) 时,才能处于此状态。
    • 来源 A:从谷底转折而来。前一个状态是 f2 (下降段结尾)。此时 y 是上升的第一步。总和 = f2 + y。
    • 来源 B:延续上升。前一个状态是 f3。总和 = f3 + y。
    • 方程:f3 = (x < y) ? Math.max(f3, f2) + y : NEG_INF。
    • 注意 :这里用的是更新前的 f2 和 f3(上一轮的值)。但在代码中,由于是顺序更新,我们需要小心变量覆盖问题。
  2. 更新 f2 (下降):

    • 只有当 x > y (下降) 时,才能处于此状态。
    • 来源 A:从峰顶转折而来。前一个状态是 f1 (第一段上升结尾)。此时 y 是下降的第一步。总和 = f1 + y。
    • 来源 B:延续下降。前一个状态是 f2。总和 = f2 + y。
    • 方程:f2 = (x > y) ? Math.max(f2, f1) + y : NEG_INF。
  3. 更新 f1 (第一段上升):

    • 只有当 x < y (上升) 时,才能处于此状态。
    • 来源 A:新的开始。子数组只包含 x 和 y。总和 = x + y。
    • 来源 B:延续上升。前一个状态是 f1。总和 = f1 + y。
    • 方程:f1 = (x < y) ? Math.max(f1, x) + y : NEG_INF。
    • 注意 :这里 Math.max(f1, x) 的 x 其实代表的是 x 作为起点的 sum,即之前的 sum 是 x。
  4. 全局最优:

    • 每次更新完 f3 后,尝试更新 ans。

关键细节 :

在代码中,f3, f2, f1 是按顺序更新的。

  • 更新 f3 时用到的是旧的 f3 和 旧的 f2。
  • 更新 f2 时用到的是旧的 f2 和 旧的 f1。
  • 更新 f1 时用到的是旧的 f1。
  • 只要按 f3 -> f2 -> f1 的倒序更新,就可以直接使用变量而不需要临时变量保存上一轮状态。

2. 完整代码

java 复制代码
class Solution {
    public long maxSumTrionic(int[] nums) {
        // 定义无穷小。使用 Long.MIN_VALUE / 2 是为了防止在后续加法运算中溢出变成正数
        final long NEG_INF = Long.MIN_VALUE / 2;
        
        long ans = NEG_INF;
        
        // f1: 以当前元素结尾,处于第一上升段的最大和
        long f1 = NEG_INF;
        // f2: 以当前元素结尾,处于下降段的最大和
        long f2 = NEG_INF;
        // f3: 以当前元素结尾,处于第二上升段的最大和
        long f3 = NEG_INF;

        // 从第二个元素开始遍历
        for (int i = 1; i < nums.length; i++) {
            int x = nums[i - 1]; // 前一个数
            int y = nums[i];     // 当前数
            
            // 1. 更新 f3 (第二段上升状态)
            // 必须满足上升条件 x < y
            // 转移来源:
            // - f2: 刚从下降段转折过来 (谷底)
            // - f3: 延续之前的第二上升段
            // 注意:这里使用的是上一轮循环的 f2 和 f3
            if (x < y) {
                f3 = Math.max(f3, f2) + y;
            } else {
                // 如果不满足上升,说明无法维持 f3 状态,重置为无效
                f3 = NEG_INF;
            }

            // 2. 更新 f2 (下降状态)
            // 必须满足下降条件 x > y
            // 转移来源:
            // - f1: 刚从第一上升段转折过来 (峰顶)
            // - f2: 延续之前的下降段
            if (x > y) {
                f2 = Math.max(f2, f1) + y;
            } else {
                // 如果不满足下降,重置为无效
                f2 = NEG_INF;
            }

            // 3. 更新 f1 (第一段上升状态)
            // 必须满足上升条件 x < y
            // 转移来源:
            // - x: 新开启一段上升 (丢弃之前的,只取 x 和 y)
            // - f1: 延续之前的第一上升段
            if (x < y) {
                // Math.max(f1, x) 的意思是:
                // 要么接在之前的上升段后面 (f1 + y)
                // 要么重新开始,只包含前一个数和当前数 (x + y)
                // 这里利用了 Math.max(f1 + y, x + y) = Math.max(f1, x) + y 的分配律
                f1 = Math.max(f1, x) + y;
            } else {
                f1 = NEG_INF;
            }
            
            // 每次迭代尝试更新全局最大值
            // 只有当 f3 有效时 (即我们已经完整经历了 升-降-升) 才有资格更新 ans
            ans = Math.max(ans, f3);
        }
        
        return ans;
    }
}

3. 时空复杂度

假设数组 nums 的长度为 N N N。

时间复杂度: O ( N ) O(N) O(N)

  • 计算依据 :
    • 代码包含单次循环,遍历整个数组。
    • 循环体内的操作(比较、加法、赋值)均为 O ( 1 ) O(1) O(1)。
  • 结论 : O ( N ) O(N) O(N)。

空间复杂度: O ( 1 ) O(1) O(1)

  • 计算依据 :
    • 只使用了 5 个变量 (ans, f1, f2, f3, i)。
    • 没有使用 DP 数组,而是使用了滚动变量优化空间。
  • 结论 : O ( 1 ) O(1) O(1)。
相关推荐
垆边人似月.1 分钟前
华为题:敏感词替换
算法
0+11110 分钟前
算法 --哈希表
数据结构·算法·散列表
Interview Aid1121 小时前
Stripe VO 面经汇总:5 轮面试题目与考察重点
算法
EEET智选1 小时前
科技晨间速报 | AI 安全警钟、开源新模型与苹果智能家居
人工智能·科技·算法·机器学习·ai
挖掘狂人1 小时前
哈希算法:一根看不见的 "指纹",如何撑起整个互联网
算法·程序员·掘金技术征文
鶴哥只手遮天1 小时前
ecRad 1.5.0 算法原理详解
算法·开源
不会就选b2 小时前
算法日常・每日刷题--<动态规划>22
算法·动态规划
释厄6232 小时前
字画同体论——字即画·几何即字·漫画与小说=文本层
人工智能·算法
沫璃染墨2 小时前
从零入门数据结构与算法(一):复杂度分析——从算法效率到时间与空间复杂度
c语言·开发语言·数据结构·笔记·算法·飞书
熊猫钓鱼>_>2 小时前
面试出现 606 次的第 K 大,被拆成三个问题问
算法·面试·职场和发展·校招·面经·oj·ai生成