算法-贪心(T1~T3)

本文简单记录三道贪心题目的答案和求解论证过程.

1. 柠檬水找零

1.1 介绍+思路

题目链接: LINK
题目思路: 思路可以简单概括为下图.
在这里插入图片描述

1.2 参考代码如下

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 参考代码如下

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 参考代码

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 证明

下面是图片版:
在这里插入图片描述

下面是文字版:

我们利用离散中的"全序关系"进行证明.
说的不严谨一点, 如果一套排序规则满足全序关系, 那么表明其是可以进行排序的
全序关系主要包含三个部分:

  1. 完全性.
    即 a, b(任意两个元素之间)是能够确定大小关系的.

  2. 反对称性.
    即a <= b, b <= a, 可以==> a == b.

  3. 传递性.
    如果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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值