数据结构与算法:贪心与相关力扣题455.分发饼干、376.摆动序列、53.最大子数组和(贪心+动态规划dp)、122.买卖股票的最佳时机Ⅱ

贪心算法

贪心策略在实现时,经常使用到的技巧:
根据某标准建立一个比较器来排序
根据某标准建立一个比较器来组成堆

举例题目:会议室安排

一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。 给你每一个项目开始的时间和结束的时间(给你一个数组,里面是一个个具体的项目)还有这个会议室开放的最早时间,你来安排宣讲的日程,要求会议室进行的宣讲的场次最多。 返回这个最多的宣讲场次。

我们发现,以会议开始的时间越早越先被安排是不对的,可以轻易举出反例如下,因为这样的话我们只会安排会议a。
在这里插入图片描述

同时,如果以会议持续的时间越短,越先被安排,也是不对的。如下我们只会安排会议b。
在这里插入图片描述

正确的应该是以会议结束的时间越早,越先安排,代码如下:

public class Program{
        public int start;
        public int end;
        public Program(int start, int end){
            this.start = start;
            this.end = end;
        }
    }
    public static class ProgramComparator implements Comparator<Program>{
        @Override
        public int compare(Program p1, Program p2){
            return p1.end-p2.end;
        }
    }
    public boolean ManageMeetings(Program[] programs, int timePoint) {
        Arrays.sort(programs, new ProgramComparator());
        int result = 0;
        for(int i=0;i<programs.length;i++){
            if(programs[i].start>=timePoint){
                timePoint=programs[i].end;
                result++;
            }
        }
        return result;
    }

贪心算法正确性的实战验证(对数器)

1.实现一个不依靠贪心策略的解法X,可以用最暴力的尝试
2.脑补出贪心策略A、贪心策略B、贪心策略C.,.
3.用解法X和对数器,去验证每一个贪心策略

举例:字符串重排后字典序最小(说明理论上来验证贪心的正确性十分复杂)

给定一组字符串数组,重新排列每个字符串的顺序,使之拼接后组成一个字典序最小的字符串。

如果字符串a和字符串b拼接,a在前的话我们记作a#b,b在前的话我们记作b#a。
这道题的正确贪心策略是,如果a#b的字典序小于b#a的字典序,那么我们优先安排a。
那么如何证明这个策略是有效的呢?首先我们要证明这个策略具有传递性。

什么是传递性,就是譬如一般的数字比较,如果a<b,b<c,那么我们就能得出a<c。
有什么策略是不具有传递性的吗?有的,譬如剪刀石头布就不具有传递性。

也就是说,我们接下来要证明如下:(黄色的小于号代表是字典序小于的意思)
if a#bb#a
and b#cc#b
then a#cc#a
证明具体过程如下:我们记字符串a的长度为stra,字符串b的长度为strb,字符串c的长度为strc
字典序就是将字符串转化为十六进制后,比较数字的大小。那么a#b的字典序就是a16^strb+b(从这里开始,之后的a都代表着原来的字符串转化为十六进制后的数,其他字母同理)
在这我们再次记16^strb为m(b),那么a#b的字典序就是a
m(b)+b,即

a#b=a*m(b)+b
b#a=b*m(a)+a

b#c=b*m(c)+c
c#b=c*m(b)+b

a#c=a*m(c)+c
c#a=c*m(a)+a
我们要证明的再次转变成了(以下的小于号代表的就是数学上的小于号)
if 			a*m(b)+b<b*m(a)+a,记为式子A
and 		b*m(c)+c<c*m(b)+b,记为式子B
then 	a*m(c)+c<c*m(a)+a,记为式子C

我们将式子A-b再*c,得到a*m(b)*c<(b*m(a)+a-b)*c
右边部分展开来,即a*m(b)*c<b*m(a)*c+a*c-b*c
此时左边部分,记作X

我们将式子B-c再*a,得到b*m(c)*a<(c*m(b)+b-c)*a
右边部分展开来,即b*m(c)*a<c*m(b)*a+b*a-c*a
再将右边的最后两项移到左边来,即b*m(c)*a+c*a-b*a<c*m(b)*a
此时右边部分,记作Y

我们发现X=Y。那么就可以推出b*m(c)*a+c*a-b*a<X/Y<b*m(a)*c+a*c-b*c
也就是b*m(c)*a+c*a-b*a<b*m(a)*c+a*c-b*c
把相同项c*a去掉,得到b*m(c)*a-b*a<b*m(a)*c-b*c
再把相同因子b去掉,得到m(c)*a-a<m(a)*c-c
再把左右的常数项调换位置,得到m(c)*a+c<m(a)*c+a
我们发现,这就是式子C,得证

