贪心算法题目总结

1. 整数替换

看到这道题目,我们首先能想到的方法就应该是递归解法,我们来画个图

此时我们出现了重复的子问题,就可以使用递归,只要我们遇到偶数,直接将n除以2递归下去,如果是奇数,选出加1和减1中最小步数的那个继续递归下去即可,直接看代码:

class Solution {
public:
    int integerReplacement(int n) {
        if(n == 1)
            return 0;
        if(n & 1 == 1) // 奇数
            return min(integerReplacement(n+1),integerReplacement(n-1)) + 1;
        else
            return integerReplacement(n / 2) + 1;
    }
};

然后我们去运行代码,很遗憾,此时代码没有通过,这是因为的递归过程中出现了大量的重复子问题的,这些重复的子问题其实我们已经计算过了,没必要再计算一次,所以我们可以利用记忆化搜索的备忘录来解决这个问题,此时我们递归的时候,如果此时备忘录立马存在这个值,我们就可以直接返回,如果没有这个值,我们再去递归。

class Solution {
    unordered_map<int,int> hash;
public:
    int integerReplacement(int n) {
        // 先判断是否再在备忘录中
        if(hash.count(n)) 
        {
            return hash[n];
        } 
        if(n == 1)
        {
            hash[1] = 0;
            return 0;
        }  
        if(n & 1 == 1) // 奇数
        {
            hash[n] = min(integerReplacement(n+1),integerReplacement(n-1)) + 1;
            return hash[n];
        }
        else
        {
            hash[n] = integerReplacement(n / 2) + 1;
            return hash[n];
        }  
    }
};

但是此时我们发现程序没有通过,通过这个测试案例我们可以知道,当n是2147483647的时候,此时是奇数,会去递归2147483647+1,此时就超过了int的范围。

此时我们需要自己来定义一个dfs函数来解决这个问题。

class Solution {
    unordered_map<int,int> hash;
public:
    int integerReplacement(int n) 
    {
        return dfs(n);
    }
    int dfs(long long int n){
        // 先判断是否再在备忘录中
        if(hash.count(n)) 
        {
            return hash[n];
        } 
        if(n == 1)
        {
            hash[1] = 0;
            return 0;
        }  
        if(n & 1 == 1) // 奇数
        {
            hash[n] = min(dfs(n+1),dfs(n-1)) + 1;
            return hash[n];
        }
        else
        {
            hash[n] = dfs(n / 2) + 1;
            return hash[n];
        }  
    }
};

此时我们的代码就能通过,我们再来写另一种解法。

直接上手代码:

class Solution {
public:
    int integerReplacement(int n) {
        return dfs(n);
    }
    int dfs(long long int n)
    {
        if(n == 1)
            return 0;
        if(n % 2 == 0) // 偶数
        {
            return dfs(n/2) + 1;
        } 
        else
        {
            if(n == 3)
            {
                return dfs(n-1) + 1;
            }
            else if(n % 4 == 1)
            {
                return dfs(n - 1) + 1;
            }
            else if(n % 4 == 3)
            {
                return dfs(n + 1) + 1;
            }
        }
        // 这里有返回值是因为要让所有的路径都有返回值
        return 0;
    }
};

其实我们这里也可以不使用递归来解决这个题目。

class Solution {
public:
    int integerReplacement(int n) {
        int ret = 0;
        while(n > 1)
        {
            if(n % 2 == 0) // 偶数
            {
                n /= 2;
                ret++;
            }
            else // 奇数
            {
                if(n == 3)
                {
                    n = 1;
                    ret += 2;
                }
                else if(n % 4 == 1)
                {
                    n -= 1;
                    n /= 2;
                    ret += 2;
                }
                else if(n % 4 == 3)
                {
                    n /= 2; // 这里需要先除2,防止n+1溢出
                    n += 1;
                    ret += 2;
                }

            }
        }
    return ret;
    }
};

2. 俄罗斯套娃信封问题

首先我们看到这个题目,就和我们之前的最长递增子序列是一样的,我们需要对左端点进行排序,然后看是否满足俄罗斯套娃的条件,既然是找最长的套娃个数,此时我们可以使用动态规划来解决这个问题,我们可以假设dp[i]位置表示i位置之前的此时最长的套娃个数,当我们到i的位置的时候,我们需要看看0到i-1位置的最长的套娃个数,如果有一个条件满足俄罗斯套娃的条件,那么最长的套娃个数就可以+1,此时就可以使用动态规划来解决这个问题。

