面试算法——贪心算法题解

文章目录

      • 贪心算法定义
      • 贪心算法的反例
        • 322.零钱兑换
      • 贪心算法案例
        • 874.模拟行走机器人
        • [860. 柠檬水找零](https://leetcode-cn.com/problems/lemonade-change/)
        • 455.分发饼干
        • 122.买卖股票的最佳时机II
        • 55.跳跃游戏I
        • 45.跳跃游戏II
      • 参考

贪心算法定义

贪心算法是一种 在每一步选择中,采取在当前状态下最好或最优的选择,从而希望通过局部最优获取全局最优。比如,跳台阶II,每次,我们从区间中选择跳最远的。

经典贪心算法实践:求图中的最小生成树, 求哈夫曼编码等。

贪心算法的反例

322.零钱兑换

题目: 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

你可以认为每种硬币的数量是无限的。

思路: 刚看到这题的时候,应该会想到贪心算法, 每次拿最大的硬币组合,那就可以凑成总金额所需的最少的硬币个数。于是写出了一下代码:

class Solution {
public:
    int coinChange(vector& coins, int amount) {
        int res = 0;
        sort(coins.begin(), coins.end(), greater());
        for (int i = 0; i < coins.size(); ++i){
            if (amount >= coins[i]){
                res += amount / coins[i];
                amount = amount % coins[i];
            }
        }
        
        return amount == 0 ? res : -1;
    }
};

以上代码跑测试案例 coins = [1, 2, 5] amount = 11时顺利通过,没有任何问题。这是因为他们具有整除关系的。”整除关系“不好理解啊。看以下例子coins = [1, 5, 6], amount = 10,对于这个案例,正确答案应该是2,但是以上代码,跑出来确是5。所以这题不适合用贪心算法。

正确解法一:动态规划

class Solution {
public:
    int coinChange(vector& coins, int amount) {
        vector dp(amount+1, amount + 1);
        dp[0] = 0;

        for(int j = 0; j <= amount; j++){
            for(int i = 0; i < coins.size(); i++){
                if(j >= coins[i])
                    dp[j] = min(dp[j], dp[j - coins[i]] + 1);
            }
        }
        return dp[amount] == amount+1 ? -1 : dp[amount];
    }
};

贪心算法案例

874.模拟行走机器人

题目: 机器人在一个无限大小的网格上行走,从点 (0, 0) 处开始出发,面向北方。该机器人可以接收以下三种类型的命令:

-2:向左转 90 度
-1:向右转 90 度
1 <= x <= 9:向前移动 x 个单位长度
在网格上有一些格子被视为障碍物。

第 i 个障碍物位于网格点 (obstacles[i][0], obstacles[i][1])

机器人无法走到障碍物上,它将会停留在障碍物的前一个网格方块上,但仍然可以继续该路线的其余部分。

返回从原点到机器人所有经过的路径点(坐标为整数)的最大欧式距离的平方。

思路: 这道题感觉没有贪心的思想啊,只是没走一步需要更新res,需要注意以下几点:

  1. 向左、向右如何表示?在这里利用dire[4][2] = { {1, 0}, {0, -1}, {-1, 0}, {0, 1}},这个代表着,往 上右下左四个方向走。
  2. 没走一步前,需要判断是否碰到障碍物。
  3. 因此,代码表示为以下。
class Solution {
public:
    int robotSim(vector& commands, vector>& obstacles) {
        int dir[4][2] = {
    {0, 1}, {1, 0}, {0, -1}, {-1, 0}};
        unordered_set uset;
        for (int i = 0; i < obstacles.size(); ++i) {
            uset.insert(to_string(obstacles[i][0]) + "#" + to_string(obstacles[i][1]));
        }

        int d = 0, x = 0, y = 0, result = 0;
        for (int command : commands) {
            if(command == -1) {
                d++;
                if (d == 4) d = 0;
            } else if (command == -2){
                d--;
                if (d == -1) d = 3;
            } else {
                while(command-- > 0 && uset.find(to_string(x + dir[d][0]) + "#" + to_string(y + dir[d][1])) == uset.end()) {
                    x += dir[d][0];
                    y += dir[d][1];
                }
            }
            result = max(result, x * x + y * y);
        }
        return result;
    }
};

860. 柠檬水找零

题目: 在柠檬水摊上,每一杯柠檬水的售价为 5 美元。

顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。

如果你能给每位顾客正确找零,返回 true ,否则返回 false

思路: 首先我们是没有任何钱,这里有以下三种情况:

  1. 给你5元,收下即可,定义five表示5元个数,ten表示10元个数,five++
  2. 给你10元,
    1. 如果five <=0 ; return false
    2. 如果five >0, ten++, five--
  3. 给你20元,
    1. 如果ten >0, ten--, five--
    2. 如果ten < 0, five -= 3
  4. 最后,我们只要判断five < 0 ?即可

流程就是以上流程了,那我们如何写出简洁的代码呢?以下代码是写的很简洁的,这里用到了逗号运算符,不明白的看 【参考3】

class Solution {
public:
    bool lemonadeChange(vector& bills) {
        int five = 0, ten = 0;
        for (int bill : bills){
            if (bill == 5) five++;
            else if (bill == 10) five--, ten++;
            else if(ten >0) ten--, five--;
            else five -= 3;
            if (five < 0)   return false;
        }
        return true;
    }
};

455.分发饼干

题目: 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

思路: 这题用贪心的思路,有两个思路:

  1. 最大的饼给胃口最大人
  2. 最小的人吃满足它最小的饼

这里用第一种思路;

class Solution {
public:
    int findContentChildren(vector& g, vector& s) {
        sort( g.begin(), g.end(), greater());
        sort( s.begin(), s.end(), greater());

        int si = 0, gi = 0;
        int res = 0;
        while ( gi < g.size() && si < s.size() ){
            if ( s[si] >= g[gi]){
                res ++;
                si++;
                gi++;
            }
            else
                gi++;
        }       
        return res;
    }
};

注意: std::sort 的compare[仿]函数,默认为less() 从小到大排列,若指定为 greater()为从大到小排列。

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

题目: 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

思路: 这题可以动态规划也可贪心算法,这里,我们可以想象,每天都有买卖,注意,每天都有买卖 这几个字要特别注意,因此,就会有,以下代码:

class Solution {
public:
    int maxProfit(vector& prices) {
        int res = 0;
        for (int i = 0; i < prices.size() - 1; ++i){
            if (prices[i] < prices[i+1])    
                res += prices[i+1] - prices[i];
        }
        return res;
    }
};

详细解释,请看 买卖股票的最佳时机II(贪心,清晰图解)

55.跳跃游戏I

题目: 给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个位置。

思路:也没啥思路,就是说,我们需要定义个,我们能跳到的距离dest,同时遍历所有台阶,在这过程判断 dest < i,同时更新dest = max(dest, nums[i] + i)。代码如下:

class Solution {
public:
    bool canJump(vector& nums) {
        int dest = 0;
        for (int i = 0; i < nums.size(); ++i){
            if (dest < i)   return false;
            // max替代if(nums[i]+i > dest)
            dest = max(nums[i] + i, dest);
        }
        return true;
    }
};

45.跳跃游戏II

**题目:**给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

你的目标是使用最少的跳跃次数到达数组的最后一个位置。

思路: 看链接:Concise O(n) one loop Java solution based on greedy

class Solution {
public:
    int jump(vector& nums) {
        int res = 0, curEnd = 0, curFarthest = 0;
        for (int i = 0; i < nums.size() - 1; ++i){
            curFarthest = max(nums[i] + i, curFarthest);
            if (i == curEnd){
                res++;
                curEnd = curFarthest;
            }
        }
        return res;
    }
};

注意以下两句话:

  1. Let’s say the range of the current jump is [curBegin, curEnd], curFarthest is the farthest point that all points in [curBegin, curEnd] can reach. Once the current point reaches curEnd, then trigger another jump, and set the new curEnd with curFarthest, then keep the above step. 注意这里是闭区间,另外 i < nums.size() - 1,去掉最后面的边界。
  2. This is an implicit bfs solution. i == curEnd means you visited all the items on the current level. Incrementing jumps++ is like incrementing the level you are on. And curEnd = curFarthest is like getting the queue size (level size) for the next level you are traversing.

参考

  1. 875模拟行走机器人 Maximum? This is crazy!
  2. 860柠檬水
  3. 逗号运算符
  4. std::sort
  5. 买卖股票的最佳时机II(贪心,清晰图解)
  6. Concise O(n) one loop Java solution based on greedy

你可能感兴趣的:(算法,跳台阶,分发饼干,贪心算法,模拟机器人行走,leetcode)