这只是第一步,证明传递性,我们还有之后证明,两两交换位置后,策略没有更优。三三交换位置后,策略没有更优,四四交换位置后,策略没有更优……
所以不建议理论上去证明贪心的正确性,直接用对数器在实践上证明即可。

举例题目:

一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的金
条,不管切成长度多大的两半,都要花费20个铜板。
一群人想整分整块金条,怎么分最省铜板?
例如,给定数组{10,20,30},代表一共三个人,整块金条长度为10+20+30=60.
金条要分成10,20,30三个部分。如果先把长度60的金条分成10和50,花费60;
再把长度50的金条分成20和30,花费50;一共花费110铜板。
但是如果先把长度60的金条分成30和30,花费60;再把长度30金条分成10和20,
花费30;一共花费90铜板。
输入一个数组,返回分割的最小代价。

lc455.分发饼干

class Solution(object):
    def findContentChildren(self, g, s):
        """
        :type g: List[int]
        :type s: List[int]
        :rtype: int
        """
        # 排序g和排序s,遍历s序列,找寻与s[i]最接近的数值。g[i]<=s[i] 双指针, 时间复杂度O(m+n)
        g.sort()  
        s.sort()  
        i, j = 0, 0 
        ans = 0

        while i < len(g) and j < len(s):
            if s[j] >= g[i]:  # 如果当前饼干能满足当前孩子
                ans += 1
                i += 1  # 移动到下一个孩子
            j += 1  # 无论饼干是否满足能满足当前孩子,饼干指针都前进。
            # 因为已经是排序后的了,当前饼干都满足不了当前孩子的话,说明饼干不够大

        return ans

时间复杂度:35ms,击败85.07%,时间复杂度为O(nlogn)

lc376.摆动序列

思路

首先想到的是,保留峰值,即驻点,局部最大值或局部最小值。然后将处于单调上坡或单调下坡的元素都删掉。
那么很重要的一个问题就是,首尾元素算不算峰值呢?

代码1:错误示范,将首尾元素算为峰值

本人一开始想的是,首尾元素算是峰值,肯定会被记录的。
所以第一版代码如下:

class Solution(object):
    def wiggleMaxLength(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums) == 1:
            return 1
        if len(nums) == 2:
            if nums[0] != nums[1]:
                return 2
            else:
                return 1
        if len(nums) ==3:
            if nums[1] < nums[0] and nums[1]<nums[2]:
                return 3
            elif nums[1] > nums[0] and nums[1]>nums[2]:
                return 3
            else:
                return 1
        # 保留峰值(首尾元素都算),将单调坡上的元素都删除
        ans = 2
        for i in range(1, len(nums)-1):
            # 从第二个元素到倒数第二个元素遍历
            if nums[i] > nums[i+1] and nums[i] > nums[i-1]:
                # 高峰峰值
                ans += 1
            elif nums[i] < nums[i+1] and nums[i] < nums[i-1]:
                # 低峰峰值
                ans += 1
        return ans

但实际上这个代码实现是有问题,就是当遇到有相邻重复元素,也就是平坡时,会漏算一些可能的摆动值。
譬如序列:[0, 1, 1, 0],返回的是2而不是预期结果3。

代码2:错误示范,将首尾元素算为峰值并考虑平坡元素

如上代码原本本人是没有加对于len为3的边界情况判断的,也是因为提交后发现对于[0,0,0]通不过,当时没想到这是平坡的情况,以为单纯是长度为3而中间元素又刚好与首尾元素相同时,应该额外加个边界判断。而现在考虑了平坡的情况,所以长度为3的边界判断就不必如此复杂了于是代码变为如下:

class Solution(object):
    def wiggleMaxLength(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums) == 1:
            return 1
        if len(nums) == 2:
            if nums[0] != nums[1]:
                return 2
            else:
                return 1
        if len(nums) == 3:
            ans = 1
        else:
            ans = 2
        i = 1
        while i < len(nums)-1:
            # 从第二个元素到倒数第二个元素遍历
            if nums[i] > nums[i+1] and nums[i] > nums[i-1]:
                # 高峰峰值
                ans += 1
            elif nums[i] < nums[i+1] and nums[i] < nums[i-1]:
                # 低峰峰值
                ans += 1
            elif nums[i] == nums[i+1] and nums[i]!=nums[0] and nums[i]!= nums[len(nums)-1]:
                # 遇到平坡元素,且不与首尾元素相同时,将平坡所有元素算作1个
                ans += 1
                # 跳过后面平坡的重复值
                while nums[i] == nums[i+1]:
                    i += 1
            i += 1
        return ans