class Solution {
public:
    int maxEnvelopes(vector<vector<int>>& envelopes) {
        // 动态规划
        sort(envelopes.begin(), envelopes.end());
        int n = envelopes.size();
        vector<int> dp(n, 1);
        int ret = 1;

        for(int i = 1; i < n; i++)
        {   
            for(int j = 0; j < i; j++)
            {
                if(envelopes[i][0] > envelopes[j][0] &&
                    envelopes[i][1] > envelopes[j][1])
                    {
                        dp[i] = max(dp[i], dp[j]) + 1;
                    }
            }
            ret = max(ret, dp[i]);
        }
        return ret;
    }
};

但是此时我们的代码会超时,所以此时我们就需要来使用另一种策略

class Solution {
public:
    int maxEnvelopes(vector<vector<int>>& envelopes) {
        // 排序
        sort(envelopes.begin(), envelopes.end(), [&](const vector<int>& v1, const vector<int>& v2)
        {
            // 左端点不同的时候
            return v1[0] != v2[0] ? v1[0] < v2[0] : v1[1] > v2[1];
        });

        vector<int> ret;
        ret.push_back(envelopes[0][1]);

        for(int i = 1; i < envelopes.size(); i++)
        {
            int b = envelopes[i][1];
            if(b > ret.back())
            {
                ret.push_back(b);
            }
            else
            {
                int left = 0, right = ret.size() - 1;
                while(left < right)
                {
                    int mid = (left + right) / 2;
                    if(ret[mid] >= b) right = mid;
                    else left = mid + 1;
                }
                ret[left] = b;
            }
        }
        return ret.size(); 
    }
};

3. 可被三整除的最大和

正难则反: 我们可以先把所有的数累加在⼀起,然后根据累加和的结果,贪心的删除⼀些数。

那么我们该如何寻找到最小的和次小的值呢?当然我们可以进行一下sort排序取出最小的和次小的值,但是我们知道sort的底层使用的是快速排序,时间复杂度是O(N*logN),所以此时并不是最优的选择,我们可以使用O(N)的时间复杂度来解决这个问题:

class Solution {
public:
    int maxSumDivThree(vector<int>& nums) {
        int x1 = 0x3f3f3f3f3f;
        int x2 = 0x3f3f3f3f3f;
        int y1 = 0x3f3f3f3f3f;
        int y2 = 0x3f3f3f3f3f;
        int sum = 0;
        // 求出总和,最小和次小的值
        for(int i = 0; i < nums.size(); i++)
        {
            sum += nums[i];
            if(nums[i] % 3 == 1) // 判断当值余数是否为1
            {
                if(nums[i] <= x1)
                    x2 = x1, x1 = nums[i];
                else if(nums[i] > x1 && nums[i] < x2)
                    x2 = nums[i];
            }
            else if(nums[i] % 3 == 2) // 判断当值余数是否为2
            {
                if(nums[i] <= y1)
                    y2 = y1, y1 = nums[i];
                else if(nums[i] > y1 && nums[i] < y2)
                    y2 = nums[i];
            }
        }
        // 分类讨论
        if(sum % 3 == 0) return sum;
        else if(sum % 3 == 1) return max(sum - x1, sum - y1 - y2);
        else return max(sum - y1, sum - x1 - x2);
    }
};

4. 距离相等的条形码

这道题目比较简单,我们只需要每次处理一批相同的数字,往 n 个空里面摆放; 每次摆放的时候,隔一个格子摆放一个数,但是我们需要优先处理出现次数最多的那个数,其余的数处理的顺序就没什么要求了。

class Solution {
public:
    vector<int> rearrangeBarcodes(vector<int>& barcodes) {
        unordered_map<int, int> hash; // 统计每个数出现的次数
        int maxVal = 0; // 最大的值
        int maxCount = 0; // 最大值的项数
        for(auto& e : barcodes)
        {
            if(maxCount < ++hash[e])
            {
                maxCount = hash[e];
                maxVal = e;
            }
        }
        vector<int> v(barcodes.size(), 0);
        // 先处理出现次数最多的数
        int index = 0;
        for(int i = 0; i < maxCount; i++)
        {
            v[index] = maxVal;
            index += 2;
        }
        // 处理剩下的数,不用管顺序,直接防即可
        hash.erase(maxVal);
        for(auto& [x, y] : hash)
        {   
            for(int i = 0; i < y; i++)
            {
                // 超过最大位置,需要重新从头开始摆放
                if(index >= barcodes.size()) 
                    index = 1;
                v[index] = x;
                index += 2;
            }
        }
        return v;
    }
};

5. 重构字符串

