快排的深入学习

目录

交换类排序

一、冒泡排序

1. 算法介绍

2.算法流程

3. 算法性能分析

(1)时间复杂度分析

(2) 空间复杂度分析

冒泡排序的特性总结:

二、快速排序

1.算法介绍

2. 执行流程

1). hoare版本

2). 挖坑法

3). 前后指针版本

3.快速排序优化(小demo)

1). 三数取中法选key

2). 递归到小的子区间时,可以考虑使用插入排序

4.快速排序非递归

5.快速排序的特性总结:

6.快排的进一步深入学习

1).快排之三路划分

eg:

2).快排之自省排序

7.分治思想(三路划分)

数组分三块:


交换类排序

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排 序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

说起快排,那一定从冒泡排序切入,我们先来回顾一下冒泡排序。

一、冒泡排序

1. 算法介绍

       起泡排序又称冒泡排序。它是通过一系列的“交换”动作完成的。首先第一个关键字和第二个关键字比较,如果第一个大,则二者交换,否则不交换;然后第二个关键字和第三个关键字比较,如果第二个大,则二者交换,否则不交换······。一直按这种方式进行下去,最终最大的那个关键字被交换到了最后,一趟起泡排序完成。经过多趟这样的排序,最终使整个序列有序。在这个过程中,大的关键字像石头一样“沉底”,小的关键字像气泡一样逐渐向上“浮动”,冒泡排序的名字由此而来。

2.算法流程

原始序列:49  38  65  97  76  13  27  49

下面进行第一趟冒泡排序。

1)1号和2号进行比较,49 > 38,交换。

结果:38  49  65  97  76  13  27  49

2)2号和3号进行比较,49 < 65,不交换。

结果:38  49  65  97  76  13  27  49

3)3号和4号进行比较,65 < 97,不交换。

结果:38  49  65  97  76  13  27  49

4)4号和5号进行比较,97 > 76,交换。

结果:38  49  65  76  97  13  27  49

5)5号和6号进行比较,97 > 13,交换。

结果:38  49  65  76  13  97  27  49

6)6号和7号进行比较,97 > 27,交换。

结果:38  49  65  76  13  27  97  49

7)7号和8号进行比较,97 > 49 ,交换。

结果:38  49  65  76  13  27  49  97

      至此一趟起泡排序结束,最大的97被交换到了最后,97到达了它最后的位置。接下来对序列38  49  65  76  13  27  49 按照同样的方法进行第二趟起泡排序。经过若干趟起泡排序后,最终序列有序。要注意的是,冒泡排序算法结束的条件是在一趟排序过程中没有发生关键字交换。
冒泡排序算法代码如下:

#include <iostream>
#include <vector>
using namespace std;// 冒泡排序
void BubbleSort(vector<int>& v, int n)
{for (int i = 0; i < n - 1; i++){//j<n-1 就是每次排序都要排除最后已经被排好序的元素,从头开始排序for (int j = 0; j < n - 1 - i; j++){if (v[j] > v[j + 1]){//交换元素 大值往后换int temp = v[j];v[j] = v[j + 1];v[j + 1] = temp;}}}for (auto e : v) cout << e << " "; cout << endl;
}int main()
{vector<int> v = { 49,38,65,97,76,13,27,49 };BubbleSort(v, v.size());return 0;
}

eg:

3. 算法性能分析

(1)时间复杂度分析

由起泡排序算法代码可知,可选取量内层循环中的关键字交换操作作为基本操作。
1)最坏情况,待排序列逆序,此时对于外层循环的每次执行,内层循环中if语句的条件R[j]<R[j-1]始终成立,即基本操作执行的次数为 n - i 。 i 的取值为1 ~ n - 1。因此,基本操作总的执行次数为(n - 1 + 1)(n - 1) / 2=n(n - 1) / 2,由此可知时间复杂度 O(n^2)。

2)最好情况,待排序列有序,此时内层循环中if 语句的条件始终不成立,交换不发生,且内层循环执行n - 1次后整个算法结束,可见时间复杂度为O(n)。
综合以上两种情况,平均情况下的时间复杂度为O(n^2)。


