排序题+贪心

news2024/12/26 21:21:02

排序力扣题

一:合并区间

56. 合并区间

方法一:先排序再合并

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如图,把区间按照起点从小到达排序,如果起点相同那么按照终点小的优先排序

然后每次记录一个区间,访问下一个区间:

  • 如果下一个区间的起点<=前一个区间的终点,那么把前一个区间终点进行更新,选择两个区间最大的

在这里插入图片描述

  • 如果下一个区间起点>前一个区间终点,说明断开了,把更新后的区间放入结果
 vector<vector<int>> merge(vector<vector<int>>& intervals) {
        //首先对interval排序
        sort(intervals.begin(),intervals.end(),\
            [](vector<int>& a,vector<int>& b){
                return a[0]<b[0] || (a[0]==b[0]&&a[1]<b[1]);
        });
        //初始化区间为-1
        int left=-1;
        int right=-1;
        vector<vector<int>> res;
        for(auto& val:intervals){
            int start=val[0];
            int end=val[1];
            if(start<=right){//区间起点<=上一个区间终点
                right=max(right,end);
            }else{
                //区间存入结果
                if(left!=-1){
                    res.push_back({left,right});
                }
                //更新区间
                left=start;
                right=end;
            }
        }
        //最后一个区间存入结果
        if(left!=-1){
            res.push_back({left,right});
        }
        return res;
    }

当然也可以在res无内容或者存放的末尾的终点<当前区间起点时候存结果

    vector<vector<int>> res;
     for(auto& val:intervals){
         int start=val[0];
         int end=val[1];
         if(res.size()==0 || res.back()[1]<start){
             res.push_back({start,end});
         }else{
             res.back()[1]=max(end,res.back()[1]);
         }
     }
     return res;

方法二:差分

定义一个事件二元组:event

  • 事件的起点:event {区间值,+1}
  • 事件的终点:event{区间值+1,-1}

在这里插入图片描述

所以count从0->正数->0,恰好是求的一个区间

 vector<vector<int>> merge(vector<vector<int>>& intervals) {
        //定义事件
        vector<pair<int,int>> event;
        for(auto& val:intervals){
            event.push_back({val[0],1});
            event.push_back({val[1]+1,-1});
        }
        //排序
        sort(event.begin(),event.end());
        //计数
        int count=0;
        int start=-1;
        vector<vector<int>> ans;
        for(auto& val:event){
            if(count==0)//第一个0:记录起点
                start=val.first;
            count+=val.second;
            if(count==0)//又变成0:成为终点
                ans.push_back({start,val.first-1});
        }
        return ans;
    }

当然也可以直接区间起点+1,区间终点-1,只是需要自定义排序

vector<vector<int>> merge(vector<vector<int>>& intervals) {
    //定义事件,这里记录:{起点,+1},{终点,-1}
    vector<pair<int,int>> events;
    for(auto& val:intervals){
         events.push_back({val[0],+1});
         events.push_back({val[1],-1});
    }
    //排序
    sort(events.begin(),events.end(),\
        [](pair<int,int>& a,pair<int,int>& b){
            return a.first<b.first ||a.first==b.first && a.second>b.second;
     });
    //开始遍历evevt
    int count=0;
    int start=-1;
    vector<vector<int>> ans;
    for(auto& eve:events){
        if(count==0){
             start=eve.first;
        }
        count+=eve.second;
        if(count==0){
             ans.push_back({start,eve.first});
        }
    }
    return ans;
 }

自定义函数也可以写成仿函数:

