题目描述

✅ 1464. 数组中两元素的最大乘积

image-20260928225912756

image-20260928225912757

题意分析

从不同下标取出两个元素,最大化 (nums[i]-1)×(nums[j]-1)。下标必须不同,但两个元素的值可以相同。

题目保证所有元素至少为 1,减一后的因子都非负。增大其中任意一个因子都不会让乘积变小,所以应取数组中最大的两个元素,无需枚举所有下标对。

解法:一次扫描维护最大两值

核心思路

[!blue]

扫描过程中,用 first 和 second 保存已处理元素中最大的两个值,并始终满足 first >= second。这里按元素出现的次数排名,不对相同数值去重。

新值 num 若大于 first,它成为新的第一大,而旧 first 一定成为第二大;因此先执行 second = first,再更新 first。否则,第一大不变,只需判断 num 是否大于 second,决定它能否替换第二大。

若 num 等于 first 但大于 second,它会进入第二个分支,两个不同位置的相等最大值就都被保留。若两个候选已经相等,再遇到同值无需更新,现有两个位置已经足够。

数组至少有两个元素,先用前两项按大小初始化,从下标 2 开始扫描。每轮只处理一个新位置并进入一个更新分支,因而两个候选始终来自不同元素;扫描结束时,将这两个值各减一后相乘即可。

解题步骤

  1. 用前两个元素的较大值初始化 first,较小值初始化 second。
  2. 从第三个元素开始扫描。若当前值超过 first,将旧第一大移到第二大,再保存新第一大。
  3. 否则只在当前值超过 second 时更新第二大,其余情况保持两个候选不变。
  4. 返回 (first-1)×(second-1)。

代码实现

class Solution {
    public int maxProduct(int[] nums) {

        int first = Math.max(nums[0], nums[1]);
        int second = Math.min(nums[0], nums[1]);

        for (int i = 2; i < nums.length; i++) {
            int num = nums[i];

            if (num > first) {

                // 新值成为第一大前,先把旧第一大降为第二大。
                second = first;
                first = num;
            } else if (num > second) {
                // 另一个相同最大值也能占据第二名,允许数值相等。

                second = num;
            }
        }

        return (first - 1) * (second - 1);
    }
}
func maxProduct(nums []int) int {

    first, second := nums[0], nums[1]
    if first < second {
        first, second = second, first
    }

    for i := 2; i < len(nums); i++ {
        num := nums[i]
        if num > first {

            // 新值成为第一大前,先把旧第一大降为第二大。
            second = first
            first = num
        } else if num > second {
            // 另一个相同最大值也能占据第二名,允许数值相等。

            second = num
        }
    }

    return (first - 1) * (second - 1)
}

复杂度分析

  • 时间复杂度:$O(n)$。每个元素只参与常数次比较与赋值。
  • 空间复杂度:$O(1)$。只维护两个最大值,不修改输入数组。

关键点总结

[!green]

  • 取两个最大值的依据是减一后的因子非负,这使乘积对每个因子都单调不减。
  • 第二大按不同元素计数,允许与第一大数值相同。
  • 新第一大出现时,旧第一大仍应保留,不能直接丢弃。

易错点总结

[!yellow]

  • 两个更新分支写成独立的 if,可能把同一个新元素同时放进两个候选位置。
  • 先覆盖 first 再执行 second = first,会丢失旧第一大并重复使用当前元素。
  • 忽略等于第一大的新值,会错过两个不同下标上的相等最大值。
  • 用前两个位置初始化后,又从下标 0 开始扫描,会把已经计入的元素再次当作新候选。

相似题目

题目 难度 关联与区别
628. 三个数的最大乘积 简单 同样只需维护少数极值,本题输入为正且选择两项,原题三项乘积还要考虑两个负数。
414. 第三大的数 简单 原题按不同值找第三大,本题两个相等最大值都可参与,不能先去重。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2024/64893995
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!