【算法】【优选算法】分治(下)

【算法】【优选算法】分治(下)

目录

一、归并排序

题目链接:归并排序
题目描述:

题目解析:

  • 就是排序数组。

解题思路:

  • 分:将数组⼀分为⼆为两部分,⼀直分解到数组的⻓度为1 ,使整个数组的排序过程被分为「左半部分排序」+「右半部分排序」;
  • 治:将两个较短的「有序数组合并成⼀个⻓的有序数组」,⼀直合并到最初的⻓度。

解题代码:

//时间复杂度:O(NlogN)//空间复杂度:O(n)classSolution{int[] tmp;publicint[]sortArray(int[] nums){ tmp =newint[nums.length];mergeSort(nums,0,nums.length-1);return nums;}//归并排序publicvoidmergeSort(int[] nums,int l,int r){if(l >= r)return;int left = l;int right = r;int mid =(left + right)/2;//分mergeSort(nums, l, mid);mergeSort(nums, mid+1, r);//治:合并两个有序数组int cur1 = left;int cur2 = mid +1;int i = left;while(cur1 <= mid && cur2 <= right){if(nums[cur1]< nums[cur2]) tmp[i++]= nums[cur1++];else tmp[i++]= nums[cur2++];}while(cur1 <= mid) tmp[i++]= nums[cur1++];while(cur2 <= right) tmp[i++]= nums[cur2++];//还原for(int j = left; j <= right; j++){ nums[j]= tmp[j];}}}

二、LCR170.交易逆序对的总数

题目链接:LCR170.交易逆序对的总数
题目描述:

题目解析:

  • 逆序对:逆序对就是指,在该数组元素之后与比它小的元素组成的两个数字,就称为一个逆序对,返回数组中逆序对总数。

2.1 分治思想

解题思路:

  • 两段子数组的逆序对和,我们可以通过递归实现。
  • 那么我们的核心就是求一左一右选取逆序对的和。
  • 直接去遍历求解,复杂度还是高,如果我们将两段子数组排序,那么我们就有两种方式简单求了。
  • 看上面的就是与归并排序的逻辑一摸一样的。

降序排序:我们就找后一个数组中小的元素个数即可(就当nums[ cur2 ] < nums[ cur1 ]时统计即可),即[ mid+1 , right ]。如果求前一个数组大元素,会有重复。

升序排序:我们就找前一个子数组中大的元素个数即可(就当nums[ cur2 ] < nums[ cur1 ]时统计即可),即[cur1 , mid]。如果求后一个数组小元素,会有重复。

其实我们可以将数组分为两段,那么数组的总逆序对和等于:两段子数组的逆序对和 + 前面一段数组中每个元素在后面一段元素组成的逆序对和(就是遍历前面一段数组,在后面一段数组中找比该元素小的元素个数)(简称为一左一右选逆序对)。

解题代码:

//时间复杂度:O(NlogN)//空间复杂度:O(n)//升序版本:classSolution{int[] tmp;publicintreversePairs(int[] nums){int n = nums.length; tmp =newint[n];returnmerageSort(nums,0, n-1);}publicintmerageSort(int[] nums,int left,int right){if(left >= right)return0;int ret =0;int mid =(left + right)/2;//分:两段子数组的逆序对和 ret +=merageSort(nums, left, mid); ret +=merageSort(nums, mid +1, right);//治:合并两个有序数组 + 统计一左一右逆序对个数int cur1 = left, cur2 = mid +1;int i = left;while(cur1 <= mid && cur2 <= right){if(nums[cur1]<= nums[cur2]) tmp[i++]= nums[cur1++];else{ ret +=(mid - cur1 +1); tmp[i++]= nums[cur2++];}}while(cur1 <= mid) tmp[i++]= nums[cur1++];while(cur2 <= right) tmp[i++]= nums[cur2++];//还原for(int j = left; j <= right; j++) nums[j]= tmp[j];return ret;}}//时间复杂度:O(NlogN)//空间复杂度:O(n)//降序版本:classSolution{int[] tmp;publicintreversePairs(int[] nums){int n = nums.length; tmp =newint[n];returnmerageSort(nums,0, n-1);}publicintmerageSort(int[] nums,int left,int right){if(left >= right)return0;int ret =0;int mid =(left + right)/2;//分:两段子数组的逆序对和 ret +=merageSort(nums, left, mid); ret +=merageSort(nums, mid +1, right);//治:合并两个有序数组 + 统计一左一右逆序对个数int cur1 = left, cur2 = mid +1;int i = left;while(cur1 <= mid && cur2 <= right){if(nums[cur1]<= nums[cur2]) tmp[i++]= nums[cur2++];else{ ret +=(right - cur2 +1); tmp[i++]= nums[cur1++];}}while(cur1 <= mid) tmp[i++]= nums[cur1++];while(cur2 <= right) tmp[i++]= nums[cur2++];//还原for(int j = left; j <= right; j++) nums[j]= tmp[j];return ret;}}

