前端必会数据结构与算法系列之贪婪算法(十二)

411 阅读3分钟

1. 什么是贪心算法

  • 贪心算法是算法设计中的一种方法。
  • 期盼通过每个阶段的局部最优选择,从而达到全局的最优。
  • 结果并不一定是最优。

贪心算法是一种在每一步选择中都采取在 前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。

贪心算法遵循一种近似解决问题的技术,期盼通过每个阶段的局部最优选择(当前最好的解),从而达到全局的最优(全局最优解)。它不像动态规划算法那样计算更大的格局。

  • 贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退。
  • 动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。

三者区别

  • 贪心:当下做局部最优判断
  • 回溯:能够回退
  • 动态规划:最优判断+回退

贪心法可以解决-些最优化问题,如:求图中的最小生成树、求哈夫曼编码等。然而对于工程和生活中的问题,贪心法一般不能得到我们所要求的答案。

一旦一个问题可以通过贪心法来解决,那么贪心法一般是解决这个问题的最好办法。由于贪心法的高效性以及其所求得的答案比较接近最优结果,贪心法也可以用作辅助算法或者直接解决一些要求结果不特别精确的问题。

适用贪心法场景:

简单地说,问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解。这种子问题最优解称为最优子结构。

2. 应用

2.1 最少硬币找零问题

从最大面额的硬币开始,拿尽可能多的这种硬币找零。当无法再拿更多这种价值的硬币时,开始拿第二大价值的硬币,依次继续。代码如下:

image.png

function minCoinChange(coins, amount) { 
    const change = []; 
    let total = 0; 
    for (let i = coins.length; i >= 0; i--) {
        const coin = coins[i]; 
        while (total + coin <= amount) {
            change.push(coin);
            total += coin;
        } 
    } 
    return change; 
}

然而,如果用[1, 3, 4]面额执行贪心算法,会得到结果[4, 1, 1]。如果用动态规划的解法,会得到最优的结果[3, 3]

贪心法反例:

image.png

比起动态规划算法而言,贪心算法更简单、更快。然而,如我们所见,它并不总是得到最优答案。但是综合来看,它相对执行时间来说,输出了一个可以接受的解

3.2 分数背包问题

在 0-1 背包问题中,只能向背包里装入完整的物品,而在分数背包问题中,可以装入分数的物品

function knapSack(capacity, weights, values) { 
    const n = values.length; 
    let load = 0; 
    let val = 0; 
    // 总重量少于背包容量,迭代物品
    for (let i = 0; i < n && load < capacity; i++) {
        // 物品可以完整地装入背包
        if (weights[i] <= capacity - load) {
            val += values[i]; 
            load += weights[i]; 
        } else { 
            // 计算能够装入部分的比例
            const r = (capacity - load) / weights[i];
            val += r * values[i]; 
            load += weights[i]; 
        } 
    } 
    return val; 
}

3. leetcode常见考题

3.1 easy

1. 分发饼干

难度:简单

题解:分发饼干(排序+贪心)

2. 买卖股票的最佳时机 II

难度:简单

题解:买卖股票的最佳时机 II(贪心)

3. 柠檬水找零

难度:简单

题解:柠檬水找零(贪心)

4. 模拟行走机器人

难度:简单

3.2 medium

1. 跳跃游戏

难度:中等

题解:跳跃游戏(贪心)

2. 跳跃游戏 II

难度:中等

题解:跳跃游戏 II(贪心)