class cmp {
 public:
     bool operator()(pair<int, int>& e1, pair<int, int>& e2) {
         if (e1.first == e2.first)
             return e1.second > e2.second;
         return e1.first < e2.first;
     }
 // 排序
 sort(events.begin(), events.end(), cmp());

二:翻转对

493. 翻转对

思路:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

​ 递归地对数组进行左右两部分的划分,直至数组长度为1时就结束递归。

这样天然的满足i<j的条件,因为i在左一半数组,j在右一半数组

为了能够使得求逆序对更容易,在合并时候进行排序,然后不断累积求的结果。

遍历一次左一半数组,每次第一个不满足的j值,之前就是符合要求的。

在这里插入图片描述

所以整个过程和分治排序一样,只不过要在合并前统计一次翻转对的个数

统计翻转对的时机:

在得到左右有序序列之后,合并左右有序序列之前。

分治算法的步骤就是:分割+求解+合并

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

分治排序过程:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

解法一:

class Solution {
public:
    int reversePairs(vector<int>& nums) {
        mergeSort(nums,0,nums.size()-1);
        for(auto& val: nums){
            cout<<val<<" ";
        }
        return ans;
    }
private:
    int ans=0;
    void merge(vector<int>& nums,int l,int mid,int r){
        int i=l;
        int j=mid+1;
        int* aux=new int[r-l+1];
        //[l,r]~[0,r-l]
        for(int k=0;k<r-l+1;k++){
            if(i>mid){
                aux[k]=nums[j++];
            }else if(j>r){
                aux[k]=nums[i++];
            }else if(nums[i]<nums[j]){
                aux[k]=nums[i++];
            }else{
                aux[k]=nums[j++];
            }
        }
        //赋值回nums
        for(int i=l;i<=r;i++){
            nums[i]=aux[i-l];
        }
    }
    void mergeSort(vector<int>& nums,int l,int r){
        if(l==r) return;
        int mid=(l+r)>>1;
        //对半划分
        mergeSort(nums,l,mid);
        mergeSort(nums,mid+1,r);
        //统计翻转对个数
        int j=mid+1;
        for(int i=l;i<=mid;i++){
            while(j<=r && (long)nums[i]>(long)2*nums[j]) j++;
            //[mid+1,j-1]是符合要求的
            ans+=j-mid-1;
        }
        //合并排序
        merge(nums,l,mid,r);
    }
};

把统计个数放在合并还原的函数也可以

