贪心算法(1)

 基本概念

贪心算法:解决问题的策略,局部最优->全局最优
1.把解决问题的过程分为若干步;解决每一步的时候,都选择当前看起来最优的解法;“希望”得到全局最优
2.贪心策略的提出是没有标准答案的;

3.“贪心策略”可能是一个错误的方法。

4.正确的贪心策略需要证明。

5.不同题目的贪心策略不同。吸收各种策略,学会证明。

题一.柠檬水找零问题(LeetCode)

题目描述

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

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

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

给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

输入:bills = [5,5,5,10,20]
输出:true
解释:前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。
输入:bills = [5,5,10,10,20]
输出:false
解释:
前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。
对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。
对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。
由于不是每位顾客都得到了正确的找零,所以答案是 false。

题目分析

我们可能收到5元,10元,20元三种数值。5元直接收下即可;10元只有找零5元一种方法;20元有找零一张5元、一张10元和三张5元两种方法。

贪心策略

若收到20元,优先考虑找零5+10,然后再考虑3×5。

题解

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

题二.将数组和减半的最小操作次数(LeetCode)

给你一个正整数数组 nums 。每一次操作中,你可以从 nums 中选择 任意 一个数并将它减小到 恰好 一半。(注意,在后续操作中你可以对减半过的数继续执行操作)

请你返回将 nums 数组和 至少 减少一半的 最少 操作数。

示例 1:

输入:nums = [5,19,8,1]
输出:3
解释:初始 nums 的和为 5 + 19 + 8 + 1 = 33 。
以下是将数组和减少至少一半的一种方法:
选择数字 19 并减小为 9.5 。
选择数字 9.5 并减小为 4.75 。
选择数字 8 并减小为 4 。
最终数组为 [5, 4.75, 4, 1] ,和为 5 + 4.75 + 4 + 1 = 14.75 。
nums 的和减小了 33 - 14.75 = 18.25 ,减小的部分超过了初始数组和的一半,18.25 >= 33/2 = 16.5 。
我们需要 3 个操作实现题目要求,所以返回 3 。
可以证明,无法通过少于 3 个操作使数组和减少至少一半。

示例 2:

输入:nums = [3,8,20]
输出:3
解释:初始 nums 的和为 3 + 8 + 20 = 31 。
以下是将数组和减少至少一半的一种方法:
选择数字 20 并减小为 10 。
选择数字 10 并减小为 5 。
选择数字 3 并减小为 1.5 。
最终数组为 [1.5, 8, 5] ,和为 1.5 + 8 + 5 = 14.5 。
nums 的和减小了 31 - 14.5 = 16.5 ,减小的部分超过了初始数组和的一半, 16.5 >= 31/2 = 15.5 。
我们需要 3 个操作实现题目要求,所以返回 3 。
可以证明,无法通过少于 3 个操作使数组和减少至少一半。

贪心策略

容易想到:每次都选择数组中最大的那个数减半,直到数组和减半为止。 

但是,如何找到数组中的最大的那个数决定了这个题的难易复杂程度。这里我们采用priority_queue。

priority_queue即优先级队列,它的使用场景很多,它底层是用大小根堆实现的,可以用log(n)的时间动态地维护数据的有序性。

特点:

自动排序:自动按照元素的大小进行排序,因此每次取出的元素都是当前最大(或最小)的元素。
高效的插入和删除操作:使用堆的数据结构,插入和删除元素的时间复杂度为O(logn),其中n是容器中的元素数量。

题解 

class Solution {
public:
	int halveArray(vector<int>& nums) {
		double sum = 0.0;
		int count = 0;
		priority_queue<double> heap;
		for (auto x : nums)
		{
			heap.push(x);
			sum += x;
		}
		double half_sum = sum / 2;
		while (half_sum > 0)
		{
			double t = heap.top() / 2;
			heap.pop();
			half_sum -= t;
			count++;
			heap.push(t);
		}
		return count;
	}
};

题三.最大数(LeetCode)

给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。

注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。

示例 1:

输入:nums = [10,2]

输出:“210”

示例 2:

输入:nums = [3,30,34,5,9]

输出:“9534330”

题目分析

排序本质:确定元素的先后顺序。我们需要定义一种排序方法。

贪心策略

把拼接成字符串后,最大的元素放在最前面。如“9”“43”组成的“943”大于“439”,则把“9”放在前面。

优化后的思路:把数转化成字符串,拼接字符串,比较字典序 。

题解 

class Solution {
public:
	string largestNumber(vector<int>& nums) {
		vector<string> strs;
		for (int x : nums)
		{
			strs.push_back(to_string(x));
		}
		sort(strs.begin(), strs.end(), [](const string& s1, const string& s2)
		{
			return s1 + s2 > s2 + s1;
		});
		string ret;
		for (auto s : strs)
		{
			ret += s;
		}
		if (ret[0] == '0') return "0";
		return ret;
	}
};

证明这种排序成立

引入全序关系的概念 

!! 设a为x位数,b为y位数,c为z位数,则ab可以表示成 10^{y}\cdot a+ b

完全性

因为ab仍是数,可以比大小,因此具有完全性。

反对称性

ab ≤ ba且ba ≥ ab,则 ab = ba(ab是两数拼接之后的数)

10^{y}\cdot a+ b\geq 10^{x}\cdot b+ a 且 10^{y}\cdot a+ b\geq 10^{x}\cdot b+ a,由夹逼定理则可知道二者相等。

传递性

ab ≤ ba 且 bc ≥ cb,则ac ≥  ca

10^{y}\cdot a+ b\geq 10^{x}\cdot b+ a 且 10^{z}\cdot b+ c\geq 10^{y}\cdot c+ b,需要得到10^{z}\cdot a+ c\geq 10^{x}\cdot c+ a

移项后得

 (10^{y}-1)\cdot a\geq (10^{x}-1)\cdot b 和 (10^{z}-1)\cdot b\geq (10^{y}-1)\cdot c

相乘后化简得到

(10^{z}-1)\cdot a\geq (10^{x}-1)\cdot c 即证!

因此我们假设的这种排序成立。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值