贪心算法是一种 在每一步选择中,采取在当前状态下最好或最优的选择,从而希望通过局部最优获取全局最优。比如,跳台阶II,每次,我们从区间中选择跳最远的。
经典贪心算法实践:求图中的最小生成树, 求哈夫曼编码等。
题目: 给定不同面额的硬币 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];
}
};
题目: 机器人在一个无限大小的网格上行走,从点 (0, 0) 处开始出发,面向北方。该机器人可以接收以下三种类型的命令:
-2:向左转 90 度
-1:向右转 90 度
1 <= x <= 9:向前移动 x 个单位长度
在网格上有一些格子被视为障碍物。
第 i 个障碍物位于网格点 (obstacles[i][0], obstacles[i][1])
机器人无法走到障碍物上,它将会停留在障碍物的前一个网格方块上,但仍然可以继续该路线的其余部分。
返回从原点到机器人所有经过的路径点(坐标为整数)的最大欧式距离的平方。
思路: 这道题感觉没有贪心的思想啊,只是没走一步需要更新res
,需要注意以下几点:
dire[4][2] = { {1, 0}, {0, -1}, {-1, 0}, {0, 1}}
,这个代表着,往 上右下左四个方向走。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;
}
};
题目: 在柠檬水摊上,每一杯柠檬水的售价为 5 美元。
顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。
如果你能给每位顾客正确找零,返回 true ,否则返回 false
思路: 首先我们是没有任何钱,这里有以下三种情况:
five++
five <=0 ; return false
,five >0, ten++, five--
;ten >0, ten--, five--
ten < 0, five -= 3
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;
}
};
题目: 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
思路: 这题用贪心的思路,有两个思路:
这里用第一种思路;
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
为从大到小排列。
题目: 给定一个数组,它的第 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(贪心,清晰图解)
题目: 给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
思路:也没啥思路,就是说,我们需要定义个,我们能跳到的距离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;
}
};
**题目:**给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
思路: 看链接: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;
}
};
注意以下两句话:
- 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
,去掉最后面的边界。- 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.