我们读完题目会发现这道题目和上一道题目是类似的,只不过上一道题目是重排数字,而这道题目是重排字符串,思路基本上都是一致的,但是上一个题目明确告诉了我们所有数字都是可以重排列的,但是我们这个题目不一定,因此我们需要先判断一下,如果出现最多的字符的个数大于(n + 1) / 2 就无法重排,其他情况下就可以直接重排列,那我们直接上代码啦!

class Solution {
public:
    string reorganizeString(string s) {
        unordered_map<char, int> hash;
        char maxVal = ' ';
        int maxCount = 0;
        for(auto& x : s)
        {
            if(maxCount < ++hash[x])
            {
                maxCount = hash[x];
                maxVal = x;
            }
        }

        if(maxCount > ((s.size() + 1) / 2))
            return "";
        // 先处理出现次数最多的那个字符
        string ret;
        ret.resize(s.size());
        int index = 0;
        for(int i = 0; i < maxCount; i++)
        {
            ret[index] = maxVal;
            index += 2;
        }
        hash.erase(maxVal); // 删除最多元素项的值
        // 处理剩余的值
        for(auto& [x, y] : hash)
        {
            for(int i = 0; i < y; i++)
            {
                if(index >= ret.size()) index = 1;
                ret[index] = x;
                index += 2;
            }
        }
        return ret;

    }
};

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

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

相关文章

面试框架一些小结

springcloud的⼯作原理 springcloud由以下⼏个核⼼组件构成&#xff1a; Eureka&#xff1a;各个服务启动时&#xff0c;Eureka Client都会将服务注册到Eureka Server&#xff0c;并且Eureka Client还可以反过来从Eureka Server拉取注册表&#xff0c; 从⽽知道其他服务在哪⾥ …

Java+JSP+Mysql+Tomcat实现Web图书管理系统

简介&#xff1a; 本项目是基于springspringmvcJdbcTemplate实现的图书馆管理系统&#xff0c;包含基本的增删改查功能&#xff0c;可作为JavaWeb初学者的入门学习案例。 环境要求&#xff1a; java8 mysql5.7及以下 eclipse最新版 项目目录 模块设计 页面设计 1. 登录页…

【Spring Boot】认识 JPA 的接口

认识 JPA 的接口 1.JPA 接口 JpaRepository2.分页排序接口 PagingAndSortingRepository3.数据操作接口 CrudRepository4.分页接口 Pageable 和 Page5.排序类 Sort JPA 提供了操作数据库的接口。在开发过程中继承和使用这些接口&#xff0c;可简化现有的持久化开发工作。可以使 …

汽车尾灯(转向灯)电路设计

即当汽车进行转弯时,司机打开转向灯,尾灯会根据转向依次被点亮,经过一定的间隔后,再全部被消灭。不停地重复,直到司机关闭转向灯。 该效果可由以下电路实现: 完整电路图: 02—电路设计要点 延时电路的要点主要有两个: 一、当转向开关被按下时,LED需要逐个亮起; 二、LED被逐…

【AI编译器】triton学习:编程模型

介绍 动机 在过去十年里&#xff0c;深度神经网络 (DNNs) 已成为机器学习 (ML) 模型的一个重要分支&#xff0c;能够实现跨领域多种应用中的最佳性能。这些模型由一系列包括参数化&#xff08;如滤波器&#xff09;和非参数化&#xff08;如缩小值函数&#xff09;元件组成的…

STM32 HAL库里 串口中断回调函数是在怎么被调用的?

跟着正点原子学习的HAL库写串口接收程序的时候一直有困惑&#xff0c;使用HAL_UART_Receive_IT开启接收中断后&#xff0c;为啥处理函数要写在HAL_UART_RxCpltCallback里&#xff0c;中断发生的时候是怎么到这个回调函数里去的&#xff1f; void MX_USART1_UART_Init(void) {h…

x-api-eid-token参数分析与加密算法还原

文章目录 1. 写在前面2. 接口分析3. 算法实现 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python…

操作符详解(下) (C语言)

操作符详解下 操作符的属性1.优先级2.结合级 表达式求值1.整型提升2.如何进行整形提升呢&#xff1f;3.算术转换4.问题表达式解析 操作符的属性 C语言的操作符有2个重要的属性&#xff1a;优先级、结合性&#xff0c;这两个属性决定了表达式求值的计算顺序。 1.优先级 优先级…

【操作系统期末速成】 EP04 | 学习笔记(基于五道口一只鸭)

文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、正文&#xff1a;☀️☀️☀️2.1 考点七&#xff1a;进程通信2.2 考点八&#xff1a;线程的概念2.3 考点九&#xff1a;处理机调度的概念及原则2.4 考点十&#xff1a;调度方式与调度算法 一、前言&#x1f680;…

DM 的断点续传测试

