Leetcode 91 子序列首尾元素的最大乘积

1 题目

3584. 子序列首尾元素的最大乘积

给你一个整数数组 nums 和一个整数 m

返回任意大小为 m子序列 中首尾元素乘积的最大值

子序列是可以通过删除原数组中的一些元素(或不删除任何元素),且不改变剩余元素顺序而得到的数组。

示例 1:

输入: nums = [-1,-9,2,3,-2,-3,1], m = 1

输出: 81

解释:

子序列 [-9] 的首尾元素乘积最大:-9 * -9 = 81。因此,答案是 81。

示例 2:

输入: nums = [1,3,-5,5,6,-4], m = 3

输出: 20

解释:

子序列 [-5, 6, -4] 的首尾元素乘积最大。

示例 3:

输入: nums = [2,-1,2,-6,5,2,-5,7], m = 2

输出: 35

解释:

子序列 [5, 7] 的首尾元素乘积最大。

提示:

  • 1 <= nums.length <= 105
  • -105 <= nums[i] <= 105
  • 1 <= m <= nums.length

2 代码实现

思考

根据以前的经验来看,这种题目无非就是在迭代更新,同时维护一个最大值,但是在这里需要注意的是会出现负数。

回忆一下我们买卖股票的代码。

cpp 复制代码
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if (n == 0){
            return 0 ;
        }
        
        int maxPro = 0 ;
        int minP = prices[0];
 
        for (int i = 0 ; i < n ; i ++){
            int curPro = prices[i] - minP;
            maxPro = max (maxPro ,curPro);
            minP = min( minP, prices[i]);
        }
 
        return maxPro;
    }
};

其实我感觉这个题目有点像定长滑窗,长度是m,然后更新的值是乘积。

先自己写一下试试看吧!

不对不对,子序列不是滑动窗口,注意子序列是可以删除元素的!!!

啊,不知道怎么写。如果这个题是全部正数的就很简单了,但是有负数,不知道怎么维护。


ai老师登场。

问题

你现在需要解决的问题是:

给定整数数组 nums 和整数 m,找到任意长度为 m 的子序列中首尾元素乘积的最大值。

核心难点在于子序列的特性(不改变顺序但可删元素)和存在负数的情况,这让直接暴力枚举所有可能的子序列变得不可行。

解题思路分析

首先明确核心逻辑:

  • 长度为 m 的子序列,其首尾元素在原数组中的位置满足 j - i >= m - 1(即从原数组第 i 个元素到第 j 个元素,中间至少有 m-1 个元素,才能选出长度为 m 的子序列)。
  • 我们需要遍历所有满足条件的 (i, j) 对,计算 nums[i] * nums[j],并找到最大值。
  • 由于数组长度可达 1e5,暴力枚举所有 i,j 会超时,因此需要优化:
    1. 对于每个可能的结尾位置 j,能和它配对的起始位置 i 必须满足 i <= j - (m-1)
    2. 我们只需要在 [0, j - (m-1)] 这个范围内,找到能让 nums[i] * nums[j] 最大的 nums[i] 即可。
    3. 由于存在负数,最大值可能来自:
      • 最大的正数 × 正数
      • 最小的负数 × 负数(负负得正,可能更大)

自己写的代码错误百出

cpp 复制代码
class Solution {
public:
    long long maximumProduct(vector<int>& nums, int m) {
        int n = nums.size();
        long long max_val = LLONG_MIN;
        if (m == 1){
            for(int i = 0 ; i < n ; ++i){
                long long maxNum = 0 ;
                maxNum = max(maxNum , (long long)nums[i]);
                max_val = maxNum * maxNum ;
            }
            return max_val;
        }
        long long maxNum = nums[0];
        long long minNum = nums[0];
        long long res = LLONG_MIN;
        for (int j = m - 1 ; j < n ; j++){
            int i = j - m +1 ;
            if (i > 0){
            maxNum = max(maxNum , (long long)nums[i]);
            minNum = min(minNum , (long long)nums[i]);
            long long cur1 = (long long)nums[i] * maxNum ;
            long long cur2 = (long long)nums[i] * minNum ;
            res = max(max(cur1 , cur2) , res);
            }
        }
        return res ;
    }
};