(2) 空间复杂度分析

由算法代码可以看出,额外辅助空间只有一个temp,因此空向复杂度为O(1)。

冒泡排序的特性总结:

1. 冒泡排序是一种非常容易理解的排序

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1)

4. 稳定性:稳定

二、快速排序

1.算法介绍

       快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

       快速排序也是“交换”类的排序,它通过多次划分操作来实现排序。以升序为例,其执行流程可以概括为:每一趟选择当前所有子序列中的一个关键字(通常是第一个)作为枢轴,将子序列中比枢轴小的移到枢轴前面,比枢轴大的移到枢轴后面;当本趟所有子序列都被枢轴以上述规则划分完毕后会得到新的一组更短的子序列,它们成为下一趟划分的初始序列集。

       说人话就是,找数组第一个元素为参考,用left 和 right 指针分别从右至左 和 从左至右 进行搜索,满足小于参考元素 或 大于的就进行交换,最后 一定是较小元素与参考元素nums[0] 进行交换。后面讲解。

 // 假设按照升序对array数组中[left, right)区间中的元素进行排序
void QuickSort(int array[], int left, int right){if(right - left <= 1)return;// 按照基准值对array数组的 [left, right)区间中的元素进行划分
int div = partion(array, left, right);}// 划分成功后以div为边界形成了左右两部分 [left, div) 和 [div+1, right)// 递归排[left, div)QuickSort(array, left, div);// 递归排[div+1, right)QuickSort(array, div+1, right);


2. 执行流程

       上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,同学们在写递归框架时可想想二叉 树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。 将区间按照基准值划分为左右两半部分的常见方式有:

1). hoare版本

//hoare版本
void PartSort1(vector<int>& v, int left, int right)
{if (left >= right) return;int begin = left, end = right;int temp = v[left];while (begin < end){while (begin < end && v[end] >= temp) end--;while (begin < end && v[begin] <= temp) begin++;swap(v[end], v[begin]);}swap(v[left], v[begin]);PartSort1(v, left, begin - 1);PartSort1(v, begin + 1, right);
}int main()
{vector<int> v = { 49,38,65,97,76,13,27,49 };PartSort1(v, 0,v.size() - 1);for (auto e : v) cout << e << " "; cout << endl;return 0;
}

现在考虑为什么每次快排,以第一个元素为基准的时候,为什么都是right先走呢?

那是因为:

1.假如left 碰 right,右边先走,那么right停在了要被交换的小于基准的元素。这个时候left在开始向右寻找,结果在left < right 的条件下,left 碰了 right 这个时候 right下标的元素就是小于基准的,就也可以满足与基准进行交换,那么此时,就已经确定了基准元素的最终位置!

2.假如right 碰 left ,那么right先走,这个时候,left与right刚被交换完,此时还是right先走,这个时候right向左寻找,一直没找到满足小于基准的元素,这个时候碰到了left,可是left下标的元素就是刚跟right下标元素进行交换的元素,这个时候,left下标元素就是小于基准的 那么现在right 碰 left 就也是小于基准的元素,也满足 right 下标元素可以跟基准元素交换。

所以综上所述,可以看出,如果选最左边为基准,就right先走;如果选右边为基准,就left先走;

2). 挖坑法

挖坑法,也是称为最简单的快排思想,只需要有一个坑,找到一个满足大于或小于基准元素,将它填满就行。

将第一个元素作为基准元素,存放在temp中,让第一个元素的位置形成一个坑位。在后续的交换过程中,其实就是不停的填坑位。一个坑位被填满,另一个坑位就空出来了。

// 快速排序挖坑法
void PartSort2(vector<int>& v, int left, int right)
{if (left >= right) return;int begin = left, end = right;int temp = v[left];while (begin < end){while (begin < end && v[end] >= temp) end--;if (begin < end){v[begin++] = v[end];}while (begin < end && v[begin] <= temp) begin++;if (begin < end){v[end--] = v[begin];}}PartSort2(v, left, begin - 1);PartSort2(v, begin + 1, right);
}int main()
{vector<int> v = { 49,38,65,97,76,13,27,49 };PartSort2(v, 0,v.size() - 1);for (auto e : v) cout << e << " "; cout << endl;return 0;
}