但是这个代码还是有问题,就是它没有记录单调坡是上行还是下行的情况,而且又将首尾元素默认一定是峰值并记录,遇到以下情况就会行不通。譬如序列[1,7,7,9,9,5],我们将平坡都算作一个元素,那么就相当于序列[1,7,9,5],可以看到最后9和5是在同一段单调下行坡上的。

代码3:错误示范,用布尔值来记录上下行状态的变化

为此我们应该使用一个变量来跟踪当前摆动的状态(上升或下降)。只将首元素默认为峰值并记录,根据最后的摆动状态来判断是否应该加入尾元素。在此我选择了布尔值,代码如下:

class Solution(object):
    def wiggleMaxLength(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums) == 1:
            return 1
        if len(nums) == 2:
            if nums[0] != nums[1]:
                return 2
            else:
                return 1
        ans = 1
        pre_diff = None
        # 记录上下行状态,True时说明是上行
        for i in range(1, len(nums)):
            # 从第二个元素到最后一个元素遍历
            diff = (nums[i]-nums[i-1]) > 0 
            if diff != pre_diff:
                # 说明是峰值
                ans += 1
                pre_diff = diff
        return ans        

然而这也是错误的,因为布尔值是无法区分平坡元素和下行元素的。当遇到[7,3]和[7,7]时,diff都是False,然而对于平坡元素和下行元素,我们是要进行不同的处理的,前者我们需要浓缩所有平坡元素为1个,再加入答案,后者我们是直接加入答案。

代码4:正确示范

class Solution(object):
    def wiggleMaxLength(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums) == 1:
            return 1
        if len(nums) == 2:
            if nums[0] != nums[1]:
                return 2
            else:
                return 1
        ans = 1
        pre_diff = 0
        # 记录上下行状态,>0时说明是上行,<0时说明是下行,==0时说明是平坡
        for i in range(1, len(nums)):
            # 从第二个元素到最后一个元素遍历
            diff = nums[i]-nums[i-1]
            if (diff > 0 and pre_diff <= 0) or (diff < 0 and pre_diff >= 0):
                # 说明是峰值
                ans += 1
                pre_diff = diff
            # 而因为diff不为0,所以只会记录到与首元素不相同的第一个平坡元素。
        return ans

效率:0ms,击败100.00%,时间复杂度O(N)

lc53.最大子数组和

贪心

连续子数组,也可以想到前缀和的思想,在这里我们思考一下,一个长度为a的序列的和的最大值,一定是从以每一个元素为结尾(包含该元素)的各个序列的和中挑选出最大值的。

那么就可以看成【前面a-1个元素的序列的和的最大值】和【最后一个数值】这两部分。
此时如果【前面a-1个元素的序列的和的最大值】是负数的话,我们应该直接舍去这部分,只要【最后一个数值】这一部分,就能达到最大。
所以代码如下:

class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        ans = nums[0]
        pre_sum = nums[0]
        for i in range(1, len(nums)):
            if pre_sum < 0:
                pre_sum = nums[i]
            else:
                pre_sum += nums[i]
            ans = max(ans, pre_sum)
        return ans
        

效率:63ms,击败97.54%,时间复杂度为O(N)。

动态规划

这道题也有动态规划的解法:

class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        # 看到连续子数组,应该想到dp动态规划。设 dp[i] 表示以 nums[i] 结尾(包含nums[i])的子数组的最大和。
        # 对于每个元素 nums[i],我们可以选择将其与前面的子数组相加,或者只选择 nums[i] 本身
        ans = nums[0]
        dp = [nums[0]] * len(nums)
        for i in range(1, len(nums)):
            dp[i] = max(dp[i-1]+nums[i], nums[i])
            ans = max(ans, dp[i])
        return ans

效率:击败20%-60%不等,时间复杂度为O(N)。

lc122.买卖股票的最佳时机

思路

这道题如果用贪心来解的话,需要想清楚一点就是,举例:

输入:prices = [1,2,3,4]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 4 天 (股票价格 = 4)的时候卖出, 这笔交易所能获得利润 = 4 - 1 = 3。
最大总利润为 3 。