   int ans=0;
    void merge(vector<int>& nums,int l,int mid,int r){
        int i=l;
        int j=mid+1;
        int* aux=new int[r-l+1];
        //统计翻转对个数
        for(int i=l;i<=mid;i++){
            while(j<=r && (long)nums[i]>(long)2*nums[j]) j++;
            //[mid+1,j-1]是符合要求的
            ans+=j-mid-1;
        }
        //把i,j值还原
        i=l;
        j=mid+1;
        int k=0;
        //[l,r]~[0,r-l]
        while(i<=mid && j<=r){
            if(nums[i]<nums[j]){
                aux[k++]=nums[i++];
            }else{
                aux[k++]=nums[j++];
            }
        }
        while(i<=mid){
            aux[k++]=nums[i++];
        }
        while(j<=r){
            aux[k++]=nums[j++];
        }
        //赋值回nums
        for(int i=0;i<r-l+1;i++){
            nums[i+l]=aux[i];
        }
    }
    void mergeSort(vector<int>& nums,int l,int r){
        if(l==r) return;
        int mid=(l+r)>>1;
        //对半划分
        mergeSort(nums,l,mid);
        mergeSort(nums,mid+1,r);
        //合并排序
        merge(nums,l,mid,r);
    }

贪心算法(Greedy Algorithm

对于一道题,要优先考虑分治,搜索,动态规划等基于全局考虑的算法,如果它们时间复杂度比较高,再去考虑能否利用贪心求解。

贪心算法的难点在于:证明这道题可以利用贪心去求解

贪心算法是一种:

  1. 每一步选择当前状态下的最优决策

也就是求:局部最优解

  1. 然后希望每次局部最优的最终结果也是全局最优

通过每次局部最优达到求全局最优的目的

贪心与搜索和动态规划的区别

  1. 贪心不对整个状态空间进行遍历或计算,而是始终按照局部最优选择执行下去,不会回头

因为局部最优并不一定会得到全局最优,所以需要证明:本题每次局部最优可以得到全局最优

  1. 能利用贪心求解的题目也可以利用搜索和动态规划求解,但是贪心一定是最高效的

​ 贪心算法:

  • 不从整体最优上加以考虑,一步一步进行,每一步只以当前情况为基础,根据某个优化测度做出局部最优选择。
  • 省去了为找到最优解要穷举所有可能所必须耗费的大量时间

贪心算法特征

能用贪心算法解决的问题必须满足下面的两个特征:

  • 贪⼼选择性质

一个问题的全局最优解可以通过一系列局部最优解来得到

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 贪心算法在进行选择时,可能会依赖之前做出的选择,但不会依赖任何将来的选择或是子问题的解

  • 贪心算法解决的问题在程序的运行过程中无回溯过程

  • 最优子结构

最优子结构性质:指的是一个问题的最优解包含其子问题的最优解

问题的最优子结构性质是该问题能否用贪心算法求解的关键

在这里插入图片描述

​ 如果原问题 𝑆 的最优解=「第 𝑎1 步通过贪心选择的局部最优解」+「 子问题𝑆子问题 的最优解」.则说明该问题满足最优子结构性质。

  • 如果不能利用子问题的最优解推导出整个问题的最优解,那么这种问题就不具有最优子结构

力扣题—贪心

一:分饼干

455. 分发饼干

解法一:大饼干分给大孩子

把孩子和饼干按照升序排序,然后遍历孩子,如果孩子胃口值<=当前大饼干,就把饼干分配给他

不然的话,:也就是饼干小于胃口,放弃当前孩子,寻找下一个更小的孩子

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

 int findContentChildren(vector<int>& g, vector<int>& s) {
        //每个孩子i有胃口值g[i]
        //每个饼干j有尺寸值s[j]
        //目标:尽可能的满足孩子多
        int j=0;
        int col=0;
        //降序排序
        sort(g.begin(),g.end(),greater<int>());
        sort(s.begin(),s.end(),greater<int>());
        //遍历胃口,优先把大饼干给大胃口
        for(int i=0;i<g.size() && j<s.size();i++){
             if(g[i]<=s[j]){//分配饼干
                col++;
                j++;
             }
        }
        return col;
    }

循环条件还可以:

  • for(int i=0;i<g.size();i++){
                if(j<s.size() && s[j]>=g[i]){
                    //饼干可以分给当前孩子
                    col++;
                    j++;
                }
            }
    
  • for(int i=0;i<g.size();i++){
                if(j==s.size()) break;
                if(s[j]>=g[i]){
                    //饼干可以分给当前孩子
                    col++;
                    j++;
                }
            }
    
  • int i=0,j=0;
            int col=0;
            //遍历孩子分饼干
            while(i<g.size() && j<s.size()){
                if(s[j]>=g[i]){
                    i++;
                    j++;
                    col++;
                }else{//放弃孩子
                    i++;
                }
            }
    

方法二:小饼干分给小孩子

这种情况是,遍历到下一个孩子之前,需要不断放弃尺寸小饼干,直至找到满足当前孩子胃口饼干后,接着遍历

在这里插入图片描述

int findContentChildren(vector<int>& g, vector<int>& s) {
        //每一个孩子i有胃口g[i]
        //每一块饼干j有尺寸s[j]
        /*孩子和饼干升序排序*/
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());
        //遍历孩子
        int j=0;
        int col=0;
        for(int i=0;i<g.size();i++){
            //遍历饼干,找到一个最小的满足胃口
            while(j<s.size() && s[j]<g[i]) j++;
            if(j<s.size()){
                //找到了
                j++;
                col++;
            }
        }
        return col;
    }

总结:

小饼干分给小孩子,相当于放弃饼干

因为最小饼干不能给小孩子,一定也不能给其他孩子

大饼干分给大孩子,相当于放弃孩子

因为大饼干肯定会被孩子吃,所以优先满足大胃口

二:买卖股票

122. 买卖股票的最佳时机 II

这里同一天既可以买股票,也可以卖股票,还可以买卖同时进行

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如图:后一天比前一天大就累计利润,相当于前一天买后一天卖;后一天比前一天小就跳过

int maxProfit(vector<int>& prices) {
        int profit=0;
        for(int i=1;i<prices.size();i++){
            profit+=max(0,prices[i]-prices[i-1]);
        }
        return profit;
    }

三:跳跃游戏

45. 跳跃游戏 II

分析:假设当前位置为index,而且该位置可以跳的最大距离为:nums[index]

​ 则:枚举1~nums[index]的所有可能,然后选择到达位置后,下一个位置的最远距离

也就是说:如果到达的位置能达到的下一个位置最远,就选该位置

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

 int jump(vector<int>& nums) {
        int n=nums.size();
        int curPos=0;
        int step=0;//跳跃次数
        while(curPos < n-1){//不到达最后位置
            // 当前位置的跳跃步数为0,到达不了
            if(nums[curPos]==0) return -1;
            //枚举位置终点
            int right=curPos+nums[curPos];
            //如果直接能超过终点,直接return
            if(right>=n-1) return step+1;
            int nextPos=curPos+1;
            for(int i=curpos+2;i<=right;i++){
                if(i+nums[i]>nextPos+nums[nextPos])
                       nextPos=i;
            }
            //选择该位置,步数+1
            curPos=nextPos;
            step++;
        }
        return step;
    }

方法二:

将start和end初始化在第一个位置,然后计算可到达的最远距离maxPos

每次:start等于上一个end+1,end选择maxPos,其实这就是遍历每一个位置取最大

在这里插入图片描述

int jump(vector<int>& nums) {
        //start和end初始化第一个位置
        int start=0,end=0;
        int ans=0;//返回结果
        while(end < nums.size()-1){
            int maxPos=0;
            for(int i=start;i<=end;i++){
                maxPos=max(maxPos,i+nums[i]);
            }
            //更新位置
            start=end+1;
            end=maxPos;
            ans++;//跳了一次
        }
        return ans;
    }

上述代码可以优化

