【算法专场】分治(下)

news2025/4/21 13:14:01

目录

前言

归并排序

思想

912. 排序数组

算法思路

算法代码

LCR 170. 交易逆序对的总数

算法思路

算法代码

315. 计算右侧小于当前元素的个数 - 力扣(LeetCode)

算法思路

算法代码

493. 翻转对

算法思路

算法代码


好久不见~时隔多日,继续来更新我们的算法咯hh~

前言

在算法专栏上一篇,我们讲解了分治是什么,如何利用分治的思想来解决一些排序问题或者找指定元素,而在上篇中,我们主要讲的是利用快排,那么本篇我们就来讲解一下归并排序。

归并排序在我数据结构专栏中有涉及,想要了解的更详细的可以去观看。

归并排序

思想

归并排序也是利用了分治的思想,将一个序列化成一个个子序列,在每个子序列上进行排序,再将已经排好的子序列进行合并,就能得到一个有序的序列。

归并排序和快速排序虽然都是利用了分治的思想,但两者排序的时机不同

快速排序是在序列分割成一个个子序列的过程同时对子序列进行排序的;

而归并排序则是将序列分成一个个子序列之后,在序列合并前进行排序。

简单来说:快排是在分割时就进行排序,归并则是在合并的过程中进行排序

912. 排序数组

这道题虽然在上一篇中已经讲过,但使用的是快排解决,本篇我们就用归并来解决这道题。

算法思路

这里我们采用归并的递归方法来解决。

  • 辅助数组:辅助数组主要是为了存储已经排好序的数组。
  • 创建归并函数:我们需要三个参数,分别是数组、每次递归的左边界left、右边界right。当left>=right时,说明数组已经排完序,此时可以直接返回。若还没有排完序,我们需要对数组进行分割排序。当分割完成后,我们就可以借助辅助数组,对数组进行排序。

算法代码


public class Solution {
    // 临时数组,用于归并排序时暂时存储元素
    int[] tmp;

    /**
     * 对数组进行排序
     *
     * @param nums 待排序的数组
     * @return 排序后的数组
     */
    public int[] sortArray(int[] nums) {
        // 初始化临时数组
        tmp = new int[nums.length];
        // 调用归并排序
        mergeSort(nums, 0, nums.length - 1);
        return nums;
    }

    /**
     * 归并排序算法
     *
     * @param nums 待排序的数组
     * @param left 排序数组的起始位置
     * @param right 排序数组的结束位置
     */
    private void mergeSort(int[] nums, int left, int right) {
        // 当左指针大于等于右指针时,说明已经处理完毕,返回
        if (left >= right) {
            return;
        }
        // 计算中间位置,用于分割数组
        int mid = (left + right) / 2;
        // 对左半部分进行归并排序
        mergeSort(nums, left, mid);
        // 对右半部分进行归并排序
        mergeSort(nums, mid + 1, right);
        // 初始化左右指针和临时数组的计数器
        int i = left, j = mid + 1;
        int cnt = 0;
        // 合并左右两部分数组
        while (i <= mid && j <= right) {
            // 将较小的元素放入临时数组
            if (nums[i] < nums[j]) {
                tmp[cnt++] = nums[i++];
            } else {
                tmp[cnt++] = nums[j++];
            }
        }
        // 处理左半部分剩余的元素
        while (i <= mid) {
            tmp[cnt++] = nums[i++];
        }
        // 处理右半部分剩余的元素
        while (j <= right) {
            tmp[cnt++] = nums[j++];
        }
        // 将排序后的元素从临时数组复制回原数组
        for (int k = left; k <= right; k++) {
            nums[k] = tmp[k - left];
        }
    }
}

时间复杂度为O(n*logn),空间复杂度为O(n).

LCR 170. 交易逆序对的总数

算法思路

这道题其实也是可以用归并排序来解决,在我们归并排序合并左右数组的时候,如果左区间(在合并的时候,左右数组已经是有序的了)中的元素大于右区间的元素,那么此时我们就可以知道逆序对有【mid-left+1】对

示例:(画的有点抽象)

算法代码


public class Solution {
    // 计数器,用于统计逆序对的总数
    int count=0;
    // 临时数组,用于归并排序时暂存数据
    int[] tmp;