2.2 暴力枚举

解题思路:

  • 直接两个for循环,将第一层遍历数组,第二层遍历后续元素比该元素小的数组个数。
  • 会超时。

解题代码:

//时间复杂度:O(n^2)//空间复杂度:O(1)classSolution{publicintreversePairs(int[] record){int ret =0;for(int i =0; i < record.length; i++){for(int j = i +1; j < record.length; j++){if(record[i]> record[j]) ret++;}}return ret;}}

三、315.计算右侧⼩于当前元素的个数

题目链接:315.计算右侧⼩于当前元素的个数
题目描述:

题目解析:

  • 这道题其实跟上一题是差不多的,但是这个是记录下当前元素后面比它小的元素的个数,并存在一个数组的下标相同的位置。
  • 最后返回存个数的数组。

3.1 分治思想

解题思路:

  • 这道题跟上一道的解题思路是一模一样的,将数组分为两段。每个下标的数组的对应值等于:前一段个数+后一段个数。
  • 由于求小的个数,所以我们使用降序求法,使用升序需要考虑边界情况。
  • 当nums[ cur2 ] < nums[ cur1 ]时统计[ mid+1 , right ]个数,放入结果数组的nums[ cur1 ]对应下标位置即可。
  • 我们要将数组中的值与其下标对应,由于数组中元素可以重复,所以不能使用哈希表,我们再使用一个数组记录每个元素的下标,两个数组绑定,同时移动。
  • 下标数组的移动和原数组的移动是一模一样的,所以合并是也需要引入临时数组。

解题代码:

//时间复杂度:O(NlogN)//空间复杂度:O(n)//降序版本:classSolution{int[] tmpNums;int[] tmpIndex;int[] ret;int[] index;publicList<Integer>countSmaller(int[] nums){int n = nums.length; tmpIndex =newint[n]; tmpNums =newint[n]; ret =newint[n];//下标数组 index =newint[n];for(int i =0; i < n; i++) index[i]= i;mergeSort(nums,0, n-1);List<Integer> list =newArrayList<>();for(int x : ret) list.add(x);return list;}publicvoidmergeSort(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;int i = left;while(cur1 <= mid && cur2 <= right){if(nums[cur2]>= nums[cur1]){ 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(int j = left; j <= right; j++){ nums[j]= tmpNums[j]; index[j]= tmpIndex[j];}}}

3.2 暴力枚举

解题思路:

  • 两层for循环即可
  • 会超时

解题代码:

//时间复杂度:O(n^2)//空间复杂度:O(1)classSolution{publicList<Integer>countSmaller(int[] nums){List<Integer> ret =newArrayList<>();for(int i =0; i < nums.length; i++){int a =0;for(int j = i +1; j < nums.length; j++){if(nums[j]< nums[i]) a++;} ret.add(a);}return ret;}}

四、493.翻转对

题目链接:493.翻转对
题目描述:

题目解析:

  • 题目非常清晰,并不用解析。

4.1 分治思想

