首页 > 编程语言 >贪心算法题解

贪心算法题解

时间:2024-03-17 11:59:43浏览次数:30  
标签:10 return nums int 题解 ret 算法 序列 贪心

前言

大家好,我是jiantaoyab,这篇文章将给大家介绍贪心算法和贪心算法题目的练习和解析,贪心算法的本质就是每一个阶段都是局部最优,从而实现全局最优。我们在做题的同时,不仅要把题目做出来,还要有严格的证明。

柠檬水找零

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

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

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

题目分析

如果顾客给5块钱,就收下。

如果顾客给10块钱,查找有没有5块钱,没有return false,有找5块钱

如果顾客给20块钱,此时有2种策略。

  1. 给顾客找10块钱和5块钱
  2. 给顾客找3张5块钱

在这道题目中5块钱的作用是很大的,假如我给顾客3张5块钱,那我剩下的5块钱就少了很多,当遇到下一个顾客可能不能找零,此时生意就做不成了。

我们用交换论证法证明一下

image-20240314133541087

代码

class Solution {
public:
    bool lemonadeChange(vector<int>& bills) 
    {
       int five = 0, ten = 0;
       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;
    }
};

将数组和减半的最少操作次数

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

题目分析

这个题目还是很好理解的,只要我们每次选出数组中最大的数减半,直到数组和减少到一半就是结果了。

选出最大的元素可以用大根堆。

用交换论证法证明,当完全遍历一次,就能得到结果了。

image-20240314142424681

代码

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

最大数

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

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

题目分析

题目要求返回组成的最大整数,那就要求大的数要放到前面,假设有2个数a,b,那么有3种情况。

ab > ba 此时 a 放到 b 的前面

ab =ba 此时 a和 b 谁放到前面都可以

ab < ba 此时 a放到 b 的前面

这么看这个过程,和排序的过程不是一样的吗?我们知道a>b,b>c,是能推出a>c的?那么在这道题中怎么证明能推出这个结论呢?

我们先来看看全序关系,全序关系用 “<=”表述 , 满足全序关系的有3个特点。

  • 反对称性:如果a≤b且b≤a,则a=b。
  • 传递性:如果a≤b且b≤c,则a≤c。
  • 完全性:对于集合中的任意两个元素,它们之间要么可以比较,要么其中一个元素大于另一个元素。

可以看到我们要证明出全序关系就行。

证明完全性:

ab 和 ba 我们看出一个数,那他们之间是能比较大小的,能比较大小就有可能存在一个元素大于另一个元素。

证明反对称性:ab <= ba ab >= ba ==> ab =ba

假设 a代表x位,b代表y位,那么ab 改写成 a* 10^y + b,b * 10^x + a;

a* 10^y + b 我举个例子,假如a= 100,b=20,那么ab = 10020,想要拼接是不是先得在100后面补2个0那就是b的位数。

带入上面的式子得
1 : a ∗ 1 0 y + b < = b ∗ 1 0 x + a 1:a* 10^y + b <= b * 10^x + a 1:a∗10y+b<=b∗10x+a

2 : a ∗ 1 0 y + b > = b ∗ 1 0 x + a 2:a* 10^y + b >= b * 10^x + a 2:a∗10y+b>=b∗10x+a

3 : a ∗ 1 0 y + b = = b ∗ 1 0 x + a 3:a* 10^y + b == b * 10^x + a 3:a∗10y+b==b∗10x+a

夹逼定理
a ∗ 1 0 y + b < = b ∗ 1 0 x + a < = a ∗ 1 0 y + b a* 10^y + b <= b * 10^x + a <=a*10^y+b a∗10y+b<=b∗10x+a<=a∗10y+b
所以
a ∗ 1 0 y + b = = b ∗ 1 0 x + a a* 10^y + b == b * 10^x + a a∗10y+b==b∗10x+a

最后证明传递性:

对于任意的 a,b,c, ab>=ba 且 bc<=cb ==> ac > ca;

假设 a代表x位,b代表y位,c代表z位。具体的改写和上面同理

