【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
    • 注意 :这里用的是更新前的 f2f3(上一轮的值)。但在代码中,由于是顺序更新,我们需要小心变量覆盖问题。
  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:新的开始。子数组只包含 xy。总和 = 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)。
相关推荐
潇冉沐晴2 小时前
div2 1064补题笔记(A~E)
笔记·算法
我爱工作&工作love我2 小时前
P4913 【深基16.例3】二叉树深度 dfs-二叉树的遍历
算法·深度优先·图论
TracyCoder1232 小时前
LeetCode Hot100(25/100)——2. 两数相加(链表)
算法·leetcode·链表
long3162 小时前
Z算法(线性时间模式搜索算法)
java·数据结构·spring boot·后端·算法·排序算法
望未来无悔2 小时前
系统学习算法 专题十九 优先级队列(堆)
java·算法
啊阿狸不会拉杆2 小时前
《机器学习导论》第3章 -贝叶斯决策理论
人工智能·python·算法·机器学习·numpy·深度优先·贝叶斯决策理论
阿蔹2 小时前
力扣面试题二Python
python·算法·leetcode·职场和发展
jaysee-sjc2 小时前
【项目二】用GUI编程实现石头迷阵游戏
java·开发语言·算法·游戏
元亓亓亓3 小时前
LeetCode热题100--169. 多数元素--简单
算法·leetcode·职场和发展