    /**
     * 计算逆序对的总数
     *
     * @param record 输入的数组
     * @return 逆序对的总数
     */
    public int reversePairs(int[] record) {
        // 初始化临时数组
        tmp=new int[record.length];
        // 调用归并排序
        mergeSort(record,0,record.length-1);
        // 返回逆序对的总数
        return count;
    }

    /**
     * 归并排序算法
     *
     * @param nums 待排序的数组
     * @param left 排序数组的起始位置
     * @param right 排序数组的结束位置
     */
    private void mergeSort(int[] nums, int left, int right) {
        // 当左指针大于等于右指针时,说明已经处理完毕,返回
        if (left >= right) {
            return;
        }
        // 计算中间位置,用于分割数组
        int mid = (left + right) / 2;
        // 对左半部分进行归并排序
        mergeSort(nums, left, mid);
        // 对右半部分进行归并排序
        mergeSort(nums, mid + 1, right);
        // 初始化左右指针和临时数组的计数器
        int i = left, j = mid + 1;
        int cnt = 0;
        // 合并左右两部分数组
        while (i <= mid && j <= right) {
            // 将较小的元素放入临时数组
            if (nums[i] < nums[j]) {
                tmp[cnt++] = nums[i++];
            } else {
                // 当右半部分的元素小于左半部分的元素时,说明找到了逆序对
                count+=mid-i+1;
                tmp[cnt++] = nums[j++];
            }
        }
        // 处理左半部分剩余的元素
        while (i <= mid) {
            tmp[cnt++] = nums[i++];
        }
        // 处理右半部分剩余的元素
        while (j <= right) {
            tmp[cnt++] = nums[j++];
        }
        // 将排序后的元素从临时数组复制回原数组
        for (int k = left; k <= right; k++) {
            nums[k] = tmp[k - left];
        }
    }
}

 时间复杂度为O(n*logn),空间复杂度为O(n).

315. 计算右侧小于当前元素的个数 - 力扣(LeetCode)

算法思路

这道题是要求每个元素后面有多个比当前元素少的个数,我们可以用归并排序来解决,在归并的时候进行计数。

1. 初始化

我们定义四个数组ret、index、tmpNums、tmpIndex。ret数组用于存储原数组每个元素右侧小于当前元素的个数;index用来存储原始数组元素的下标;tmpNums用于存储归并排序完的元素,而tmpIndex则是存储排序完每个元素对应的原始下标位置。

2. 归并和计数

  1. 定义一个merge方法,不需要返回值。在merge方法中,需要nums,数组的左右边界left、right;
  2. 如果left>=right时或者子数组的长度只有1时,则直接返回,此时不需要进行计数。
  3. 如果left<right,那么就将数组分割成两部分,继续调用merge方法进行递归;在合并左右两个子数组的时候,我们需要借助两个指针cur1和cur2,cur1和cur2指向左子数组和右子数组的起始位置,用一个i当做临时数组的下标。
  4. 在循环whlie(cur1<=mid&&cur2<=right)中,比价左子数组和右子数组的元素:
    1. 如果nums[cur1]<=nums[cur2],将右子数组的当前元素放入到临时数组中,同时将对应的下标放入到tmpIndex中,再将i和cur2往后移;
    2. 如果nums[cur1]>nums[cur2],由于我们这里对数组是进行降序操作,所以说明从[cur2,right]这个区间上所有元素都是小于nums[cur1]的,即nums[cur1]右侧有right-cur2+1个元素小于它,将这个数量累加到ret[index[cur1]]中。再将nums[cur1]放入到临时数组tmpNums中,同时将对应的原始下标放入到tmpIndex中,再将i和cur1往后移。
  5. 当跳出循环后,说明左右子数组中其中有一个全部放入到临时数组中,但其中还有一个子数组没有放入临时数组中,我们利用两个循环来处理左子数组和右子数组的剩余元素。
  6. 当把数组全部放到临时数组后,我们再将临时数组中的元素复制回nums和index数组中。