关键修改点说明

问题类型 错误代码行为 修复后行为
m=1 逻辑错误 初始化 maxNum=0,截断负数 直接计算每个元素的平方,遍历取最大值(包含负数的平方)
边界覆盖不全 只处理 i>0 的情况,漏掉 i=0 把计算逻辑移到 if 外,i=0 时直接用初始的 max_start/min_start 计算
乘积逻辑错误 计算 nums [i]*maxNum(同位置相乘) 计算 max_startnums[j]/min_startnums [j](起始

修正后通过的代码

cpp 复制代码
class Solution {
public:
    long long maximumProduct(vector<int>& nums, int m) {
        int n = nums.size();
        long long max_val = LLONG_MIN;
        if (m == 1){
            for(int i = 0 ; i < n ; ++i){
                long long square = (long long )nums[i] * nums[i];
                max_val = max(square ,max_val);
            }
            return max_val;
        }
        long long maxNum = nums[0];
        long long minNum = nums[0];
        long long res = LLONG_MIN;
        for (int j = m - 1 ; j < n ; j++){
            int i = j - m +1 ;
            if (i > 0){
            maxNum = max(maxNum , (long long)nums[i]);
            minNum = min(minNum , (long long)nums[i]);
            }
            long long cur1 = (long long)nums[j] * maxNum ;
            long long cur2 = (long long)nums[j] * minNum ;
            res = max(max(cur1 , cur2) , res);
        }
        return res ;
    }
};

3 小结

反思

看起来很恐怖的代码(至少在过去的我看来比较麻烦),但实际上思路很清晰。

case 1 : 如果m给的长度是1 ,那么就是找一个最大平方,这里我原先还弄错了变成了找最大的数,再平方。

case 2 : m给的长度大于1 ,相当于我要在一个m长度的序列里面找到最大的乘积,这里采用的是j作为循环遍历,其实用i也是一样的。

判断i是否大于0 ,就是在判断是否还有余下的序列更新。所有注意更新的循环放在里面,但是计算的逻辑放在循环外面。(这里我又弄错)

计算两个cur ,处理存在负数,负负得正的情况。


修改

一、核心修改点(按优先级排序)

类别 原代码问题 修复方案
编译 / 作用域 无(本次修改后解决了编译问题,但核心是逻辑) - 原代码曾将cur1/cur2定义在if(i>0)块内,外部访问报错;修复后移到块外,确保全程可访问
m=1 逻辑错误 初始化maxNum=0截断负数,直接赋值覆盖结果 1. 取消maxNum=0的错误初始化,直接计算每个元素的平方;2. 用max(max_val, square)迭代更新最大值,而非直接赋值
边界覆盖不全 仅处理i>0的情况,漏掉i=0的关键场景 把乘积计算逻辑移出if(i>0)块,i=0时用初始的max_start/min_start计算,确保第一个有效jj=m-1)能被处理
乘积逻辑错误 错误计算nums[i]*maxNum(同位置相乘) 修正为max_start*nums[j]/min_start*nums[j](起始位置最值 × 结尾位置值),符合 "子序列首尾乘积" 的定义
变量维护错误 错误更新maxNum=max(maxNum, nums[j]) 修正为max_start=max(max_start, nums[i_max]),维护的是起始位置范围的最值,而非结尾位置值

二、核心反思点(新手易踩坑 + 解题思路层面)

1. 对 "特殊场景" 的思考不足
  • 反思:m=1是题目中 "子序列首尾为同一个元素" 的特殊场景,最初错误地用 "正数最大值" 的思路处理,忽略了负数平方可能更大的特性。
  • 改进:遇到 "乘积 / 平方" 类问题,只要有负数参与,必须优先考虑 "负负得正" 的情况,不能默认只取正数。