特殊情况,a = b = c = 0 的话,还是套上面的公式,在本题中0 是可以当做一位数的
a ∗ 1 0 y + b > = b ∗ 1 0 x + a a* 10^y + b >= b * 10^x + a a∗10y+b>=b∗10x+a
通过移项,改写成;


a ∗ ( 1 0 y − a ) > = ( 1 0 x − b ) ∗ b a*(10^y -a) >= (10^x - b)*b a∗(10y−a)>=(10x−b)∗b
同样的,将剩下的2个式子也改写
b ∗ ( 1 0 z − 1 ) > = ( 1 0 y − 1 ) ∗ c b*(10^z -1) >= (10^y - 1)*c b∗(10z−1)>=(10y−1)∗c

a ∗ ( 1 0 z − 1 ) > = ( 1 0 x − 1 ) ∗ c a*(10^z -1) >= (10^x - 1)*c a∗(10z−1)>=(10x−1)∗c

可以看到最后的式子是没有b的,我们把b消去就行,我们现在讨论的是a,b,c至少都是有1位数的情况,本题0也能当成一位数,所以能当成分母除。
( 1 0 y − 1 / 1 0 x − 1 ) ∗ a > = b (10^y-1/10^x-1)*a >=b (10y−1/10x−1)∗a>=b

b > = ( 1 0 y − 1 / 1 0 z − 1 ) ∗ c b>=(10^y-1/10^z-1)*c b>=(10y−1/10z−1)∗c

通过上面2个式子的化简移项,最后得出
a ∗ ( 1 0 z − 1 ) > = ( 1 0 x − 1 ) ∗ c a*(10^z -1) >= (10^x - 1)*c a∗(10z−1)>=(10x−1)∗c
所以是满足全序关系的,所以我们这个题目是能排序的。

代码

class Solution {
public:
    string largestNumber(vector<int>& nums) {
      //将数字转化为字符串
      vector<string> tmp;
      for(auto x : nums) tmp.push_back(to_string(x));

      //排序
      sort(tmp.begin(), tmp.end(),[](const string& s1, const string& s2)
      {
        return s1 + s2 > s2 + s1;
      });

      //返回结果
      string ret;
      for(auto s : tmp) ret += s;
      if(ret[0] == '0') return "0";
      return ret;

    }
};

摆动序列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 **摆动序列 。**第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。

  • 例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。
  • 相反,[1, 4, 7, 2, 5][1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。

子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。

给你一个整数数组 nums ,返回 nums 中作为 摆动序列最长子序列的长度

题目分析

可以看出题目要求求最长的摆动的子序列,把数列在坐标系中用点的形式画出来,然后连成折线。

在这里插入图片描述

用反证法证明

图像是连续的,所以上面所说的波峰和波谷在数学上称作极值点。

下面图片在贪心解中一个有4个极值点,假如最优解在这个情况下,能选更多的点,那它一定比4个极值点更多。

在这里插入图片描述

在这里插入图片描述

代码

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
      int n = nums.size();
      if(n < 2) return n;
      int ret = 0, left_trend = 0;
      for(int i = 0; i < n - 1; i++)
      {
        int right_trend = nums[i + 1] - nums[i];
        if(right_trend == 0) continue; //平台直接跳过
        // <= 0 把起点也上
        if(right_trend * left_trend <= 0) ret++; //波峰或者波谷
        left_trend = right_trend;
      }

      return ret + 1; //把终点加上
    }
};

最长递增子序列

这道到题目前面的记忆化搜索的文章中已经解决过了,这次用贪心算法的思想来解答

题目分析

image-20240317114427363

在这里插入图片描述

但是上面这样的操作时间复杂度是On^2,并没有优化。这道题我们并不需要关系这个序列长什么样子,我们只关心最后一个元素是谁。
用二分来优化
在这里插入图片描述