在【第 1 天买入,在第 4 天卖出】所收获的利润
实际上等于
【(在第1天买入, 在第二天卖出的利润)+(在第2天买入,在第3天卖出的利润 )+ (在第3天买入,在第4天卖出的利润)】
即 4-1 = (2-1) + (3-2) + (4-3)
只要当天和前一天的利润差为正值,我们就卖出。那么结果就是所有正值利润差之和。贪心贪就贪在,因为买入无成本,所以每天都试图卖出并买入

class Solution(object):
    def maxProfit(self, prices):
        """
        :type prices: List[int]
        :rtype: int
        """
        ans = 0
        # 贪心,每天都试图买入和卖出
        for i in range(1, len(prices)):
            # 当天与前一天的价格差
            cur_diff = prices[i] - prices[i-1]
            if cur_diff > 0:
                ans += cur_diff
        return ans

效率:4ms,击败99.02%,时间复杂度O(N)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/897278.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Go 1.19.4 命令调用、日志、包管理、反射-Day 17

1. 系统命令调用 所谓的命令调用&#xff0c;就是通过os&#xff0c;找到系统中编译好的可执行文件&#xff0c;然后加载到内存中&#xff0c;变成进程。 1.1 exec.LookPath&#xff08;寻找命令&#xff09; 作用&#xff1a; exec.LookPath 函数用于在系统的环境变量中搜索可…

2024年中国工业大模型行业发展研究报告|附43页PDF文件下载

工业大模型伴随着大模型技术的发展&#xff0c;逐渐渗透至工业&#xff0c;处于萌芽阶段。 就大模型的本质而言&#xff0c;是由一系列参数化的数学函数组成的计算系统&#xff0c;且是一个概率模型&#xff0c;其工作机制是基于概率和统计推动进行的&#xff0c;而非真正的理解…

C++ 进阶:类相关特性的深入探讨

⭐在对C 中类的6个默认成员函数有了初步了解之后&#xff0c;现在我们进行对类相关特性的深入探讨&#xff01; &#x1f525;&#x1f525;&#x1f525;【C】类的默认成员函数&#xff1a;深入剖析与应用&#xff08;上&#xff09; 【C】类的默认成员函数&#xff1a;深入剖…

SpringBoot使用RestTemplate实现发送HTTP请求

Java 实现发送 HTTP 请求&#xff0c;系列文章&#xff1a; 《Java使用原生HttpURLConnection实现发送HTTP请求》 《Java使用HttpClient5实现发送HTTP请求》 《SpringBoot使用RestTemplate实现发送HTTP请求》 1、RestTemplate 的介绍 RestTemplate 是 Spring 框架提供的一个用…

【java】抽象类和接口(了解,进阶,到全部掌握)

各位看官早安午安晚安呀 如果您觉得这篇文章对您有帮助的话 欢迎您一键三连&#xff0c;小编尽全力做到更好 欢迎您分享给更多人哦 大家好我们今天来学习Java面向对象的的抽象类和接口&#xff0c;我们大家庭已经来啦~ 一&#xff1a;抽象类 1.1:抽象类概念 在面向对象的概念中…

12寸FAB厂试产到量产实现无纸化要素之软硬件

在12寸先进封装半导体车间从试产到量产的过程中&#xff0c;实现生产过程无纸化&#xff0c;需要综合考虑软硬件的配置。以下是一些关键的规划建议&#xff1a; 1、生产文档管理系统&#xff08;PDM&#xff09;&#xff1a; 采用基于SOLIDWORKS PDM开发的无纸化方案&#xf…

uniapp 整合 OpenLayers - 加载Geojson数据(在线、离线)

Geojson数据是矢量数据&#xff0c;主要是点、线、面数据集合 Geojson数据获取&#xff1a;DataV.GeoAtlas地理小工具系列 实现代码如下&#xff1a; <template><!-- 监听变量 operation 的变化&#xff0c;operation 发生改变时&#xff0c;调用 openlayers 模块的…

Java面试场景题(1)---如何使用redis记录上亿用户连续登陆天数

感谢uu们的观看&#xff0c;话不多说开始~ 对于这个问题&#xff0c;我们需要先来了解一下~ 海量数据都可以用bitmap来存储&#xff0c;因为占得内存小&#xff0c;速度也很快 我大概计算了一下~ 完全够&#xff1a;String类型512M 1byte 8个bit位 8个状态 512M1024byt…

数据库性能测试报告总结模板

1计划概述 目的&#xff1a;找出系统潜在的性能缺陷 目标&#xff1a;从安全&#xff0c;可靠&#xff0c;稳定的角度出发&#xff0c;找出性能缺陷&#xff0c;并且找出系统最佳承受并发用户数&#xff0c;以及并发用户数下长时间运行的负载情况&#xff0c;如要并发100用户&a…

