0
点赞
收藏
分享

微信扫一扫

贪心算法--介绍和例题

若如初梘 2022-04-02 阅读 46

贪心算法是把问题分成很多个步骤,然后在每个步骤当中选取当前的最优解,不考虑每一步对后面的影响,在后面操作时也不回头改变之前的步骤。简单来说就是“目光短浅”。

使用贪心法的条件是局部最优解等于全局最优解

目录

1、如何判断一个题目能用贪心法解决?

2、贪心法的使用方法

3、硬币问题

4、分发糖果问题

5、买卖股票的最佳时机2

6、部分背包问题

6、总结


1、如何判断一个题目能用贪心法解决?

(1)问题的局部最优能扩展到整体最优,也就是说:一个问题的最优解包含其子问题的最优解;

(2)问题的最优解可以通过一系列局部的最优解的选择来得到,就相当于我们在具体操作时,每次都保存和整体最优最接近的局部最优解,最后直接返回这个局部最优。

2、贪心法的使用方法

贪心没有模板,正常的题目也不会单独的考察贪心(事实上,能用贪心解决的都能用dp解决)很多同学在没接触过贪心的情况下就用贪心做了不少题。贪心一般会跟可以数组、排序、二分、或者是c++里面一些奇形怪状的函数结合起来,使用了贪心的算法思想,代码实现可以很轻松,而且不易出错。

3、硬币问题

默认带着很多硬币去购物,他有1元的、2元的和5元的,需要支付M元,问怎样支付才能使支付的硬币的数量最少。

方案:先拿出5元的,再拿出2元的,最后拿出1元的。硬币总数最少。

4、分发糖果问题

题目链接

135. 分发糖果 - 力扣(LeetCode) (leetcode-cn.com)icon-default.png?t=M276https://leetcode-cn.com/problems/candy/

 首先是对题目的理解:任意两个相邻的孩子,就像示例1,第一个孩子的糖果就不能是一个,因为他比第二个孩子的分数高,而第三个孩子的糖果也只能是2个,同样因为它比第二个孩子的分数高。所以,我们不能对数组只按一个方向遍历一次。

步骤:

1、创建两个数组left和right,分别表示向左和向右遍历rating数组两次得到的每个孩子的糖果数。创建两个数组的原因是避免重复。

2、因为left和right的大小必定一样,for循环同时遍历这两个数组,将对应两个元素的最大值相加,得出结果。

代码:

int candy(vector<int>& ratings) {
        int left[20005] = { 1 };
        int right[20005] = { 1 };
        int len = ratings.size();
        int sum = 0;
        for (int i = 1; i < len; i++)
        {
            left[i] = 1;
            right[i] = 1;
            if (ratings[i] > ratings[i - 1])
                left[i] = left[i - 1] + 1;
        }
        for (int i = len - 2; i >= 0; i--)
        {
            if (ratings[i] > ratings[i + 1])
                right[i] = right[i + 1] + 1;
        }
        for (int i = 0; i < len; i++)
        {
            sum += max(left[i], right[i]);
        }
        return sum;
    }

5、买卖股票的最佳时机2

题目链接:

122. 买卖股票的最佳时机 II - 力扣(LeetCode) (leetcode-cn.com)icon-default.png?t=M276https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/

 这一题最能体现出贪心法的思想优越性,我们不按照题目示例的方法来买卖。我们的方法简言之就是有钱赚就卖。即我们买进股票,在第二天判断卖出这张股票的利润是否大于0,大于0就马上卖了,管他赚多少,反正是赚了。最后将每一次的利润相加,得到最大利润。在具体实现过程中,我们还可以省略具体的买卖计算过程,也就是在今天判断今明两天的价格是否有钱赚,有,就加上这次交易的利润,至于具体的买卖流程,我们不关心。

代码:

int maxProfit(vector<int>& prices) {
        int len = prices.size();
        int sum = 0;
        for(int i = 0; i < len - 1; i++)
        {
            sum += max(0,prices[i + 1] - prices[i]);
        }
        return sum;
    }

6、部分背包问题

 这题用dp写会更加简单。每堆金币的价值重量比都不一样,所以我们需要找到价值重量比最大的那一堆,然后争取把它全部拿下,在找到价值重量比第二的一堆.......没错,就是排序。每一个金堆都有两个元素,怎样快速的堆二维数组排序呢。我用的是最快(la)速(ji)的选择排序。还请有知道的dalao评论或私信赐教啊,小白我不胜感激!具体的代码实现也有一些技巧

#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
	int sum = 0;
	double ans = 0;
	int n = 0;
	int k = 0;
	scanf("%d%d", &n, &k);  //k表示背包容量
	double arr[100][2] = { 0 };
	for (int i = 0; i < n; i++)
	{
		int a = 0;
		int b = 0;
		cin >> a >> b;
		arr[i][0] = a;
		arr[i][1] = b * 1.0 / a;
	}
	for (int i = 0; i < n; i++) //从大到小排序 ,价值比高的在前,价值比相等时重量大的在前
	{
		for (int j = i + 1; j < n; j++)
		{
			if (arr[i][1] < arr[j][1] || (arr[i][1] == arr[j][1] && arr[i][0] < arr[j][0]))
			{
				double tmp = arr[i][0];
				arr[i][0] = arr[j][0];
				arr[j][0] = tmp;
				double ret = arr[i][1];
				arr[i][1] = arr[j][1];
				arr[j][1] = ret;
			}
		}
	}
	int q = 0;
	while (q < n && sum < k)
	{
		if (sum + arr[q][0] <= k)
		{
			sum += arr[q][0];
			ans += arr[q][0] * arr[q][1];
		}
		else
		{
			int s = k - sum;  //还剩下这么多空间
			sum += (arr[q][0] - s);
			ans += s * arr[q][1];
			break;
		}
		q++;
	}
	printf("%.2lf", ans);
	return 0;
}

6、总结

我自己是很喜欢用贪心法解题的,因为它不像dp那样有那么多的条条框框,可以随意发挥自己的想法和经验(但也不要太随意),难度较高的贪心题一般都是先找到一个最关键的贪心点,对这个元素执行最完善的贪心,便可以很轻松的解题。

PS:请随意根据你您的想法和经验,自行ps

理论参考:《算法竞赛入门到进阶》罗勇军 郭卫斌著,清华大学出版社

举报

相关推荐

0 条评论