3). 前后指针版本

     初始时,prev指向数组首元素,cur指向prev下一个元素

前后指针法写起来比较简洁,只要考虑 prev 和 cur 两个指针对应的元素的大小进行交换即可。具体思想其实跟 hoare 大差不差。

实现思想:

仍然是用temp将第一个元素作为基准存起来,在满足cur <= right 当cur还没走到最右边时,就进行元素比较和交换。只要cur下标对应的元素小于temp && 在++prev != cur 时 防止自己跟自己交换,那么就将两者元素进行交换,这种双指针法思想也挺简单的。就是要注意,在交换前满足++prev

最后循环完了就交换prev 与 left 对应下标的元素,将left下边元素的值确定到最终位置。

// 快速排序前后指针法
void PartSort3(vector<int>& v, int left, int right)
{if (left >= right) return;int prev = left;int cur = prev + 1;int temp = v[left];while (cur <= right){while (v[cur] < temp && ++prev != cur)swap(v[cur], v[prev]);++cur;}swap(v[prev], v[left]);PartSort3(v, left, prev - 1);PartSort3(v, prev + 1, right);
}int main()
{vector<int> v = { 49,38,65,97,76,13,27,49 };PartSort3(v, 0,v.size() - 1);for (auto e : v) cout << e << " "; cout << endl;return 0;
}

3.快速排序优化(小demo)

1). 三数取中法选key

每次选key 选择的都是最左边的left对应下标的元素,有点过于随机,可能会发生本身数组就是有序的,我还选left下标对应的元素,那么快排就可能会退化到O(N^2),为了防止这种随机的事件来拖慢快排的效率,那么我们就可以在选key时就保证不是选择的最大或最小,防止快排退化的过于严重。

那么就采取三数取中策略,取left  mid= (left + right) / 2  right 三个下标对应的值来选取,既不选择最大的,也不选择最小的,那么第三个数就一定是中间值,不会存在极端情况。

//找中间值 三数取中
int GetMidi(vector<int>& v, int left, int right)
{int midi = (left + right) / 2;//left midi rightif (v[left] < v[midi]){if (v[midi] < v[right]) return midi;else if (v[left] < v[right]) return right;//走了else 就说明v[midi]>v[right]else return left;//剩下就是v[left] 就是中间值}else{if (v[midi] > v[right]) return midi;else if (v[right] > v[left]) return left;else return right;}
}

三数取中,这样肯定能保证性能得到优化。

2). 递归到小的子区间时,可以考虑使用插入排序

递归到小的子区间优化,考虑插入排序,因为在一般O(N^2) 的较弱的排序中,插入排序对于小数组是非常nice的,吊打冒泡和交换排序。

如果快排 排序的数很多,就有可能会不断的递归造成栈溢出,为了减小栈溢出的概率,我们可以减少快排的递归的次数,在最后几次递归的过程中,对于小数组的递归,消耗较大,没有必要,这个时候就可以采用 递归到小的子区间时,可以考虑使用插入排序,这时就可以完全满足减少递归消耗,还能保证效率。

此时,就可以将小区间优化跟三数取中结合在一起:

void QuickSort(vector<int>& v, int left, int right)
{if (left >= right) return;//小区间优化,不在递归分割排序,减少递归次数if ((right - left + 1) < 10){//进行插入排序InSertSort(v + left, right - left + 1);}else{//三数取中int midi = GetMidi(v, left, right);//将midi下标对应的元素 交换到left处std::swap(v[left], v[midi]);}
}

4.快速排序非递归

在部分面试笔试的过程中,可能会存在对于递归快排非常熟悉,要求手撕非递归快排,那么就要考虑用 栈stack 来模拟实现递归的过程,就可以达到同样的效果。

