题目描述

✅ 1330. 翻转子数组得到最大的数组值

image-20260928224222769

题意分析

数组值是所有相邻元素绝对差的总和,要求翻转一个连续子数组后使它最大。翻转长度为 1 的区间不会改变数组,因此最优答案至少等于原数组值。

解法:枚举边界 + 内部最优

核心思路

[!blue]

翻转区间内部的相邻元素只交换方向,绝对差仍相同。真正改变的只有区间与外部连接的边,因此先求原数组值 base,再寻找最大的增益 best,不需要实际翻转或重新求和。

先处理只有一条外部边的情况。对相邻位置 i - 1、i,翻转前缀 [0, i - 1] 会把这条边替换为 nums[0] 与 nums[i] 的连接;翻转后缀 [i, n - 1] 则替换为 nums[i - 1] 与 nums[n - 1] 的连接。新边的绝对差减去旧边的绝对差,就是代码中的 gain1、gain2,枚举所有 i 即可覆盖全部前后缀。

对不触及首尾的区间 [l, r],令四个边界值为 $a=nums[l-1]$、$b=nums[l]$、$c=nums[r]$、$d=nums[r+1]$。旧边是 $(a,b)$、$(c,d)$,翻转后变成 $(a,c)$、$(b,d)$,增益为 $ a-c + b-d - a-b - c-d $。

把旧边看成数轴上的两个区间,并将四个端点排序为 $x_1\le x_2\le x_3\le x_4$。任意配对的距离和都不超过两大端点之和减去两小端点之和。若两区间相交,旧边长度和已是这个上限 $(x_4-x_1)+(x_3-x_2)$,重新配对无法得到更大的和。若两区间分离,旧边长度和为 $(x_2-x_1)+(x_4-x_3)$,新边都跨过中间空隙,长度和变成 $(x_3-x_1)+(x_4-x_2)$,增益恰好是两倍空隙 $2(x_3-x_2)$。

因此只需在所有相邻对中,维护最大下端点 maxMin = max(min(a, b)) 和最小上端点 minMax = min(max(a, b)),最大正空隙就是 maxMin - minMax。差为正时,提供这两个端点的旧边在数轴上分离,不可能共用数组位置;按它们在数组中的先后顺序翻转中间部分,就能实现该增益。差不为正时没有内部正增益,用初始为 0 的 best 排除它即可。

解题步骤

  • 原值累加全部相邻绝对差。
  • 扫描前缀、后缀翻转的单接缝增益。
  • 扫描相邻对极值得到内部候选。
  • 把最大非负增益加回原值。

代码实现

class Solution {
    public int maxValueAfterReverse(int[] nums) {
        int n = nums.length;

        if (n < 2) {
            return 0;
        }

        int base = 0;

        for (int i = 1; i < n; i++) {
            base += Math.abs(nums[i] - nums[i - 1]);
        }

        int best = 0;

        for (int i = 1; i < n; i++) {

            // 前缀翻转只改变这一处外部接缝,内部绝对差总和不变。
            int gain1 = Math.abs(nums[0] - nums[i]) - Math.abs(nums[i] - nums[i - 1]);

            // 后缀翻转同样只改变一处接缝。
            int gain2 = Math.abs(nums[n - 1] - nums[i - 1]) - Math.abs(nums[i] - nums[i - 1]);

            best = Math.max(best, Math.max(gain1, gain2));
        }

        int maxMin = Integer.MIN_VALUE;
        int minMax = Integer.MAX_VALUE;

        for (int i = 1; i < n; i++) {
            int a = nums[i - 1];
            int b = nums[i];
            int mn = Math.min(a, b);
            int mx = Math.max(a, b);

            // 正间隙由最大下端与最小上端确定,两条分离边不会共用数组位置。
            maxMin = Math.max(maxMin, mn);
            minMax = Math.min(minMax, mx);
        }

        best = Math.max(best, 2 * (maxMin - minMax));

        return base + best;
    }
}
func maxValueAfterReverse(nums []int) int {
    n := len(nums)

    if n < 2 {
        return 0
    }

    base := 0
    for i := 1; i < n; i++ {
        base += abs(nums[i] - nums[i-1])
    }

    best := 0
    for i := 1; i < n; i++ {

        // 前缀翻转只改变这一处外部接缝,内部绝对差总和不变。
        gain1 := abs(nums[0]-nums[i]) - abs(nums[i]-nums[i-1])

        // 后缀翻转同样只改变一处接缝。
        gain2 := abs(nums[n-1]-nums[i-1]) - abs(nums[i]-nums[i-1])
        if gain1 > best {
            best = gain1
        }
        if gain2 > best {
            best = gain2
        }
    }

    // 已有至少两个元素,用真实相邻对初始化极值。
    maxMin := nums[0]
    minMax := nums[1]
    if maxMin > minMax {
        maxMin, minMax = minMax, maxMin
    }
    for i := 1; i < n; i++ {
        a := nums[i-1]
        b := nums[i]
        mn := a
        if b < mn {
            mn = b
        }
        mx := a
        if b > mx {
            mx = b
        }

        if mn > maxMin {
            maxMin = mn
        }
        if mx < minMax {
            minMax = mx
        }
    }

    // 正间隙对应合法内部翻转的两处增益,没有正间隙时不改善答案。
    cand := 2 * (maxMin - minMax)
    if cand > best {
        best = cand
    }

    return base + best
}

func abs(x int) int {
    if x < 0 {
        return -x
    }
    return x
}

复杂度分析

  • 时间复杂度:$O(n)$,三次独立线性扫描。
  • 空间复杂度:$O(1)$,不实际修改数组。

关键点总结

[!green]

  • 前后缀只有一处接缝,不能套内部两接缝公式。
  • 单元素没有相邻对,直接返回零。
  • 翻转整个数组也没有增益;初始 best = 0 已覆盖所有不改善答案的选择。
  • 题面保证答案在 32 位整数范围内,原值又不超过答案,现有整数类型足够。

易错点总结

[!yellow]

  • 只重算翻转区间内部、却漏算两处外部接缝,会遗漏实际变化的贡献。
  • 内部候选漏乘二,会少算另一处接缝的收益。
  • 只枚举内部翻转,会漏掉首尾边界的最优结果。

相似题目

题目 难度 关联与区别
396. 旋转函数 中等 同样通过推导操作前后的增量避免完整模拟,本题反转后内部相邻绝对差不变,只改变区间边界贡献。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/47747422
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!