Java分治算法题目练习(快速/归并排序)

Java分治算法题目练习(快速/归并排序)

分治算法

使用快速排序和归并排序进行解决问题,因为这两个都是采用归并的思想

颜色分类

在这里插入图片描述
题目解析:将其数组中0放在左边,1放在中间,2放在右边
在双指针算法中有一个移动零的题目,就是将所有0元素移动到右边,但是非0元素相对位置不改变
那题使用双指针将其数组分为三部分,因此这题也可以将其数组分块
left表示为0区域最右侧,i遍历数组,right表示2区域最左侧
使用这三个指针将这个数组分为了4部分
在这里插入图片描述
在这里插入图片描述


在这里插入图片描述
classSolution{publicvoidsortColors(int[] nums){//可以将其数组分为三部分//[0,left]:全是0//[left+1,i-1]全都是1//[i,right-1]待扫描//[right,n-1]全是2int left =-1;int i =0;int right = nums.length;while(i < right){if(nums[i]==0){swap(nums,++left,i++);}elseif(nums[i]==1){ i++;}else{swap(nums,--right,i);//此时会将right-1元素放到i下标,但是这个元素没有扫描过,所以这里不可以让i++}}}publicvoidswap(int[] nums,int i ,int j){int tem = nums[i]; nums[i]= nums[j]; nums[j]= tem;}}
时间复杂度:O(n)
空间复杂度:O(1)

排序数组(快排)

在这里插入图片描述
题目解析:给一个数组让我们排序
分治:使用快速排序,找一个key基准值,根据基准值,让其数组变成三个模块,左边模块<key,中间模块=key,右边模块>key,这时候在对左右两边的模块,分别进行找基准值进行排序,左右两边每一个模块又可以分为这三个部分,就这样不断排序最终结果就变成有序的了
key基准值如何取
这里采用随机取,随机其对应区间下标 r = new Random().nextInt( right - left + 1) + left,这样整个下标区间的取值就是[left,right]了
在这里插入图片描述


在这里插入图片描述
classSolution{publicint[]sortArray(int[] nums){qsort(nums,0,nums.length-1);return nums;}publicvoidqsort(int[] nums,int l,int r){if(l >= r){return;}//这里采用随机获取key//随机的下标是[l,r];int key = nums[newRandom().nextInt(r - l +1)+ l];//这里根据key将其数组分三块进行排序int left = l -1;int right = r +1;int i = l;while(i < right){if(nums[i]< key){swap(nums,++left,i++);}elseif(nums[i]== key){ i++;}else{swap(nums,--right,i);}}qsort(nums,l,left);qsort(nums,right,r);}publicvoidswap(int[] nums,int i,int j){int tem = nums[i]; nums[i]= nums[j]; nums[j]= tem;}}

数组中第K个最大元素

在这里插入图片描述
题目解析:给了一个数组,找出其中第K个大的元素
快速排序,根据上一题的快速排序,这里我们仍然使用快排,但是这里不一样的是,快速排序以后将数组分为三个模块,这里我们可以判断其第K个大的元素在那个模块,此时我们根据其每一个模块元素个数进行判断,在一个模块中,只需要在这一个模块中找即可剩下的两个模块就不用管了
在这里插入图片描述
classSolution{publicintfindKthLargest(int[] nums,int k){returnqsort(nums,0,nums.length-1,k);}publicintqsort(int[] nums,int l,int r,int k){if(l == r){return nums[l];}int key = nums[newRandom().nextInt(r - l +1)+l];int left = l -1;int right = r +1;int i = l;while(i < right){if(nums[i]< key){swap(nums,++left,i++);}elseif(nums[i]== key){ i++;}else{swap(nums,--right,i);}}//这里将其分为了[l,left] [left+1,right-1] [right,r]//判断其第k大的元素在那个模块int b = right - left -1;int c = r - right +1;if(c >= k){//只在右边找即可returnqsort(nums,right,r,k);}elseif((b+c)>=k){return key;}else{returnqsort(nums,l,left,k-b-c);}}publicvoidswap(int[] nums,int i,int j){int tem = nums[i]; nums[i]= nums[j]; nums[j]= tem;}}

最小的K个数

在这里插入图片描述
题目解析:返回数组中最小的k个数
快速选择算法 + 随机获取key值
这题和上一题找出第k个最大的数类似,唯一不同的就是排序后三个模块如果处理,因为这里最小的k个数顺序是随机的,也是每次只需要调整一个模块即可,剩下的两个模块不用管了
在这里插入图片描述
classSolution{publicint[]smallestK(int[] arr,int k){//快速选择算法qsort(arr,0,arr.length-1,k);int[] ret =newint[k];for(int i =0;i < k;i++){ ret[i]= arr[i];}return ret;}publicvoidqsort(int[] arr,int l ,int r ,int k){if(l >= r){return;}int key = arr[newRandom().nextInt(r - l +1)+ l];int left = l -1;int right = r +1;int i = l;while(i < right){if(arr[i]< key){swap(arr,++left,i++);}elseif(arr[i]== key){ i++;}else{swap(arr,--right,i);}}//[l,left] [left + 1, right - 1],[right , r]int a = left - l +1;int b = right - left -1;if(a > k){qsort(arr,l,left,k);}elseif(a+b >= k){return;}else{qsort(arr,right,r,k-a-b);}}publicvoidswap(int[] arr,int i,int j){int tem = arr[i]; arr[i]= arr[j]; arr[j]= tem;}}
时间复杂度:O(n),因为随机存取key,所以渐进O(n)
空间复杂度:O(k)

排序数组(归并)

在这里插入图片描述
题目解析:排序
这里仍然是归并的思想,根据mid将其数组分为左右两部分,左右两部分分别进行排序,左边部分又可以分为左右两部分,右边同理,当左右两边分完以后,就可以进行合并,左边先合并,右边再合并,最后合并到一起,最终整个数组再进行一次排序即可
在这里插入图片描述


归并排序详细介绍

在这里插入图片描述
classSolution{int[] tem;publicint[]sortArray(int[] nums){ tem =newint[nums.length];//归并排序mergeSort(nums,0,nums.length-1);return nums;}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[] tem = new int[right - left + 1];//合并两个有序数组int cur1 = left;int cur2 = mid+1;int i =0;while(cur1 <= mid && cur2 <= right){ tem[i++]= nums[cur1]>= nums[cur2]? nums[cur2++]: nums[cur1++];}//处理还没有遍历完的数组while(cur1 <= mid){ tem[i++]= nums[cur1++];}while(cur2 <= right){ tem[i++]= nums[cur2++];}//将排好序的数组放回去for(int j = left;j <= right;j++){ nums[j]= tem[j - left];}}}

交易逆序对的总数

在这里插入图片描述
题目解析:相对顺序不变,找出ai > aj , i < j,这样逆序对的总个数
算法原理:仍然采用分治的思想,采用
在这里插入图片描述


在这里插入图片描述


在这里插入图片描述
//升序classSolution{int[] tem;publicintreversePairs(int[]record){ tem =newint[record.length];//归并排序returnmergeSort(record,0,record.length-1);}publicintmergeSort(int[]record,int left,int right){if(left >= right){return0;}//根据中间点进行划分int mid =(left + right)/2;//左右两边统计分别进行排序int ret =mergeSort(record, left, mid)+mergeSort(record, mid +1, right);//int[] tem = new int[right - left + 1];//一左一右的个数+排序//合并两个有序数组int cur1 = left;int cur2 = mid +1;int i =0;while(cur1 <= mid && cur2 <= right){if(record[cur1]<=record[cur2]){//cur1向后走,此时没有 tem[i++]=record[cur1++];}else{ ret += mid - cur1 +1; tem[i++]=record[cur2++];}}//处理还没有遍历完的数组while(cur1 <= mid){ tem[i++]=record[cur1++];}while(cur2 <= right){ tem[i++]=record[cur2++];}//将排好序的数组放回去for(int j = left; j <= right; j++){record[j]= tem[j - left];}return ret;}}
//降序classSolution{//降序就是找后面又几个数比我小int[] tem;publicintreversePairs(int[]record){ tem =newint[record.length];//归并排序returnmergeSort(record,0,record.length-1);}publicintmergeSort(int[]record,int left,int right){if(left >= right){return0;}//根据中间点进行划分int mid =(left + right)/2;//左右两边统计分别进行排序int ret =mergeSort(record, left, mid)+mergeSort(record, mid +1, right);//int[] tem = new int[right - left + 1];//一左一右的个数+排序//合并两个有序数组int cur1 = left;int cur2 = mid +1;int i =0;while(cur1 <= mid && cur2 <= right){if(record[cur1]<=record[cur2]){//cur1向后走,此时没有 tem[i++]=record[cur2++];}else{ ret += right - cur2 +1; tem[i++]=record[cur1++];}}//处理还没有遍历完的数组while(cur1 <= mid){ tem[i++]=record[cur1++];}while(cur2 <= right){ tem[i++]=record[cur2++];}//将排好序的数组放回去for(int j = left; j <= right; j++){record[j]= tem[j - left];}return ret;}}

翻转对

在这里插入图片描述
题目解析:找出前面一个元素大于后面元素2倍的总个数
归并:此题目和上题一样,只不过这里变成了2倍,上面我们将更新结果和合并数组放到一起,这里我们因为条件不同,所以要将其分分开写即可
在这里插入图片描述
这里2倍可能会溢出,因此直接让前一个数 / 2.0进行比较即可
//降序classSolution{int[] tem;publicintreversePairs(int[] nums){int n = nums.length; tem =newint[n];returnmergeSort(nums,0,n-1);}publicintmergeSort(int[] nums,int left,int right){if(left >= right){return0;}int ret =0;int mid =(left + right)/2; ret +=mergeSort(nums,left,mid); ret +=mergeSort(nums,mid+1,right);//先计算翻转对的个数int cur1 = left;int cur2 = mid+1;//降序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;int i = left;while(cur1 <= mid && cur2<=right){ tem[i++]= nums[cur1]>= nums[cur2]? nums[cur1++]: nums[cur2++];}//处理剩余while(cur1 <= mid){ tem[i++]= nums[cur1++];}while(cur2 <= right){ tem[i++]= nums[cur2++];}for(int j = left;j <= right;j++){ nums[j]= tem[j];}return ret;}}
//升序classSolution{int[] tem;publicintreversePairs(int[] nums){int n = nums.length; tem =newint[n];returnmergeSort(nums,0,n-1);}publicintmergeSort(int[] nums,int left,int right){if(left >= right){return0;}int ret =0;int mid =(left + right)/2; ret +=mergeSort(nums,left,mid); ret +=mergeSort(nums,mid+1,right);//先计算翻转对的个数int cur1 = left;int cur2 = mid+1;//降序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;int i = left;while(cur1 <= mid && cur2<=right){ tem[i++]= nums[cur1]>= nums[cur2]? nums[cur2++]: nums[cur1++];}//处理剩余while(cur1 <= mid){ tem[i++]= nums[cur1++];}while(cur2 <= right){ tem[i++]= nums[cur2++];}for(int j = left;j <= right;j++){ nums[j]= tem[j];}return ret;}}

计算右侧小于当前元素的个数

在这里插入图片描述
题目解析:返回一个新数组,对应下标的值是后面元素比当前下标元素小的个数
归并:这一题和上面一题非常类似,但是唯一问题,就是我们不断排序,不断更新结果,然而我们找不到其原本下标进行更新结果,因此这里我们就需要使用一个index数组进行存放原本下标,因此也需要一个临时数组存放临时下标
在这里插入图片描述
classSolution{int[] ret;//返回结果int[] index;//对应下标int[] temIndex;int[] temNums;publicList<Integer>countSmaller(int[] nums){int n = nums.length; ret =newint[n]; index =newint[n]; temIndex =newint[n]; temNums =newint[n];//初始化对应下标for(int i =0; i < n; i++){ index[i]= i;}mergeSort(nums,0, n -1);List<Integer> l =newArrayList<>();for(int x : ret){ l.add(x);}return l;}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;int cur2 = mid +1;int i =0;//进行排序while(cur1 <= mid && cur2 <= right){//降序if(nums[cur1]<= nums[cur2]){ temNums[i]= nums[cur2];//这里要注意对应下标移动 temIndex[i++]= index[cur2++];}else{//更新结果,此时找到cur1对应的下标进行赋值 ret[index[cur1]]+= right - cur2 +1; temNums[i]= nums[cur1]; temIndex[i++]= index[cur1++];//更新下标}}//剩余进行排序while(cur1 <= mid){ temNums[i]= nums[cur1]; temIndex[i++]= index[cur1++];}while(cur2 <= right){ temNums[i]= nums[cur2]; temIndex[i++]= index[cur2++];}//合并for(int j = left; j <= right; j++){ nums[j]= temNums[j - left];//下标也要更新 index[j]= temIndex[j - left];}}}

Read more

Vibe Coding范式实战:用AI工具链(Stitch+Figma+ai studio+Trae)快速开发全栈APP

Vibe Coding范式实战:用AI工具链(Stitch+Figma+ai studio+Trae)快速开发全栈APP

文章目录 * 概要 * stitch制作设计稿 * figma 原型展示 * ai studio 生成前端代码 * 基于trae + Supabase生成后端代码和数据库 * Github + vercel * pc端后台管理系统设计 概要 在 AI 技术深度渗透软件开发领域的当下,一种名为 “Vibe Coding”(氛围编程)的全新范式正在重塑开发者的工作方式。它的核心在于,开发者不再是逐行编写代码的 “码农”,而是通过自然语言描述意图、引导 AI 生成代码的 “创意引导者” 和 “结果验证者”,从而将精力聚焦于更高价值的产品设计和逻辑思考上。 本文提供一种 Vibe Coding 的工作模式:设计阶段以 Google Stitch 为起点,开发者通过文本或草图快速生成响应式 UI 设计与前端代码,再无缝导入 Figma 进行精细化视觉调整和原型设计,实现了从 “想法” 到

By Ne0inhk
一句话生成PCB?和AI聊聊天,就把板子画了!

一句话生成PCB?和AI聊聊天,就把板子画了!

在键盘上敲下一句“我要一个STM32的电机驱动板,带CAN总线”,几秒后,一张完整的原理图和PCB布局在你眼前展开——这不是科幻电影,而是AI给硬件工程师带来的真实震撼。 清晨的阳光洒进办公室,资深硬件工程师李工没有像往常一样直接打开Altium Designer。他对着电脑屏幕上的对话框,敲入了一行简单的需求描述:“设计一个基于ESP32的智能插座PCB,要求支持Wi-Fi控制、过载保护,尺寸尽量小巧。” 15分钟后,一份完整的原理图草案、经过初步优化的双层板布局,甚至是一份物料清单(BOM)初稿已经呈现在他面前。这不可思议的效率背后,正是AI驱动的PCB设计工具在重新定义电子设计的边界。 01 效率革命,从对话到电路板 如今的PCB设计领域正经历着一场静悄悄的革命。传统上,一块电路板从概念到图纸,需要工程师经历需求分析、器件选型、原理图绘制、布局布线等一系列复杂工序,耗时数天甚至数周。 AI工具的出现彻底改变了这一流程。这类工具的核心是经过海量电路数据和设计规则训练的大型语言模型,它们能理解自然语言描述的需求,自动完成从逻辑设计到物理实现的全流程或关键环节。 比如,当

By Ne0inhk
人工智能:循环神经网络(RNN)与序列数据处理实战

人工智能:循环神经网络(RNN)与序列数据处理实战

循环神经网络(RNN)与序列数据处理实战 1.1 本章学习目标与重点 💡 学习目标:掌握循环神经网络的核心原理、经典变体结构,以及在文本序列任务中的实战开发流程。 💡 学习重点:理解 RNN 的循环计算机制,学会使用 TensorFlow/Keras 搭建基础 RNN 与 LSTM 模型,完成文本分类任务。 1.2 循环神经网络核心原理 1.2.1 为什么需要 RNN 💡 传统的前馈神经网络(如 CNN、全连接网络)的输入和输出是相互独立的。它们无法处理序列数据的上下文关联特性。 序列数据在现实中十分常见,比如自然语言文本、语音信号、时间序列数据等。这些数据的核心特点是,当前时刻的信息和之前时刻的信息紧密相关。 循环神经网络通过引入隐藏状态,可以存储历史信息,从而有效捕捉序列数据的上下文依赖关系。 1.2.2 RNN

By Ne0inhk
人工智能:自然语言处理高级应用与前沿发展

人工智能:自然语言处理高级应用与前沿发展

人工智能:自然语言处理高级应用与前沿发展 学习目标 💡 理解自然语言处理(NLP)的前沿技术和发展趋势 💡 掌握高级NLP应用(如文本生成、情感分析、机器翻译) 💡 学会使用前沿NLP模型(如GPT-3、BERT、T5) 💡 理解NLP在多模态融合、零样本学习、少样本学习中的应用 💡 通过实战项目,开发一个高级文本生成应用 重点内容 * NLP前沿技术和发展趋势 * 高级NLP应用(文本生成、情感分析、机器翻译) * 前沿NLP模型(GPT-3、BERT、T5) * 多模态融合、零样本学习、少样本学习 * 实战项目:高级文本生成应用开发 一、NLP前沿技术和发展趋势 1.1 多模态融合 1.1.1 多模态融合的基本概念 多模态融合是将不同模态的数据(如文本、图像、音频)结合起来,进行处理和分析的过程。它可以提高模型的性能和准确性。

By Ne0inhk