CTFHUB技能树之SQL——字符型注入

开启靶场&#xff0c;打开链接&#xff1a; 直接指明是SQL字符型注入&#xff0c;但还是来判断一下 &#xff08;1&#xff09;检查是否存在注入点 1 and 11# 返回正确 1 and 12# 返回错误 说明存在SQL字符型注入 &#xff08;2&#xff09;猜字段数 1 order by 2# 1 order…

颠覆编程!通义灵码、包阅AI、CodeGeeX三大AI助手解锁无限潜力!

随着科技的疾速前行&#xff0c;人工智能&#xff08;AI&#xff09;辅助编程工具已跃然成为软件开发领域及编程爱好者群体中不可或缺的得力助手。这些融入了尖端智能化算法的工具&#xff0c;不仅深刻改变了编程工作的面貌&#xff0c;通过自动化和优化手段显著提升了编程效率…

GJS-WCP

不懂的就问&#xff0c;但我也是二把手......哭死 web GJS-ezssti 很常规的ssti模板注入&#xff0c;只过滤了"/","flag"。 过滤了/,flag 可以利用bash的特性绕过&#xff0c;如字符串截取&#xff0c;环境变量等等。payload1: {{url_for.__globals__[…

柔性数组的使用

//柔性数组的使用 #include<stdio.h> #include<stdlib.h> #include<errno.h> struct s {int i;int a[]; }; int main() {struct s* ps (struct s*)malloc(sizeof(struct s) 20 * sizeof(int));if (ps NULL){perror("malloc");return 1;}//使用这…

react18中在列表项中如何使用useRef来获取每项的dom对象

在react中获取dom节点都知道用ref&#xff0c;但是在一个列表循环中&#xff0c;这样做是行不通的&#xff0c;需要做进一步的数据处理。 实现效果 需求&#xff1a;点击每张图片&#xff0c;当前图片出现在可视区域。 代码实现 .box{border: 1px solid #000;list-style: …

Math类、System类、Runtime类、Object类、Objects类、BigInteger类、BigDecimal类

课程目标 能够熟练使用Math类中的常见方法 能够熟练使用System类中的常见方法 能够理解Object类的常见方法作用 能够熟练使用Objects类的常见方法 能够熟练使用BigInteger类的常见方法 能够熟练使用BigDecimal类的常见方法 1 Math类 1.1 概述 tips&#xff1a;了解内容…

Java | Leetcode Java题解之第493题翻转对

题目&#xff1a; 题解&#xff1a; class Solution {public int reversePairs(int[] nums) {Set<Long> allNumbers new TreeSet<Long>();for (int x : nums) {allNumbers.add((long) x);allNumbers.add((long) x * 2);}// 利用哈希表进行离散化Map<Long, Int…

【JAVA】第三张_Eclipse下载、安装、汉化

简介 Eclipse是一种流行的集成开发环境&#xff08;IDE&#xff09;&#xff0c;可用于开发各种编程语言&#xff0c;包括Java、C、Python等。它最初由IBM公司开发&#xff0c;后来被Eclipse Foundation接手并成为一个开源项目。 Eclipse提供了一个功能强大的开发平台&#x…

AI 编译器学习笔记之四 -- cann接口使用

1、安装昇腾依赖 # CANN发布件地址 https://cmc.rnd.huawei.com/cmcversion/index/releaseView?deltaId10274626629404288&isSelectSoftware&url_datarun Ascend-cann-toolkit_8.0.T15_linux-aarch64.run Ascend-cann-nnal_8.0.T15_linux-aarch64.run Ascend-cann-ker…

当下大语言模型(LLM)应用的架构介绍

想要构建你的第一个大语言模型应用&#xff1f;这里有你需要了解的一切&#xff0c;以及你今天就能开始探索的问题领域。 LLM 应用架构 我们的目标是让你能够自由地使用大语言模型进行实验、打造自己的应用&#xff0c;并挖掘那些尚未被人注意的问题领域。为此&#xff0c;Git…

数据类型的通用操作

#通用操作有&#xff1a;for语句遍历&#xff0c;len()统计元素个数&#xff0c;是数据类型间的相互转换&#xff0c;元素的排序&#xff08;正反向&#xff09; 1.for语句遍历若遍历字典则 只去字典中的key(即名词) 2.各数据类型间的数据转换&#xff08;若为字典转化为列表…