void QuickSortNonR(int* a, int left, int right){Stack st;StackInit(&st);StackPush(&st, left);StackPush(&st, right);while (StackEmpty(&st) != 0){right = StackTop(&st);StackPop(&st);left = StackTop(&st);StackPop(&st);if(right - left <= 1)continue;int div = PartSort1(a, left, right);// 以基准值为分割点,形成左右两部分:[left, div) 和 [div+1, right)StackPush(&st, div+1);StackPush(&st, right);StackPush(&st, left);StackPush(&st, div);}StackDestroy(&s);}

5.快速排序的特性总结:

1). 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

2). 时间复杂度:O(N*logN)

3). 空间复杂度:O(logN)

4). 稳定性:不稳定

6.快排的进一步深入学习

1).快排之三路划分

(专治有大量重复的情况)

a.key默认取left位置的值

b.left指向区间最左边,right指向区间最后边,cur指向left+1位置

c.cur遇到比key小的值后跟left位置交换,换到左边,left++,cur++

d.cur遇到比key大的值后跟right位置交换,换到右边,right--

e.cur遇到跟key相等的值后,cur++

f.直到cur > right 结束

eg:

将left下标对应的元素存入key中,key=6;

1.v[cur] < key 跟v[left++]交换

2.此时v[cur] > key ,那么就跟v[right--] 无脑交换 ,不用在乎v[right] 是否大于v[cur]

3.此时继续判断v[cur] > key ,那么继续执行刚才的逻辑,继续无脑跟 v[right--] 进行交换

4.因为cur没动,继续判断v[cur] < key 那么此时swap(v[cur++], v[left] );进行交换,让left,和cur都向前走

5.此时当遇到 v[cur]==key 时,就让cur一直走,直到 cur > right 时 就停止运动,然后这个时候就进行左右递归

伪代码用例,可以加入QuickSort 专门解决出现大量重复数据的快排问题,速度很快。

//快排之三路划分
void Count_out_three(vector<int>& v, int left, int right)
{int begin = left, end = right;int key = v[left];int cur = left + 1;while (cur <= right){if (v[cur] < key) swap(v[cur++], v[left++]);else if (v[cur] > key) swap(v[cur], v[right--]);else cur++; //v[cur] == key }//[begin,left-1] [left,right] [right+1,end]Count_out_three(v, begin, left - 1);Count_out_three(v, right + 1, end);
}

2).快排之自省排序

在前面的优化中,都是针对特定的情况下,可以时快排的性能得到较大的优化,但是在一些极端情况下,可能优化就不会那么明显,包括三数取中,三路划分等问题。

但是在工业中,面对C++,STL里面sort() 快排就是用的introsort 自省排序。当递归深度太深,如果继续使用当前的排序,那么性能可能大打折扣,那么此时可以改为堆排序,堆排序在给个情况下都是十分稳定的O(NlogN);

7.分治思想(三路划分)

讲到这里,其实可以看出,快排就是一种分治的思想,先排好一个元素,在排排左边的元素和右边的元素,最后得到有序数组。最核心的一步就是数据划分的步骤。朴素版本就是数据划分两部分。

但是但是,如果有很多重复元素的话,那么我们时间复杂度就退化成O(N^2)了,那么这个时候,我们就在可能存在重复元素的基础上多划分一步,变成数据划三份来进行排序。

[<key] [==key] [>key]

利用数据划分三块进行排序数组,来进行分类讨论:

数组分三块:

1.nums[i] < key  swap(nums[++left],nums[i++])

2.nums[i] == key i++;

3.nums[i] > key swap(nums[--right],nums[i]) //这里i不++,因为此时i还要继续判断

优化:随机选key (逼近NlogN)

int r=rand()

return nums[r%(right-left+1)+left];就一定返回 [0,n-1] 这个区间的值

            

