贪心算法进阶

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;
    }
}

代码解析

  1. 初始化maxProfit 初始化为 0,记录总利润。
  2. 遍历价格数组:从索引 1 开始(因为要对比前一天),逐个检查当天与前一天的价格差。
  3. 贪心决策:只要差价为正,就把这个差价加到总利润中(相当于 "当天卖、前一天买")。
  4. 返回结果 :遍历结束后,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. 边界处理:如果数组只有 1 个元素,直接返回 true(已经在终点)。
  2. 初始化最远位置maxReach 初始化为 0,记录当前能到达的最远索引。
  3. 遍历数组
    • 若当前索引 i > maxReach,说明无法到达当前位置,返回 false。
    • 计算 i + nums[i](从当前位置能跳到的最远索引),更新 maxReach
    • maxReach ≥ 数组最后一个索引,说明能到达终点,直接返回 true。
  4. 最终返回:遍历结束仍未满足条件,返回 false。

45. 跳跃游戏 II

解题思路

这道题的贪心核心是:在当前能跳的范围内,找下一步能跳的最远位置(最少步数策略)。

  • 维护三个变量:
    • currentEnd:当前步数能到达的最远位置。
    • maxReach:遍历过程中,下一步能到达的最远位置。
    • steps:记录跳跃步数。
  • 遍历数组时,在 currentEnd 范围内不断更新 maxReach;当遍历到 currentEnd 时,说明需要跳一步,更新 currentEndmaxReach,并增加步数。
  • 注意:遍历到倒数第二个元素即可(因为到达最后一个元素无需再跳)。

完整 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. 边界处理:数组长度为 1 时,无需跳跃,返回 0。
  2. 变量初始化steps(步数)、currentEnd(当前步最远位置)、maxReach(下一步最远位置)均初始化为 0。
  3. 遍历数组(到倒数第二个元素)
    • 不断更新 maxReach 为 "当前位置能跳的最远索引"。
    • 当遍历到 currentEnd 时,说明当前步的范围已遍历完,需要跳一步:steps++,并将 currentEnd 更新为 maxReach
    • currentEnd 已覆盖最后一个索引,提前退出循环(优化性能)。
  4. 返回步数 :遍历结束后,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;
    }
}

代码解析

  1. 排序数组:升序排序后,最小的负数在最前面,优先处理。
  2. 反转负数:遍历数组,遇到负数且 K>0 时,将其取反(变正),K 减 1;遇到非负数时跳出循环(后续数更大,反转不划算)。
  3. 处理剩余 K
    • 若 K 是偶数:反转同一个数偶数次,等于没反转,总和不变。
    • 若 K 是奇数:反转数组中最小的数(总和减少最少),因此需要重新排序找最小值。
  4. 计算总和:遍历数组,累加所有元素得到最终结果。

总结

  1. 买卖股票 II:贪心核心是 "赚所有正差价",无需考虑完整波段,只累加每日正收益。
  2. 跳跃游戏:贪心核心是 "维护最远可达位置",遍历中若当前索引超出最远位置则失败,否则更新最远位置。
  3. 跳跃游戏 II:贪心核心是 "在当前步范围内找下一步最远位置",遍历到当前步边界时步数 + 1,保证最少步数。
  4. K 次取反最大化和:贪心核心是 "优先反转最小负数,剩余 K 为奇数时反转最小正数",让总和损失最小。
相关推荐
玄冥剑尊1 小时前
贪心算法深化 I
算法·贪心算法
52Hz1182 小时前
力扣73.矩阵置零、54.螺旋矩阵、48.旋转图像
python·算法·leetcode·矩阵
BHXDML2 小时前
第一章:线性回归& 逻辑回归
算法·逻辑回归·线性回归
iAkuya2 小时前
(leetcode)力扣100 二叉搜索树种第K小的元素(中序遍历||记录子树的节点数)
算法·leetcode·职场和发展
Remember_9933 小时前
【LeetCode精选算法】滑动窗口专题二
java·开发语言·数据结构·算法·leetcode
Gorgous—l4 小时前
数据结构算法学习:LeetCode热题100-动态规划篇(下)(单词拆分、最长递增子序列、乘积最大子数组、分割等和子集、最长有效括号)
数据结构·学习·算法
北京地铁1号线4 小时前
2.3 相似度算法详解:Cosine Similarity 与 Euclidean Distance
算法·余弦相似度
Remember_9935 小时前
【LeetCode精选算法】滑动窗口专题一
java·数据结构·算法·leetcode·哈希算法
小饼干超人5 小时前
详解向量数据库中的PQ算法(Product Quantization)
人工智能·算法·机器学习