  • 可以在i==end统计次数
for (int i = start; i < nums.size() - 1; i++) {
            maxPos = max(maxPos, i + nums[i]);
            //因为i从头遍历到最后,所以==end时候,步数+1
            if (i == end) {
                start = end + 1;
                end = maxPos;
                ans++;
            }
        }
  • 可以没有起点

区间遍历[start,end],start每次更细为end+1,所以相当于从0依次遍历

 for (int i = 0; i < nums.size() - 1; i++) {
            maxPos = max(maxPos, i + nums[i]);
            //因为i从头遍历到最后,所以==end时候,步数+1
            if (i == end) {
                end = maxPos;
                ans++;
            }
        }https://leetcode.cn/problems/minimum-initial-energy-to-finish-tasks/)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构与算法】使用单链表实现队列:原理、步骤与应用

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​ 目录 一、引言 &#x1f384;队列的概念 &#x1f384;为什么要用单链表实现队列 二、单…

Socket编程权威指南(四)彻底解密 Epoll 原理

在上一篇文章中&#xff0c;我们优化了基于 Socket 的网络服务器&#xff0c;从最初的 select/poll 模型进化到了高效的 epoll。很多读者对 epoll 的惊人性能表示极大的兴趣&#xff0c;对它的工作原理也充满了好奇。今天&#xff0c;就让我们一起揭开 epoll 神秘的面纱&#x…

DeepSORT(目标跟踪算法)中的马氏距离详解(很详细)

DeepSORT&#xff08;目标跟踪算法&#xff09;中的马氏距离详解&#xff08;很详细&#xff09; flyfish 马氏距离的公式是由印度统计学家【普拉萨纳钱德拉马哈拉诺比斯&#xff08;Prasanta Chandra Mahalanobis&#xff09;】&#xff09;&#xff08;好长的名字&#xff…

挑战绝对不可能:再证有长度不同的射线

黄小宁 一空间坐标系中有公共汽车A&#xff0c;A中各座位到司机处的距离h是随着座位的不同而不同的变数&#xff0c;例如5号座位到司机处的距离是h3&#xff0c;…h5&#xff0c;…。A移动了一段距离变为汽车B≌A&#xff0c;B中5号座位到司机处的距离h’h3&#xff0c;…h’h5…

Redis原理篇——哨兵机制

Redis原理篇——哨兵机制 1.Redis哨兵2.哨兵工作原理2.1.哨兵作用2.2.状态监控2.3.选举leader2.4.failover 1.Redis哨兵 主从结构中master节点的作用非常重要&#xff0c;一旦故障就会导致集群不可用。那么有什么办法能保证主从集群的高可用性呢&#xff1f; 2.哨兵工作原理 …

Leetcode 力扣114. 二叉树展开为链表 (抖音号:708231408)

给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同。 示例 1&#xf…

力扣每日一题 6/10

881.救生艇[中等] 题目&#xff1a; 给定数组 people 。people[i]表示第 i 个人的体重 &#xff0c;船的数量不限&#xff0c;每艘船可以承载的最大重量为 limit。 每艘船最多可同时载两人&#xff0c;但条件是这些人的重量之和最多为 limit。 返回 承载所有人所需的最小船…

Qt实现简易播放器

效果如图 源码地址&#xff1a; 简易播放器: 基于Qt的简易播放器&#xff0c;底层采用VLC源码 - Gitee.com GitHub:GitHub - a-mo-xi-wei/easy-player: 基于Qt的调用VLC的API的简易播放器

蓝牙安全入门——两道CTF题目复现

文章目录 蓝牙安全入门题目 low_energy_crypto获取私钥解密 题目 蓝牙钥匙的春天配对过程配对方法密钥分发数据加密安全漏洞和保护实际应用实际应用 蓝牙安全入门 &#x1f680;&#x1f680;最近一直对车联网比较感兴趣&#xff0c;但是面试官说我有些技术栈缺失&#xff0c;所…

【Linux】进程7——进程地址空间

1.再谈fork 之前提到了fork之后对父子进程返回不同的id值&#xff0c;给父进程返回子进程的pid&#xff0c;给子进程返回0&#xff0c;所以对于一个id如何存储两个值的说法&#xff0c;在我们之前已经提到过了一个概念叫做写时拷贝&#xff0c;就是在子进程要想修改父进程的id…

AI 边缘计算平台 - 回归开源 BeagleY-AI 简介

BeagleBoard.org 于 3 月 27 号发布了一款单板计算机 BeagleY-AI &#xff0c;这款 SBC 凭借其完全开源的特性&#xff0c;旨在激发并推动开源社区的生态系统繁荣发展。 一、简介&#xff1a; BeagleY-AI 采用德州仪器新推出的 AM67A AI 视觉处理器。这款处理器集成了四个 64…

招聘在家抄书员?小心是骗局!!!

在家抄书员的骗局是一种常见的网络诈骗手段&#xff0c;旨在利用人们想要在家轻松赚钱的心理。这种骗局通常会以招聘兼职抄写员的形式出现&#xff0c;声称只需在家中抄写书籍即可赚取可观的收入。然而&#xff0c;实际上这背后隐藏着诸多陷阱和虚假承诺。 首先&#xff0c;这些…

程序猿大战Python——流程控制——while循环

程序里的循环 目标&#xff1a;了解循环语句的作用。 在程序中&#xff0c;有时候会遇到代码需要重复多次运行的情况。 例如&#xff0c;一起来完成&#xff1a; &#xff08;1&#xff09;在生活中做事没让媳妇儿满意&#xff0c;跟她承认错误&#xff0c;说10遍&#xff1a…

Java加密体系结构参考指南-Java Cryptography Architecture

本文是从英文的官网摘了翻译的&#xff0c;用作自己的整理和记录。水平有限&#xff0c;欢迎指正。版本是&#xff1a;22 原文地址&#xff1a;https://docs.oracle.com/en/java/javase/22/security/java-cryptography-architecture-jca-reference-guide.html#GUID-815542FE-CF…

Vue 2看这篇就够了

Vue 2 技术文档 Vue.js 是一款用于构建用户界面的渐进式框架。与其他重量级框架不同的是&#xff0c;Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三方库或既有项目整合。而 Vue.js 2&#xff08;以下简称 Vue…

Java——IO流(一)-(2/9):File类的常用方法(判断文件类型、获取文件信息、创建删除文件、遍历文件夹)

目录 常用方法1&#xff1a;判断文件类型、获取文件信息 方法 实例演示 常用方法2&#xff1a;创建文件、删除文件 方法 实例演示 常用方法3&#xff1a;遍历文件夹 方法 实例演示 常用方法1&#xff1a;判断文件类型、获取文件信息 方法 File提供的判断文件类型、获…

转型AI产品经理(6):“ 序列位置效应”如何应用在Chatbot产品中

序列位置效应是心理学中的一个记忆现象&#xff0c;指的是人们对一系列信息的记忆效果受到信息在序列中位置的影响。具体来说&#xff0c;人们通常更容易记住列表的开头和结尾部分的项目&#xff0c;而对中间部分的项目记忆较差。这个效应可以进一步分为“首因效应”和“近因效…

32、matlab:基于模板匹配的车牌识别

1、准备工作 1&#xff09;准备材料 车牌字符模板和测试的实验车牌 2&#xff09;车牌字符模板 数字、字母和省份缩写 3&#xff09;测试车牌 四张测试车牌 2、车牌识别实现(已将其嵌入matlab) 1&#xff09;打开APP 找到APP 找到我的APP双击点开 2)界面介绍 包括&am…

最新下载:Navicat for MySQL 11软件安装视频教程

软件简介&#xff1a; Navicat for MySQL 是一款强大的 MySQL 数据库管理和开发工具&#xff0c;它为专业开发者提供了一套强大的足够尖端的工具&#xff0c;但对于新用户仍然易于学习。Navicat For Mysql中文网站&#xff1a;http://www.formysql.com/ Navicat for MySQL 基于…

html+CSS+js部分基础运用19

1. 应用动态props传递数据&#xff0c;输出影片的图片、名称和描述等信息【要求使用props】&#xff0c;效果图如下&#xff1a; 2.在页面中定义一个按钮和一行文本&#xff0c;通过单击按钮实现放大文本的功能。【要求使用$emit()】 代码可以截图或者复制黏贴放置在“实验…