//数组分三块class Solution {
public:vector<int> sortArray(vector<int>& nums) {srand(time(NULL)); //种下一颗随机数的种子qsort(nums,0,nums.size()-1);return nums;}void qsort(vector<int>& nums,int l,int r){if(l>=r) return;int key=getrand(nums,l,r); //得到这个随机数返回的nums的值int i=l,left=l-1,right=r+1;while(i<right){if(nums[i]==key) i++;else if(nums[i]<key) swap(nums[++left],nums[i++]);else swap(nums[--right],nums[i]);}//[l,left] [left+1,right-1] [right,r]qsort(nums,l,left);qsort(nums,right,r);}int getrand(vector<int>& nums,int left,int right){int r=rand();//得到这个随机数return nums[r%(right-left+1)+left];  //控制在区间[0,n-1]之间取值}
};

这个版本真的很重要,一定一定要写会!!!

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

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

相关文章

阿里达摩院:FunASR语音识别

阿里达摩院:FunASR语音识别 github&#xff1a; https://github.com/modelscope/FunASR/ 1 clone 代码到本地&#xff0c;切换到 FunASR/ git clone https://github.com/alibaba/FunASR.git && cd FunASR2 虚拟环境 conda create -p ./venv python3.12 conda activ…

python---爬取QQ音乐

如Cookie为非vip&#xff0c;仅能获取非vip歌曲 1.下载包 pip install jsonpath 2.代码 import os import time import requests from jsonpath import jsonpathdef search_and_download_qq_music(query_text):headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; …

使用 scikit-learn 实战感知机算法

一 引言 感知机&#xff08;Perceptron&#xff09;是最早的人工神经网络模型之一&#xff0c;由 Frank Rosenblatt 在 1957 年提出。虽然它相对简单&#xff0c;但在处理线性可分问题时却非常有效。本文将介绍如何使用 Python 的 scikit-learn 库来实现感知机&#xff0c;并通…

uniapp使用defineExpose暴露和onMounted访问

defineExpose作用 暴露方法和数据 允许从模板或其他组件访问当前组件内部的方法和数据。明确指定哪些方法和数据可以被外部访问&#xff0c;从而避免不必要的暴露。 增强安全性 通过显式声明哪些方法和数据可以被外部访问&#xff0c;防止意外修改内部状态。提高组件的安全性&a…

828华为云征文|华为云Flexus X实例docker部署srs6并调优,协议使用webrtc与rtmp

828华为云征文&#xff5c;华为云Flexus X实例docker部署srs6并调优&#xff0c;协议使用webrtc与rtmp 华为云最近正在举办828 B2B企业节&#xff0c;Flexus X实例的促销力度非常大&#xff0c;特别适合那些对算力性能有高要求的小伙伴。如果你有自建MySQL、Redis、Nginx等服务…

React 更新界面

文章目录 发现宝藏引入 useState声明和使用状态多个组件的状态管理解析代码 状态的局部性和性能优化结论 发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【宝藏入口】。 在 React 中&#xff…

docker实战基础三(Docker基础命令)

Docker 实战案例:构建镜像、查看容器运行信息、查看镜像构建信息 在这个实战案例中,我们将详细介绍如何构建Docker镜像、查看容器运行信息以及查看镜像构建信息。这些知识点非常实用,可以帮助你在实际工作中更好地利用Docker进行开发和运维。 一、构建Docker镜像 1. 创建…

Android 下的 XML 文件(概念理解、存储位置理解)

一、XML 1、XML 概述 XML&#xff08;Extensible Markup Language&#xff0c;可扩展标记语言&#xff09;是一种用于存储和传输数据的标记语言 类似于 HTML&#xff0c;但旨在传输和存储数据&#xff0c;而不是显示数据&#xff0c;且基本语法都是标签 2、XML 的特点 &am…

协程源码剖析(三) 调度器设计实现