代码

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
    int n = nums.size();
    vector<int> ret;
    ret.push_back(nums[0]);
    for(int i = 1; i < n; i++)
    {
      //大于最后一个元素不用二分,新开空间放入
      if(nums[i] > ret.back())
      {
        ret.push_back(nums[i]);
      }
      else
      {
        
        int left = 0, right = ret.size() - 1;
        //二分插入位置
        while(left < right)
        {
          int mid = (left + right) >> 1;
          if(ret[mid] < nums[i]) left = mid + 1;
          else  right = mid;
        }
        ret[left] = nums[i];
      }
    }
    return ret.size();
  }
};



标签:10,return,nums,int,题解,ret,算法,序列,贪心
From: https://blog.csdn.net/weixin_53425006/article/details/136779544

相关文章

  • codeforce Round 934 div2 个人题解(A~C)
    A.DestroyingBridges时间限制:1秒内存限制:256兆输入:标准输入输出:标准输出有$n$个岛屿,编号为$1,2,…,n$。最初,每对岛屿都由一座桥连接。因此,一共有$\frac{n(n-1)}{2}$座桥。Everule住在岛屿$1$上,喜欢利用桥梁访问其他岛屿。Dominater有能力摧毁最多$k$座......
  • AtCoder-abc345_f题解
    题意简述给定一个无向图。你要在其中选出一些边,使得选出的边所构成的图中,度数为奇数的点有\(K\)个。如果可以,输出选了哪些边,否则输出-1。思路首先在选一条边时,边两端点度数的奇偶性一定都会改变,即要么都变为奇数,要么两个点的奇偶性交换过来,要么都变为偶数。这三种情况时满足......
  • P2633 Count on a tree 题解
    题目链接:Countonatree大概可以认为是树上主席树的板子我在之前的某些题解提到了,主席树一般来说有两个基本功能:可持久化功能,可以选择回退或者新增版本。对于可差性问题,可以有更好的转化为前缀和做法,常见的问题为权值类型问题。在树上的路径第\(k\)大,显然如果我们能......
  • kmp算法next数组详解
             kmp算法是一项特别重要的算法,它的难点主要在于next数组的求解。##首先next[i]表示字符串下标i前子字符串(s[0~i-1])的最长相同前后缀的值。以字符串s="ababbacaba"为例子分析。前缀:aababaababababbababba ababbac ababbaca ababbacab后缀:aba......
  • 【洛谷 P8661】[蓝桥杯 2018 省 B] 日志统计 题解(滑动窗口+优先队列+双端队列+集合)
    [蓝桥杯2018省B]日志统计题目描述小明维护着一个程序员论坛。现在他收集了一份“点赞”日志,日志共有NNN行。其中每一行的格式是tsid,表示在......
  • 【洛谷 P8602】[蓝桥杯 2013 省 A] 大臣的旅费 题解(图论+深度优先搜索+树的直径+链式
    [蓝桥杯2013省A]大臣的旅费题目描述很久以前,T王国空前繁荣。为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。为节省经费,T国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。同......
  • 文心一言 VS 讯飞星火 VS chatgpt (216)-- 算法导论16.2 3题
    三、假定在0-1背包问题中,商品的重量递增序与价值递减序完全一样。设计一个高效算法求此背包问题的变形的最优解,证明你的算法是正确的。如果要写代码,请用go语言。文心一言:在0-1背包问题中,如果商品的重量递增序与价值递减序完全一样,那么我们可以利用这个特性设计一种高效的算法......
  • 代码随想录算法训练营第十天(栈和队列I)| 232. 用栈实现队列、225. 用队列实现栈(JAVA)
    文章目录栈和队列理论基础概念方法栈队列232.用栈实现队列解题思路源码225.用队列实现栈解题思路源码总结栈和队列理论基础概念栈:后进先出队列:先进先出方法栈方法名作用Stackstack=newStack<>();构造栈stack.push(Ee)将e入栈,并返回estack.pop()将栈......
  • 【WSN覆盖优化】基于蛇群算法优化无线传感器覆盖优化附matlab代码
     ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,代码获取、论文复现及科研仿真合作可私信。......
  • 【无人机三维路径规划】基于磷虾群算法KH实现复杂地形下无人机避障三维航迹规划附Matl
     ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,代码获取、论文复现及科研仿真合作可私信。......