2. 对 "变量维护目标" 的理解模糊
  • 反思:原代码混淆了 "维护起始位置的最值" 和 "维护结尾位置的值",错误地更新maxNumnums[j],本质是没理清 "子序列首尾位置的约束关系"(j - i >= m-1)。
  • 改进:解题前先明确 "维护的变量对应什么场景"------ 本题中max_start/min_start是 "所有能和j配对的起始位置i的最值",而非结尾位置的值。
3. 对 "边界条件" 的验证不足
  • 反思:原代码只处理i>0的情况,漏掉了i=0(第一个有效j),本质是没验证 "循环第一次执行时的场景"。
  • 改进:写循环逻辑时,先手动代入第一个循环值 (如j=m-1i=0)验证,确认边界场景能被覆盖。
4. 对 "变量作用域" 的基础认知不扎实
  • 反思:最初将cur1/cur2定义在if块内,导致外部访问报错,是新手常见的语法漏洞。
  • 改进:定义变量时先明确 "该变量是否需要跨块使用",跨块使用的变量需定义在块外,或在块内完成所有相关计算。

三、解题思路层面的核心总结

  1. 本题的本质是 "找满足j - i >= m-1(i,j)对,计算nums[i]*nums[j]的最大值",而非暴力枚举子序列;
  2. 处理负数乘积时,必须同时维护 "最大值" 和 "最小值"------ 最小值(最负)× 负数可能得到更大的正数;
  3. 特殊场景(如m=1)需单独处理,避免通用逻辑覆盖特殊情况。

这些反思不仅能解决本题,也适用于所有 "数组最值 + 负数参与运算" 的题目,核心是:先明确变量的语义,再验证边界场景,最后考虑负数的特殊运算规则


概述

一、核心反思

  1. 特殊场景别漏判m=1 是首尾同元素的特殊情况,需直接算元素平方取最大,而非默认取正数再平方,忽略负数平方的更大值。
  2. 变量语义要盯死 :维护的 maxNum/minNum可配对的起始位置最值 ,不是结尾位置值,更新时要加起始位置的 nums[i],而非 nums[j]
  3. 边界条件先验证 :循环首次执行(i=0)必须覆盖,计算逻辑不能只放在 i>0 的分支里,否则会漏掉初始的有效配对。
  4. 负数乘积双维护:涉及乘积最值且有负数时,要同时维护最大值和最小值 ------ 负负得正可能出现更大结果,只维护一个最值必错。

二、同类题解题心法

  1. 化繁为简抓本质 :子序列首尾配对问题 → 转化为满足 j-i≥m-1(i,j) 对乘积最值,避开暴力枚举子序列的坑。
  2. 遍历一端护两端 :固定结尾 j,维护 j 左侧可配对范围的最值,O (n) 遍历即可,无需嵌套循环。
  3. 负数乘积必双持:只要有负数参与乘积最值计算,必须同时维护当前范围的最大值和最小值,两种乘积都要算。
  4. 边界特殊先处理 :先解决 m=1 等特殊情况,再写通用逻辑,避免特殊场景干扰正常流程。
相关推荐
Tisfy2 小时前
LeetCode 840.矩阵中的幻方:模拟(+小小位运算)
算法·leetcode·矩阵
Word码2 小时前
LeetCode1089. 复写零(双指针精讲)
算法
Aliex_git2 小时前
Vue 2 - 模板编译源码理解
前端·javascript·vue.js·笔记·前端框架
BMS小旭2 小时前
CubeMx-CAN
单片机·学习·cubemx·can
saadiya~2 小时前
实战笔记:在 Ubuntu 离线部署 Vue + Nginx 踩坑与避雷指南
vue.js·笔记·nginx
Swift社区2 小时前
LeetCode 461 - 汉明距离
算法·leetcode·职场和发展
天呐草莓2 小时前
计算机视觉学习路线
人工智能·学习·计算机视觉
CoderCodingNo2 小时前
【CSP】CSP-XL 2025辽宁复赛真题-第三题, 小L打比赛(match)
数据结构·算法
被遗忘在角落的死小孩2 小时前
SSD 存储安全协议 TCG KPIO 笔记
笔记·安全