先来看协程调度器的结构体中处理调度的部分 typedef struct _nty_schedule {...nty_coroutine_queue ready; // 就绪队列 &#xff08;优先级最高&#xff09;nty_coroutine_queue defer; // &#xff08;暂时没用到&#xff09;nty_coroutine_link busy; // 忙碌链表&#xf…

【数据结构与算法 | 搜索二叉树篇 力扣篇】力扣530, 501

1. 力扣530&#xff1a;二叉搜索树的最小绝对差 1.1 题目&#xff1a; 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 示例 1&#xff1a; 输入&#xff1a;root [4,…

C# 调用百度API批量识别发票,并存到EXCEL

C# 调用百度API批量识别发票&#xff0c;并存到EXCEL ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/f406f2185184456daa9ba7829c26138d.png#pic_center)using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Thr…

BUUCTF—[BJDCTF2020]The mystery of ip

题解 打开环境点击上面的flag可以看到这个IP页面。 抓个包看看有啥东西无&#xff0c;可以看到在返回包有IP。 看到IP就想到X-Forwarded-For这个玩意&#xff0c;我们用X-Forwarded-For随便添加个IP看看。可以看到返回的IP内容变成了123。 X-Forwarded-For:123 推测它会输出我…

Java项目怎么从零部署到Linux服务器上?

目录 一.Java环境&#xff08;JDK&#xff09;安装 二.数据库&#xff08;MySQL&#xff09;安装 三.部署上线 ▐ 部署Jar包 ▐ 运行程序 ▐ 开放端口 一个Java项目首先需要一个支持它编译的Java环境&#xff0c;因此首先要保证服务器上安装的有相应的JDK 一.Java环境&a…

JWT与shiro结合实现认证

随着微服务架构的普及和前后端分离的趋势&#xff0c;越来越多的项目开始采用无状态的认证机制&#xff0c;其中JSON Web Token&#xff08;JWT&#xff09;成为了一种流行的选择。与此同时&#xff0c;Apache Shiro作为一个强大且易于使用的Java安全框架&#xff0c;仍然被广泛…

Windows电脑获取目录及子目录结构及包含文件名的命令

在Windows 11中&#xff0c;你可以使用多种方式来获取当前目录及其子目录的结构&#xff0c;包括文件名。最直接且广泛使用的方法是使用命令行工具&#xff0c;如cmd&#xff08;命令提示符&#xff09;或PowerShell。下面是两种不同方式来实现这一需求的示例。 使用cmd&#…

负载均衡 Ribbon 与 Fegin 远程调用原理

文章目录 一、什么是负载均衡二、Ribbon 负载均衡2.1 Ribbon 使用2.2 Ribbon 实现原理 (★)2.3 Ribbon 负载均衡算法 三、Feign 远程调用3.1 Feign 简述3.2 Feign 的集成3.3 Feign 实现原理 (★) 一、什么是负载均衡 《服务治理&#xff1a;Nacos 注册中心》 末尾提到了负载均…

Vue2和Vue3项目创建的区别和 element ui 和element plus的导入方式

文章目录 创建项目Vue2Vue3 Vue2项目机构Vue 2 Element UI VUE3项目结构Vue 3 Element Plus 创建项目 Vue2 Vue CLI 3 及之前版本&#xff1a; 使用 vue-cli 创建项目&#xff1a; npm install -g vue/cli vue create my-project-vue2 cd my-project-vue2Vue3 Vue CLI 4…

第二十篇——行军篇:别指望外援,好好培养亲兵

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 微观层面&#xff0c;从驯服的视角&#xff0c;我们可以洞察到人性中非常…

一篇文章深入了解Oracle常用命令

1. 数据库 1.1. 数据库启动 & 关闭 –启动数据库 SQL> startup nomount; SQL> alter database mount; SQL> alter database open;–关闭数据库 SQL> shutdown immediate&#xff1b;1.2. 连接数据库 –登录普通用户 SQL>sqlplus 用户名/密码实例名–…

2024.9.4

继续该题&#xff0c;除了实在改不来的&#xff0c;基本快改完了 #2316. 飓风&#xff08;hurricane&#xff09; #1575. 【EOJ Long Round】本质不同GCD 被hack了重新写一下&#xff0c;乱搞复杂度大了点 #2303. 最小子列&#xff08;subseq&#xff09; 先从没有限制考虑起&a…