LeetCode 53 最大子序和
(简单)
题目
描述
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例1
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [0]
输出:0
示例 4:
输入:nums = [-1]
输出:-1
示例 5:
输入:nums = [-100000]
输出:-100000
思路
本题可用贪心的思想解决,局部最优:当前“连续和”为负数的时候⽴刻放弃,从下⼀个元素重新计算“连续和”,因为负数加上下⼀个元素 “连续和”只会越来越⼩。全局最优:选取最⼤“连续和”。本题要求最大的子序列和,而此和的结果又是受到此最大和序列中每个元素的影响,其中有的元素“贡献”自己的力量,有的元素“消耗”集体的力量,同时,我们又不能简单地理解为上面的两类元素分别是正值、负值,因为实际情况中,举一个极端的例子,也可能本序列中的值都为负值,因此,上面所说的“贡献”与“消耗”是相对的,是相对与每次循环时,前一次循环的累积财富值与当前循环中的元素的 贡献/消耗 值之和,与当前元素的值相比,是否能维持其较大的状态,即前者是否能较大,若能,则应继续遍历剩余元素,否则应将当前的较大状态转为当前元素的值。
(补充:之前,本题也用动态规划解决过,若读者有兴趣可去阅读:详细解析:连续子数组的最大和)
实现
public class TX3最大子序和 {
public int maxSubArray(int[] nums) {
int sum = 0;
int maxSum = nums[0];
//for (int i = 0; i < nums.length; i++) {
// sum = Math.max(sum + nums[i], nums[i]);
// maxSum = Math.max(sum, maxSum);
//}
for (int num : nums) {
sum = Math.max(sum + num, num);
maxSum = Math.max(sum, maxSum);
}
return maxSum;
}
public static void main(String[] args) {
TX3最大子序和 s = new TX3最大子序和();
System.out.println("s.maxSubArray(new int[]{-2,1,-3,4,-1,2,1,-5,4}) = " + s.maxSubArray(new int[]{-2, 1, -3, 4, -1, 2, 1, -5, 4}));
System.out.println("s.maxSubArray(new int[]{5,4,-1,7,8}) = " + s.maxSubArray(new int[]{5, 4, -1, 7, 8}));
System.out.println("s.maxSubArray(new int[]{-2,-1}) = " + s.maxSubArray(new int[]{-2, -1}));
}
}
本题我用 for i 和 for each 分别提交了一次,发现执行情况中的执行时间相差一倍,
我用 for i 循环时的执行情况:
我用 for each 循环时的执行情况:
难道这两种遍历方式对于数组来说还有差别,for i 下标方式获取不也是用哈希函数一次获取吗,于是我便进行了如下测试:
for i 测试
int[] test = new int[100000];
Arrays.fill(test, 1);
long startTime=System.currentTimeMillis(); //获取开始时间
for (int i = 0; i < test.length; i++) {
System.out.println(i);
}
//for (int i : test) {
// System.out.println(i);
//}
long endTime=System.currentTimeMillis(); //获取结束时间
System.out.println("程序运行时间: "+(endTime-startTime)+"ms");
for each 测试
int[] test = new int[100000];
Arrays.fill(test, 1);
long startTime=System.currentTimeMillis(); //获取开始时间
//for (int i = 0; i < test.length; i++) {
// System.out.println(i);
//}
for (int i : test) {
System.out.println(i);
}
long endTime=System.currentTimeMillis(); //获取结束时间
System.out.println("程序运行时间: "+(endTime-startTime)+"ms");
经过多次本地测试,都是 for i 要快些,而对于 LeetCode 官方的执行平台,测试结果正好相反,那么结论便显而易见了,对于此题,我们不能相信 L 方的执行效率。
LeetCode 122:买卖股票的最佳时机 II
(中等)
题目
描述
给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例1
输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: prices = [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: prices = [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
思路
本题在读懂题意后,可抛去此题目的背景,只考虑数据元素的求得思路,也就是抛去本题的股票题目背景,只去考虑,在一个序列中,规定被减数在减数的右侧,多对这样的数,进行减法运算,结果的和最大是多少?
我们可以将每两个数之间作为一个跨度,而不是将某一个较大的区间(即长度大于2的区间)作为一个跨度,例如,对于序列 1 6 7,以两数之间作为一个跨度的话,则组成1 6 一对,6 7 一对,结果分别为 5 1,和为6;而此处以主观判断来说,我们之间以1 和 7 为跨度,结果为 6,也是一样的。原理就是对于一个单调递增的子序列,以跨度为多少进行 f(x右) - f(x左),最终和的结果都是一样的,同样的还有下图这个例子:
对于下图的例子,因为我们已经将跨度设定为两个数之间了(也就是紧邻的两个数)那么每个子序列(长度为2)不是单调递增,就是单调递减,因此,我们求最终和最大,只需要将所有递增的序列的差一个个求出来,最终的和,就是答案。
因此,这种贪心思想的局部最优就是每个递增的序列的正数差,全局最优就是所有正数差的和。
实现
public class TX4买卖股票的最佳时机II {
public int maxProfit(int[] prices) {
if (prices.length <= 1) {
return 0;
}
int profit = 0;
int sum = 0;
for (int i = 0; i < prices.length - 1; i++) {
profit = prices[i + 1] - prices[i];
if (profit > 0) {
sum += profit;
}
}
return sum;
}
}