122. 买卖股票的最佳时机 II
解题思路
这道题的贪心核心是:只赚所有 "正差价"。
- 不纠结于 "什么时候买、什么时候卖" 的完整波段,而是把连续上涨的波段拆分成每天的小涨幅。
- 例如:价格从 1→3→5,总利润是 4,等价于 (3-1)+(5-3) = 4。
- 只要当天价格比前一天高,就把这个差价加入利润,最终总和就是最大利润。
完整 Java 代码
java
class Solution {
public int maxProfit(int[] prices) {
int maxProfit = 0;
// 从第2天开始遍历(索引1),对比前一天价格
for (int i = 1; i < prices.length; i++) {
// 只要当天价格 > 前一天价格,就赚这个差价
if (prices[i] > prices[i - 1]) {
maxProfit += prices[i] - prices[i - 1];
}
}
return maxProfit;
}
}
代码解析
- 初始化 :
maxProfit初始化为 0,记录总利润。 - 遍历价格数组:从索引 1 开始(因为要对比前一天),逐个检查当天与前一天的价格差。
- 贪心决策:只要差价为正,就把这个差价加到总利润中(相当于 "当天卖、前一天买")。
- 返回结果 :遍历结束后,
maxProfit就是最大利润。
55. 跳跃游戏
解题思路
这道题的贪心核心是:维护当前能到达的最远位置。
- 遍历数组时,不断更新 "从当前位置能跳到的最远位置"。
- 如果遍历过程中,当前索引超过了 "最远能到达的位置",说明无法继续前进,返回 false。
- 如果 "最远能到达的位置"≥ 数组最后一个索引,直接返回 true。
完整 Java 代码
java
class Solution {
public boolean canJump(int[] nums) {
if (nums.length == 1) {
return true; // 只有一个元素,直接到达
}
int maxReach = 0; // 记录当前能到达的最远位置
for (int i = 0; i < nums.length; i++) {
// 如果当前索引超过了最远能到达的位置,说明跳不到这里
if (i > maxReach) {
return false;
}
// 更新最远能到达的位置:当前能跳的最远 vs 之前记录的最远
maxReach = Math.max(maxReach, i + nums[i]);
// 如果已经能到达最后一个位置,直接返回true
if (maxReach >= nums.length - 1) {
return true;
}
}
return false;
}
}
代码解析
- 边界处理:如果数组只有 1 个元素,直接返回 true(已经在终点)。
- 初始化最远位置 :
maxReach初始化为 0,记录当前能到达的最远索引。 - 遍历数组 :
- 若当前索引
i>maxReach,说明无法到达当前位置,返回 false。 - 计算
i + nums[i](从当前位置能跳到的最远索引),更新maxReach。 - 若
maxReach≥ 数组最后一个索引,说明能到达终点,直接返回 true。
- 若当前索引
- 最终返回:遍历结束仍未满足条件,返回 false。
45. 跳跃游戏 II
解题思路
这道题的贪心核心是:在当前能跳的范围内,找下一步能跳的最远位置(最少步数策略)。
- 维护三个变量:
currentEnd:当前步数能到达的最远位置。maxReach:遍历过程中,下一步能到达的最远位置。steps:记录跳跃步数。
- 遍历数组时,在
currentEnd范围内不断更新maxReach;当遍历到currentEnd时,说明需要跳一步,更新currentEnd为maxReach,并增加步数。 - 注意:遍历到倒数第二个元素即可(因为到达最后一个元素无需再跳)。
完整 Java 代码
java
class Solution {
public int jump(int[] nums) {
if (nums.length == 1) {
return 0; // 只有一个元素,无需跳跃
}
int steps = 0; // 记录跳跃步数
int currentEnd = 0; // 当前步数能到达的最远位置
int maxReach = 0; // 下一步能到达的最远位置
for (int i = 0; i < nums.length - 1; i++) { // 遍历到倒数第二个元素即可
// 更新下一步能到达的最远位置
maxReach = Math.max(maxReach, i + nums[i]);
// 当遍历到当前步数的最远位置时,需要跳一步
if (i == currentEnd) {
steps++; // 步数+1
currentEnd = maxReach; // 更新当前步数的最远位置
// 如果已经能到达最后一个位置,提前退出
if (currentEnd >= nums.length - 1) {
break;
}
}
}
return steps;
}
}
代码解析
- 边界处理:数组长度为 1 时,无需跳跃,返回 0。
- 变量初始化 :
steps(步数)、currentEnd(当前步最远位置)、maxReach(下一步最远位置)均初始化为 0。 - 遍历数组(到倒数第二个元素) :
- 不断更新
maxReach为 "当前位置能跳的最远索引"。 - 当遍历到
currentEnd时,说明当前步的范围已遍历完,需要跳一步:steps++,并将currentEnd更新为maxReach。 - 若
currentEnd已覆盖最后一个索引,提前退出循环(优化性能)。
- 不断更新
- 返回步数 :遍历结束后,
steps就是最少跳跃次数。
1005. K 次取反后最大化的数组和
解题思路
这道题的贪心核心是:优先反转最小的负数,其次反转最小的正数(若 K 还有剩余)。
- 步骤 1:将数组按升序排序,优先反转负数(负数变正后总和最大)。
- 步骤 2:遍历排序后的数组,遇到负数且 K>0 时,反转该数(取反),K--。
- 步骤 3:若 K 还有剩余(此时数组全为非负数),若 K 是偶数,总和不变;若 K 是奇数,反转数组中最小的数(总和减少最少)。
- 步骤 4:计算最终数组的和。
完整 Java 代码
java
import java.util.Arrays;
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
// 步骤1:按升序排序,优先处理最小的负数
Arrays.sort(nums);
// 步骤2:遍历数组,反转负数(K>0时)
for (int i = 0; i < nums.length && k > 0; i++) {
if (nums[i] < 0) {
nums[i] = -nums[i]; // 反转负数
k--; // K减1
} else {
break; // 后续都是非负数,无需继续遍历
}
}
// 步骤3:若K还有剩余,且为奇数,反转最小的数
if (k % 2 == 1) {
// 重新排序找最小值(因为反转负数后数组可能无序)
Arrays.sort(nums);
nums[0] = -nums[0];
}
// 步骤4:计算数组总和
int sum = 0;
for (int num : nums) {
sum += num;
}
return sum;
}
}
代码解析
- 排序数组:升序排序后,最小的负数在最前面,优先处理。
- 反转负数:遍历数组,遇到负数且 K>0 时,将其取反(变正),K 减 1;遇到非负数时跳出循环(后续数更大,反转不划算)。
- 处理剩余 K :
- 若 K 是偶数:反转同一个数偶数次,等于没反转,总和不变。
- 若 K 是奇数:反转数组中最小的数(总和减少最少),因此需要重新排序找最小值。
- 计算总和:遍历数组,累加所有元素得到最终结果。
总结
- 买卖股票 II:贪心核心是 "赚所有正差价",无需考虑完整波段,只累加每日正收益。
- 跳跃游戏:贪心核心是 "维护最远可达位置",遍历中若当前索引超出最远位置则失败,否则更新最远位置。
- 跳跃游戏 II:贪心核心是 "在当前步范围内找下一步最远位置",遍历到当前步边界时步数 + 1,保证最少步数。
- K 次取反最大化和:贪心核心是 "优先反转最小负数,剩余 K 为奇数时反转最小正数",让总和损失最小。