作者&#xff1a; 大鱼海棠 原文来源&#xff1a; https://tidb.net/blog/4540ae34 一、概述 DM有all、full、incremental三种数据迁移同步方式&#xff08;task-mode&#xff09;&#xff0c;在all同步模式下&#xff0c;因一些特殊情况&#xff0c;需要变更上游MySQL的数…

【解释】i.MX6ULL_IO_电气属性说明

【解释】i.MX6ULL_IO_电气属性说明 文章目录 1 Hyst1.1 迟滞&#xff08;Hysteresis&#xff09;是什么&#xff1f;1.2 GPIO的Hyst. Enable Field 参数1.3 应用场景 2 Pull / Keep Select Field2.1 PUE_0_Keeper — Keeper2.2 PUE_1_Pull — Pull2.3 选择Keeper还是Pull 3 Dr…

Coursera耶鲁大学金融课程:Financial Markets 笔记Week 03

Financial Markets 本文是学习 https://www.coursera.org/learn/financial-markets-global这门课的学习笔记 这门课的老师是耶鲁大学的Robert Shiller https://en.wikipedia.org/wiki/Robert_J._Shiller Robert James Shiller (born March 29, 1946)[4] is an American econom…

Analyze an ORA-12801分析并行 parallel 12801 实际原因

"ORA-06512: at "PKG_P_DATA", line 19639 ORA-06512: at "PKG_P_DATA", line 19595 ORA-06512: at "PKG_P_DATA", line 14471-JOB 调用 -ORA-12801: error signaled in parallel query server P009, instance rac2:dwh2 (2) Error: ORA-12…

Python实现无头浏览器采集应用的反爬虫与反检测功能解析与应对策略

Python实现无头浏览器采集应用的反爬虫与反检测功能解析与应对策略 随着网络数据的快速增长&#xff0c;爬虫技术在数据采集、信息分析和业务发展中扮演着重要的角色。然而&#xff0c;随之而来的反爬虫技术也在不断升级&#xff0c;给爬虫应用的开发和维护带来了挑战。为了应…

Linux多进程和多线程(三)进程间通讯-信号处理方式和自定义处理函数

进程间通信之信号 信号信号的种类 信号在操作系统中的定义如下: 信号的处理流程在 Linux 中对信号的处理⽅式 自定义信号处理函数 信号的发送 kill() 函数:raise() 函数: 示例 : 创建⼀个⼦进程&#xff0c;⼦进程通过信号暂停&#xff0c;⽗进程发送 终⽌信号等待信号 pause()…

【嵌入式Linux】<总览> 多线程(更新中)

文章目录 前言 一、多线程 1. 概述 2. 创建线程 3. 线程退出 4. 线程回收 5. 线程分离 6. 线程取消 7. 线程的ID比较 二、线程同步 前言 记录学习多线程的知识重点与难点&#xff0c;若涉及版权问题请联系本人删除&#xff01; 一、多线程 1. 概述 线程是轻量级的…

期末考试后班主任如何发布学生成绩?

期末考试成绩一出&#xff0c;家长们便急切地想要了解孩子的学习情况。以往&#xff0c;老师们需要一个个私信家长&#xff0c;将成绩单发送出去&#xff0c;这项工作既繁琐又耗时。期末之际&#xff0c;老师们的工作本就繁重&#xff0c;如何有效减轻他们的负担&#xff0c;让…

构建现代医疗:互联网医院系统源码与电子处方小程序开发教学

本篇文章&#xff0c;笔者将探讨互联网医院系统的源码结构和电子处方小程序的开发&#xff0c;帮助读者更好地理解和掌握这些前沿技术。 一、互联网医院系统源码结构 互联网医院系统通常由多个模块组成&#xff0c;每个模块负责不同的功能。以下是一个典型的互联网医院系统的主…

【云原生】Prometheus 使用详解

目录 一、前言 二、服务监控概述 2.1 什么是微服务监控 2.2 微服务监控指标 2.3 微服务监控工具 三、Prometheus概述 3.1 Prometheus是什么 3.2 Prometheus 特点 3.3 Prometheus 架构图 3.3.1 Prometheus核心组件 3.3.2 Prometheus 工作流程 3.4 Prometheus 应用场景…

Linux 进程信号篇

文章目录 1. 生活中的信号2. 信号的概念3. 信号的产生3.1 系统调用3.2 软件条件3.2 异常3.3 Core和Term的区别 4. 信号的保存5. 信号的处理5.1 地址空间的进一步理解5.2 键盘输入数据的过程5.3 理解OS如何正常运行5.3.1 OS如何运行5.3.2 如何理解系统调用 5.4 内核态和用户态 6…