基础问题:给定一个整数数组选出 2 个数,使他们的总和最大。

  • 暴力算法:O(n^2),略。
  • 时间复杂度为O(n)的解法:考虑数组中的两个数nums[i], nums[j], i < j且∈[0, n),从前往后枚举第二个数,对于每个nums[j]来说,只关心它前面最大的那个数字, 因为只有前面最大的数nums[j] 相加才可能产生最大的结果。因此在枚举j的过程中,维护j之前的最大的数即可。
    int n = nums.size();
    int maxNi = nums[0];
    int ansTwo = nums[0] + nums[1];
    for(int j = 1; j < n; ++j){//j = 1开始,枚举第二个数
        maxNi = max(maxNi, nums[j-1]); 
        ansTwo = max(ansTwo, maxNi + nums[j]);
    }
    cout << ansTwo << endl; 

问题升维:给定一个整数数组选出 3 个数,使他们的总和最大。

  • 在上述基础问题的基础上,由于2个数有O(n)的解法,因此对于3个数的情况,显然可以在枚举j之后,再从 [ j + 1,n) 中找一个最大的数,但是这个做法时间复杂度是O(N^2)

  • 仔细分析这个问题的本质:实际上可以使用同样的思想来处理3个数的问题,关键思想:对于每个j更新完后,ansTwo里就是从[0,j] 中最大的两个数的和。那么对于第三个下标j+1,只需要关心它前面最大的ansTwo,因为只有前面最大的两数之和与nums[j+1] 相加才可能产生最大的结果。因此在枚举j+1的过程中,维护之前的ansTwo即可。

int n = nums.size();
    int maxNi = nums[0];
    int ansTwo = nums[0] + nums[1];
    int ansThree = nums[0] + nums[1] + nums[2];
    for(int j = 2; j < n; ++j){//这里从j = 2开始相当于枚举第三个数
        maxNi = max(maxNi, nums[j-2]); 
        ansTwo = max(ansTwo, maxNi + nums[j-1]);
        ansThree = max(ansThree, ansTwo + nums[j]); 
    }
    cout << ansThree << endl;

总结与讨论

  • 可以将其理解为一种特殊的动态规划;
  • 对于类似数组中顺序的数据的最大/小的和/差,可以先维护前面的结果枚举后面的数据即可。这个思想的用途非常广泛。

例题运用

今日LC每日一题:2016. 增量元素之间的最大差值

给你一个下标从 0 开始的整数数组 nums ,该数组的大小为 n ,请你计算 nums[j] - nums[i] 能求得的 最大差值 ,其中 0 <= i < j < n 且 nums[i] < nums[j] 。

返回 最大差值 。如果不存在满足要求的 i 和 j ,返回 -1 。

class Solution {
	//枚举第j个数字,维护j前面最小的数min,最大差值只可能由nums[j] - min产生
    public int maximumDifference(int[] nums) {
		int min = nums[0];
        int ans = -1;
        int n = nums.length;
        for (int j = 1; j < n; j++) {
        	min = Math.min(min, nums[j-1]);
            if (nums[j] > min) {
                ans = Math.max(ans, nums[j] - min);
            }
        }
        return ans;
    }
}