解题思路:

  • 我们依旧是将数组分为两段:那么数组的总翻转对和等于:两段子数组的翻转对和 + 前面一段数组中每个元素在后面一段元素组成的翻转对和(就是遍历前面一段数组,在后面一段数组中找比该元素一半小的元素个数)(简称为一左一右选翻转对)。
  • 依旧可以是分升序和降序来求:
    • 升序排序:立足cur2,我们就找前一个子数组中大的元素个数即可,即[cur1 , mid]。
    • 降序排序:立足cur1,我们就找后一个数组中小的元素个数即可,即[ mid+1 , right ]。
  • 但是由于我们的判断条件与合并是不一样的,所以单独写一个循环来计算。
  • 每个元素不会超出int范围,但是乘2就有可能会超出,所以判断条件写 nums[cur1] / 2.0 <= nums[cur2]

解题代码:

//时间复杂度:O(NlogN)//空间复杂度:O(n)//升序版本:classSolution{int[] tmp;publicintreversePairs(int[] nums){ tmp =newint[nums.length];returnmergeSort(nums,0,nums.length-1);}publicintmergeSort(int[] nums,int left,int right){if(left >= right)return0;int mid =(left + right)/2;int ret =0;//分:两段子数组的翻转对和 ret +=mergeSort(nums, left, mid); ret +=mergeSort(nums, mid+1, right);//统计一左一右逆序对个数int cur1 = left, cur2 = mid +1;int i = left;while(cur2 <= right){while(cur1 <= mid && nums[cur1]/2.0<= nums[cur2]) cur1++;if(cur1 > mid)break; ret +=(mid - cur1 +1); cur2++;}//治:合并两个有序数组 cur1 = left; cur2 = mid +1;while(cur1 <= mid && cur2 <= right){if(nums[cur1]< nums[cur2]) tmp[i++]= nums[cur1++];else tmp[i++]= nums[cur2++];}while(cur1 <= mid) tmp[i++]= nums[cur1++];while(cur2 <= right) tmp[i++]= nums[cur2++];//还原for(int j = left; j <= right; j++){ nums[j]= tmp[j];}return ret;}}//时间复杂度:O(NlogN)//空间复杂度:O(n)//降序版本:classSolution{int[] tmp;publicintreversePairs(int[] nums){ tmp =newint[nums.length];returnmergeSort(nums,0,nums.length-1);}publicintmergeSort(int[] nums,int left,int right){if(left >= right)return0;int mid =(left + right)/2;int ret =0;//分:两段子数组的翻转对和 ret +=mergeSort(nums, left, mid); ret +=mergeSort(nums, mid+1, right);//统计一左一右逆序对个数int cur1 = left, cur2 = mid +1;int i = left;while(cur1 <= mid){while(cur2 <= right && nums[cur1]/2.0<= nums[cur2]) cur2++;if(cur2 > right)break; ret +=(right - cur2 +1); cur1++;}//治:合并两个有序数组 cur1 = left; cur2 = mid +1;while(cur1 <= mid && cur2 <= right){if(nums[cur1]< nums[cur2]) tmp[i++]= nums[cur2++];else tmp[i++]= nums[cur1++];}while(cur1 <= mid) tmp[i++]= nums[cur1++];while(cur2 <= right) tmp[i++]= nums[cur2++];//还原for(int j = left; j <= right; j++){ nums[j]= tmp[j];}return ret;}}

4.2 暴力枚举

解题思路:

  • 直接两层for循环即可
  • 会超时

解题代码:

//时间复杂度:O(n^2)//空间复杂度:O(1)classSolution{publicintreversePairs(int[] nums){int ret =0;for(int i =0; i < nums.length; i++){for(int j = i+1; j < nums.length; j++){if(nums[i]/2.0> nums[j]) ret++;}}return ret;}}

Read more

鸿蒙UI框架演进史:从Java UI到ArkUI的架构变迁,解码声明式UI与跨端一致性的技术革命

鸿蒙UI框架演进史:从Java UI到ArkUI的架构变迁,解码声明式UI与跨端一致性的技术革命