算法代码

 int[] ret;//用来存放结果
    int[] index;//用来存放原始数组的下标
    int[] tmpNums;//辅助数组,用来存储排序后的数组
    int[] tmpIndex;//辅助数组,用来存储排序后的数组的下标
    public List<Integer> countSmaller(int[] nums) {
        int n = nums.length;
        ret = new int[n];
        index = new int[n];
        tmpNums = new int[n];
        tmpIndex = new int[n];
        for(int i=0;i<n;i++){
            index[i]=i;
        }
        merge(nums,0,n-1);
        List<Integer> list = new ArrayList<>();
        for(int num:ret){
            list.add(num);
        }
        return list;
    }
    private void merge(int[] nums,int left,int right){
        if(left>=right) return;
        int mid = left+(right-left)/2;
        merge(nums,left,mid);
        merge(nums,mid+1,right);
        int cur1=left,cur2=mid+1,i=0;
        while (cur1<=mid && cur2<=right){
            if(nums[cur1]<=nums[cur2]){
                tmpNums[i]=nums[cur2];
                tmpIndex[i++]=index[cur2++];
            }else{
                ret[index[cur1]]+=right-cur2+1;
                tmpNums[i]=nums[cur1];
                tmpIndex[i++]=index[cur1++];
            }
        }
        while (cur1<=mid){
            tmpNums[i]=nums[cur1];
            tmpIndex[i++]=index[cur1++];
        }
        while (cur2<=right){
            tmpNums[i]=nums[cur2];
            tmpIndex[i++]=index[cur2++];
        }
        for(i=left;i<=right;i++){
            nums[i]=tmpNums[i-left];
            index[i]=tmpIndex[i-left];
        }
    }

时间复杂度为(nlogn),空间复杂度为O(n)

493. 翻转对

算法思路

