本文简单记录三道贪心题目的答案和求解论证过程.
目录
-
- [1. 柠檬水找零](#1. 柠檬水找零)
-
- [1.1 介绍+思路](#1.1 介绍+思路)
- [1.2 参考代码如下](#1.2 参考代码如下)
- [1.3 证明](#1.3 证明)
- [2. 将数组和减半的最少操作次数](#2. 将数组和减半的最少操作次数)
-
- [2.1 介绍 + 思路](#2.1 介绍 + 思路)
- [2.2 参考代码如下](#2.2 参考代码如下)
- [2.3 证明](#2.3 证明)
- [3. 最大数](#3. 最大数)
-
- [3.1 介绍 + 思路](#3.1 介绍 + 思路)
- [3.2 参考代码](#3.2 参考代码)
- [3.3 证明](#3.3 证明)
1. 柠檬水找零
1.1 介绍+思路
题目链接: LINK
题目思路: 思路可以简单概括为下图.
1.2 参考代码如下
cpp
class Solution {
public:
bool lemonadeChange(vector<int>& bills)
{
int five = 0, ten = 0;
for(auto& bill : bills)
{
// bill == 5
if(bill == 5) five += 1;
// bill == 10
else if(bill == 10)
{
if(five >= 1) five--, ten++;
else return false;
}
// bill == 20
else
{
if(five >= 1 && ten >= 1) five--, ten--;
else if(five >= 3) five -= 3;
else return false;
}
}
return true;
}
};
1.3 证明
为啥贪心解就是最优解? 下面我们来进行证明, 以确定我们的贪心算法是适用本题的.
我们假设对于一个数列有解(可以找零), 那么我们可以有下面两种策略:
我们假设贪心算法的策略: ... -> a -> b -> c -> d ...
同时我们也可以知道最优解: ... -> A -> B -> C -> D ...
我们的证明方法是: 交换论证法 .
即: 如果可以把最优解通过交换或者等效替换的方式且不失去"最优性"的前提下转换成贪心解, 那么我们就说贪心解是正确的.
下面我们来详细解释:
对于前两种, 最优解和贪心解给出的找钱方式是一样的, 因为没有别的选择!
区别只在于第三种给20块钱的情况.
换言之, 上面我们假设的找钱方式的队列中有很多是相等的, 因为有很多5元和10元的情况.
我们从不相等的一个开始分类讨论, 假如说b 和 B是不相等的, 那么此时意思就是说别人给了你20元
然后我们的贪心解肯定是给的10 + 5元, 而最优解给的是5 + 5 + 5元(请注意我们说的是不相等位置).
在这个前提下, 有两种情况,
一种是10元在最优解中压根没用到, 那么就是说5+5 等价于 10元. (因为没用到啊)
另一种是10元在最优解中用到了, 只不过不是在这个位置用的, 可能在前面或者后面用的, 此时也可以把10元和5+5元进行替换. (因为5+5是更万能的)
综上, 我们说明了如果两个位置的找钱方式不同, 一定可以进行替换或者调换顺序使得在相同位置下两者的值相同.
以此类推, 我们可以证得贪心解和最优解是等价的, 也就是说我们的贪心解法是正确的.
2. 将数组和减半的最少操作次数
2.1 介绍 + 思路
题目链接: LINK
题目思路: 贪心求解, 每次挑选最大的数减半.
2.2 参考代码如下
cpp
class Solution {
public:
int halveArray(vector<int>& nums)
{
priority_queue<double> pqueue;
// 把nums中的元素入到大根堆中
double sum = 0;
for(auto num:nums)
{
pqueue.push(num);
sum += num;
}
sum /= 2.0;
int count = 0;
while(sum > 0) // 减少一半 或者 一半以上都可以
{
double t = pqueue.top() / 2.0;
pqueue.pop();
sum -= t;
count++;
pqueue.push(t);
}
// 返回结果
return count;
}
};
2.3 证明
这道题的证明思路, 我们同样使用交换论证法进行证明.
我们就从第一个不相等的数开始讨论, 假如说b != B.
由"贪心"和最优可知, b >= B.
因为b!=B且b>=B, 我们只需要证明b > B这种情况是可以等效替换的即可
即 b > B:
假如说在最优解中, b没有用过, 那么 b <=> B(两者可以相互替换),
因为越大的数/=2减的越多
假如说在最优解中, 用过b, 那么最优解中的b 和 B交换顺序即可, 也可以
达到使得贪心解和最优解的顺序一致的效果.
在这种情况下, 如果存在后续也使用到B的情况, 比如下面:
并且同时, 因为贪心解每次都是/2的最大的数, 因此length(贪心) <= length(最优解)
然而最优解指的是最小的count次数, 因此length(最优解) <= length(贪心)
所以说length(贪心) == length(最优解)
综上所述, 最优解和贪心解等价, 因此贪心解可行.
3. 最大数
3.1 介绍 + 思路
题目链接: LINK
题目思路: 先排序, 按照 "a+b" > "b+a" -> a在前b在后的方式排序, 然后依次合并.
对于我们传统的排序, 我们的排序条件是: x > y就x在后y在前(如果是升序的话)
但是, 我们这题的排序方式是:
if("x" + "y" >= "y" + "x") ==> x在前, y在后
else if("x" + "y" <= "y" + "x") ==> y在前, x在后
3.2 参考代码
cpp
class cmp
{
public:
bool operator()(const string& s1, const string& s2)
{
string ab = s1 + s2;
string ba = s2 + s1;
return ab > ba;
/*
* 如果ab >= ba, 就返回true, 表示不用交换
* 如果ab < ba, 就返回false, 表示需要进行交换
*/
}
};
class Solution {
public:
string largestNumber(vector<int>& nums)
{
// 把nums字符串化
vector<string> strs;
for(auto num : nums)
{
strs.push_back(to_string(num));
}
// 排序
sort(strs.begin(), strs.end(), cmp());
// 拼接
string ret;
for(auto& str: strs)
{
ret += str;
}
// 去掉前导0
if(ret[0] == '0') return "0";
else return ret;
}
};
小细节:
3.3 证明
下面是图片版:
下面是文字版:
我们利用离散中的"全序关系"进行证明.
说的不严谨一点, 如果一套排序规则满足全序关系, 那么表明其是可以进行排序的
全序关系主要包含三个部分:
-
完全性.
即 a, b(任意两个元素之间)是能够确定大小关系的.
-
反对称性.
即a <= b, b <= a, 可以==> a == b.
-
传递性.
如果a >= b, b >= c, 必须能够推得a >= c.
我们前面排序定义的排序规则是:
"a" + "b" > "b" + "a" ==> "a"+"b"
"a" + "b" < "b" + "a" ==> "b"+"a"
"a" + "b" = "b" + "a" ==> 都可以
证明完全性:
我们设 a:x位 b: y位
- "a"+"b" : 10^y * a + b
- "b + a" : 10^x * b + a
显然两个元素可以由数进行表示, 因此任意两个元素之间可以确定大小关系, 满足完全性.
证明反对称性:
ab <= ba 且 ab >= ba ==> ab == ba
① ② ③
我们用数字表示一下各个表达式
①:10^y * a + b <= 10^x * b + a
②:10^y * a + b >= 10^x * b + a
==> 联立①②: 10^y * a + b <= 10^x * b + a <= 10^y * a + b
由高等数学中的迫敛性定理可知:
==> 10^y * a + b = 10^x * b + a = 10^y * a + b
因此, 可以推得满足反对称性
证明传递性:
我们需要证明: ab >= ba && bc >= cb ==> ac >= ca
① ② ③
同时, 因为"0"在两个数合并的时候是字符串相加的缘故, 因此"0"算个位数(在一般小学数学当中, 0不算个位数).
①: 10^y * a + b >= 10^x * b + a => (10^y - 1) / (10^x - 1) * a >= b
②: 10^z * b + c >= 10^y * c + b => b >= (10^y - 1) / (10^z - 1) * c
连立①②: (10^y - 1) / (10^x - 1) * a >= b >= (10^y - 1) / (10^z - 1) * c
③: 10^z * a + c >= 10^x * c + a => (10^y - 1) / (10^x - 1) * a >= b >= (10^y - 1) / (10^z - 1) * c
所以① + ② -> ③
因此, 满足传递性.
综上, 我们定义的运算规则满足全序关系, 因此可以进行排序, 也证明我们的贪心算法是正确的.
优化: 把数转换成字符串按字典序排序, 不用真的转变成数字, 因为比较麻烦.
问题: 贪心体现在哪里?
答: 比较规则是贪心算法, 以局部最优 -> 全局最优
EOF.