鸿蒙UI框架演进史:从Java UI到ArkUI的架构变迁,解码声明式UI与跨端一致性的技术革命 第一章 :UI框架的十年之变 在移动操作系统的演进史上,UI框架的变迁始终是开发者体验与系统能力的晴雨表。从2012年EMUI 1.0诞生,到2025年HarmonyOS NEXT全面推广ArkUI,华为的UI框架走过了13年的技术迭代之路。 这期间,我们见证了从“命令式UI”到“声明式UI”的范式转移,经历了从“单设备适配”到“多端一致”的架构革命。对于开发者而言,理解这段演进史,不仅是回顾技术发展脉络,更是把握鸿蒙生态未来方向的关键。 本文将系统梳理鸿蒙UI框架的演进历程,深入剖析渲染引擎的优化技术,用量化数据证明声明式UI的性能优势,并解密跨端UI一致性的实现方案。全文约12000字,包含大量代码示例与实践建议。 第二章 鸿蒙UI框架演进史:从Java UI到ArkUI的架构变迁 2.1 EMUI时代:定制化UI的探索期(2012-2019) 要理解鸿蒙UI的今天,必须先回顾EMUI的昨天。2012年,华为发布了基于Android的EMUI 1.0,

By Ne0inhk
【Java 开发日记】有了解过 SpringBoot 的参数配置吗?

【Java 开发日记】有了解过 SpringBoot 的参数配置吗?

目录 核心概念:application.properties 与 application.yml 配置的加载位置与优先级 外部化配置(非常强大) 如何在代码中获取配置值? 常用配置示例 总结 当然了解,Spring Boot 的参数配置是其核心特性之一,也是它实现“约定大于配置”理念的关键。它极大地简化了传统 Spring 应用中繁琐的 XML 配置。 一、核心概念:application.properties 与 application.yml Spring Boot 默认使用这两种文件进行配置(二者选其一即可,.yml 更常用)。 application.properties (传统键值对格式) server.port=8081 spring.datasource.url=jdbc:mysql://localhost:

By Ne0inhk
Java之Volatile 关键字全方位解析:从底层原理到最佳实践

Java之Volatile 关键字全方位解析:从底层原理到最佳实践

文章目录 * 课程导言 * 适用对象 * 学习目标 * 第一部分:从并发三要素看volatile的定位 * 1.1 并发编程的三座大山 * 1.2 volatile的坐标:轻量级的同步利器 * 1.3 一个先导案例:感受volatile的魔力 * 第二部分:volatile与Java内存模型(JMM) * 2.1 为什么要JMM? * 2.2 JMM的核心结构:主内存 vs 工作内存 * 2.3 可见性问题的根源 * 2.4 volatile如何保证可见性? * 2.5 JMM对volatile的规范 * 第三部分:有序性与指令重排序 * 3.1 什么是指令重排序? * 3.2 重排序的潜在风险 * 3.3 volatile如何禁止重排序? * 3.

By Ne0inhk
【保姆级教程】无成本零门槛安装配置OpenClaw龙虾AI全能助手

【保姆级教程】无成本零门槛安装配置OpenClaw龙虾AI全能助手

哈喽大家好!最近爆火的 OpenClaw(龙虾AI)全能助手大家体验了吗?它不仅能帮你自动整理邮件、查询天气,还能全自动写小红书笔记并发布,简直是打工人和自媒体人的摸鱼神器! 很多小伙伴想玩但又怕配置太复杂、花销太大。今天给大家带来一篇零门槛、保姆级的安装配置教程!教你如何低成本获取云服务器,轻松实现 AI 大模型自由。全程图文指引,小白也能轻松搞定,赶紧跟着操作起来吧! 一、获取云服务器 想要畅玩 OpenClaw,首先我们需要一个服务器。这次教大家如何获取腾讯云轻量服务器来进行配置。 ⏰ 活动时间:2026年1月21日 - 3月31日 腾讯推出了登录 CodeBuddy 送 2C2G4M 轻量服务器的限时活动:登录先送1个月,活跃7天再送2个月。 👉 【官方地址】:https://www.codebuddy.cn/promotion/?ref=ie2rwhd1loq 根据页面提示安装好软件并登录账号后,直接选择一个月的轻量应用服务器即可。 之后只要累计活跃7天就能续费两个月(每天和 AI

By Ne0inhk