本道题可以用归并的方法来解决,题目要求我们找翻转对,即满足i<j,并且nums[i[>2*nums[j]。

我们可以想成nums[i]元素/2.0后,比nums[j]还要大,这样的话,我们采用归并降序就好解多了。

  1. 初始化:​​​​​​定义一个临时数组tmp,以及一个变量ans,用来存储翻转对的个数;
  2. 归并
    1. 定义一个merge方法,在此方法中,如果left>=right,那么就直接返回
    2. 如果left<right,那么就继续对数组进行分割为两部分递归调用merge方法。
    3. 定义三个变量cur1、cur2、index,cur1和cur2分别指向左右子数组的起始位置。index用做临时数组的下标
  3. 计算翻转对数量
    1. 在循环while(cur1<=mid)中,让左数组中的元素nums[cur1],和右子数组中每个元素nums[cur2]进行比较,查看满足条件nums[cur1]/2.0<=nums[cur2]的位置,如果找到,说明从cur2到right这段区间上的元素都能和nums[cur1]构成翻转对。将right-cur2+1累加到ans中,再让cur1++,查找下一个左子数组元素的翻转对。
  4. 合并两个有序的子数组:套路和上一题差不多,都是类似写法

算法代码

// 临时数组,用于归并排序时的合并操作
int[] tmp;
// 记录翻转对的数量
int ans;

/**
 * 计算数组中的翻转对数量。翻转对定义为:对于下标 i < j,如果 nums[i] > 2 * nums[j],则 (i, j) 是一个翻转对。
 *
 * @param nums 输入的整数数组
 * @return 翻转对的数量
 */
public int reversePairs(int[] nums) {
    tmp = new int[nums.length];
    mergeSort(nums, 0, nums.length - 1);
    return ans;
}

/**
 * 使用归并排序的方法对数组进行排序,并在排序过程中统计翻转对的数量。
 *
 * @param nums 需要排序的数组
 * @param left 当前子数组的左边界
 * @param right 当前子数组的右边界
 */
public void mergeSort(int[] nums, int left, int right) {
    if (left >= right) return;
    int mid = (left + right) / 2;
    mergeSort(nums, left, mid);
    mergeSort(nums, mid + 1, right);

    // 统计当前左右两个子数组之间的翻转对数量
    int cur1 = left, cur2 = mid + 1;
    while (cur1 <= mid) {
        while (cur2 <= right && nums[cur1] / 2.0 <= nums[cur2]) cur2++;
        if (cur2 > right) break;
        ans += right - cur2 + 1;
        cur1++;
    }

    // 合并左右两个已排序的子数组
    cur1 = left;
    cur2 = mid + 1;
    int index = left;
    while (cur1 <= mid && cur2 <= right) {
        tmp[index++] = nums[cur1] <= nums[cur2] ? nums[cur2++] : nums[cur1++];
    }
    while (cur1 <= mid) tmp[index++] = nums[cur1++];
    while (cur2 <= right) tmp[index++] = nums[cur2++];
    for (int i = left; i <= right; i++) nums[i] = tmp[i];
}

时间复杂度为O(n*logN),空间复杂度为O(n)


以上就是本篇所有内容~

若有不足,欢迎指正~

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

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

相关文章

OSPF基础(2):数据包详解

OSPF数据包(可抓包) OSPF报文直接封装在IP报文中&#xff0c;协议号89 头部数据包内容&#xff1a; 版本(Version):对于OSPFv2&#xff0c;该字段值恒为2(使用在IPV4中)&#xff1b;对于OSPFv3&#xff0c;该字段值恒为3(使用在IPV6中)。类型(Message Type):该OSPF报文的类型。…

Docker Desktop安装kubernetes时一直在Starting:Kubernetes failed to start

原因&#xff1a;由于墙的问题&#xff0c;导致拉取国外的K8s镜像失败 解决&#xff1a; 下载 k8s-for-docker-desktop 选中自己的kubernetes 版本 下载zip包 PowerShell运行load_images.ps1文件 重启docker kubernetes运行成功

StarSpider 星蛛 爬虫 Java框架 可以实现 lazy爬取 实现 HTML 文件的编译,子标签缓存等操作

StarSpider 星蛛 爬虫 Java框架 开源技术栏 StarSpider 能够实现 针对 HTML XSS SQL 数学表达式等杂乱数据的 爬取 解析 提取 需求&#xff01; 目录 文章目录 StarSpider 星蛛 爬虫 Java框架目录介绍如何获取&#xff1f;maven配置 架构是什么样的&#xff1f;结果对象的类…

【翻译+论文阅读】DeepSeek-R1评测:粉碎GPT-4和Claude 3.5的开源AI革命

目录 一、DeepSeek-R1 势不可挡二、DeepSeek-R1 卓越之处三、DeepSeek-R1 创新设计四、DeepSeek-R1 进化之路1. 强化学习RL代替监督微调学习SFL2. Aha Moment “啊哈”时刻3. 蒸馏版本仅采用SFT4. 未来研究计划 部分内容有拓展&#xff0c;部分内容有删除&#xff0c;与原文会有…

动态规划LeetCode-121.买卖股票的最佳时机1

给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取的最大利润。…

#渗透测试#批量漏洞挖掘#微商城系统 goods SQL注入漏洞

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

import { Component, Vue, Prop, Watch } from ‘vue-property-decorator‘

文章目录 导入部分的解释总结Vue 3 的推荐替代方案总结 你提供的代码片段是使用 vue-property-decorator 库的示例&#xff0c;这是一个第三方库&#xff0c;它提供了 Vue 组件的装饰器&#xff0c;使得编写类风格的 Vue 组件更加方便。以下是对代码中每个部分的详细解释&…

X Window System 架构概述

X Window System 架构概述 1. X Server 与 X Client ​ 这里引入一张维基百科的图&#xff0c;在Linux系统中&#xff0c;若用户需要图形化界面&#xff0c;则可以使用X Window System&#xff0c;其使用**Client-Server**架构&#xff0c;并通过网络传输相关信息。 ​ ​ X…

【ArcGIS Pro 简介1】

ArcGIS Pro 是由 Esri &#xff08;Environmental Systems Research Institute&#xff09;公司开发的下一代桌面地理信息系统&#xff08;GIS&#xff09;软件&#xff0c;是传统 ArcMap 的现代化替代产品。它结合了强大的空间分析能力、直观的用户界面和先进的三维可视化技术…

启明星辰发布MAF大模型应用防火墙产品,提升DeepSeek类企业用户安全

2月7日&#xff0c;启明星辰面向DeepSeek等企业级大模型业务服务者提供的安全防护产品——天清MAF&#xff08;Model Application Firewall&#xff09;大模型应用防火墙产品正式发布。 一个新赛道将被开启…… DeepSeek的低成本引爆赛道规模 随着DeepSeek成为当前最热的现象级…

小米AI眼镜官微上线,将与小米15 Ultra同台亮相,近屿智能用心培育 AI 人才

近日&#xff0c;小米眼镜官微已正式上线&#xff0c;认证主体为小米通讯技术有限公司。据悉&#xff0c;小米AI眼镜已获得入网许可&#xff0c;并计划提前至2月发布&#xff0c;与小米15 Ultra同台亮相。 此前&#xff0c;小米AI眼镜原定于2025年3月至4月发布。早在去年&#…

Mac下使用brew安装go 以及遇到的问题

首先按照网上找到的命令进行安装 brew install go 打开终端输入go version&#xff0c;查看安装的go版本 go version 配置环境变量 查看go的环境变量配置&#xff1a; go env 事实上安装好后的go已经可以使用了。 在home/go下新建src/hello目录&#xff0c;在该目录中新建…

在rtthread中,scons构建时,它是怎么知道是从rtconfig.h找宏定义,而不是从其他头文件找?

在rtthread源码中&#xff0c;每一个bsp芯片板级目录下都有一个 SConstruct scons构建脚本的入口&#xff0c; 在这里把rtthread tools/目录下的所有模块都添加到了系统路径中&#xff1a; 在tools下所有模块中&#xff0c;最重要的是building.py模块&#xff0c;在此脚本里面…

Unity游戏(Assault空对地打击)开发(7) 爆炸效果

效果 准备 首先请手搓一个敌军基地。 然后添加一个火焰特效插件或者自建。 爆炸脚本编写 新建一个脚本命名为Explode。 无需挂载到对象上。 首先是全部代码。 using System.Collections; using System.Collections.Generic; using System.Linq; using TMPro; using UnityEngine…

嵌入式面试题 C/C++常见面试题整理_7

一.什么函数不能声明为虚函数? 常见的不能声明为虚函数的有:普通函数(非成员函数):静态成员函数;内联成员函数;构造函数;友元函数。 1.为什么C不支持普通函数为虚函数?普通函数(非成员函数)只能被overload&#xff0c;不能被override&#xff0c;声明为虚函数也没有什么意思…

excel实用问题:提取文字当中的数字进行运算

0、前言&#xff1a; 这里汇总在使用excel工作过程中遇到的问题&#xff0c;excel使用wps版本&#xff0c;小规模数据我们自己提取数据可行&#xff0c;大规模数据就有些难受了&#xff0c;因此就产生了如下处理办法。 需求&#xff1a;需要把所有文字当中的数字提取出来&…

【prompt实战】AI +OCR技术结合ChatGPT能力项目实践(BOL提单识别提取专家)

本文原创作者:姚瑞南 AI-agent 大模型运营专家,先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗;多年人工智能行业智能产品运营及大模型落地经验,拥有AI外呼方向国家专利与PMP项目管理证书。(转载需经授权) 目录 1. 需求背景 2. 目标 3. BOL通用处理逻辑…

昇思打卡营第五期(MindNLP特辑)番外:硅基流动 x 华为云DeepSeek V3 API推理MindTinyRAG

1.前言 前脚&#xff0c;DeepSeek面临的巨头企业官宣加入vs多国政府下场质疑的冰火两重天局势尚未平静&#xff08;DeepSeek在美两重天&#xff1a;五大巨头接入&#xff0c;政府诚惶诚恐&#xff09;&#xff1b;后脚&#xff0c;OpenAI被逼急&#xff0c;凌晨亮出全新推理…

DeepSeek R1 Distill Llama 70B(免费版)API使用详解

DeepSeek R1 Distill Llama 70B&#xff08;免费版&#xff09;API使用详解 在人工智能领域&#xff0c;随着技术的不断进步&#xff0c;各种新的模型和应用如雨后春笋般涌现。今天&#xff0c;我们要为大家介绍的是OpenRouter平台上提供的DeepSeek R1 Distill Llama 70B&…

LabVIEW与PLC交互

一、写法 写命令立即读出 写命令后立即读出&#xff0c;在同一时间不能有多个地方写入&#xff0c;因此需要在整个写入后读出过程加锁 项目中会存在多个循环并行执行该VI&#xff0c;轮询PLC指令 在锁内耗时&#xff0c;就是TCP读写的实际耗时为5-8ms&#xff0c;在主VI六个…