当前位置: 首页 > news >正文

优选算法2

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 7. 分治-快排
    • 7.1 颜⾊分类
    • 7.2 快速排序
    • 7.3 快速选择算法
    • 7.4 最⼩的 k 个数
  • 8. 分治-归并
    • 8.1 归并排序
    • 8.2 数组中的逆序对
    • 8.3 计算右侧⼩于当前元素的个数
    • 8.4 翻转对
  • 9. 链表
    • 9.1 链表常用技巧和操作总结
    • 9.2 两数相加
    • 9.3 两两交换链表中的节点
  • 10. 哈希表
    • 10.1 简介
    • 10.2 两数之和
    • 10.3 判断是否互为字符重排
  • 总结


前言

7. 分治-快排

就是把一个大问题划分为子问题,在把子问题划分为更小的子问题

7.1 颜⾊分类

颜⾊分类

数组分三块

以前的移动零,划分为两个区域,所以就是双指针

这个就相当于把小于1的放左边,大于1的放右边,等于1的放中间
划分三个区域,可以搞一个三指针

i来遍历,left指向0的最右侧,指向的就是0,right指向2的最左侧,指向的就是2

0~left:全是0
left+1~i-1:全是1
i~right-1:未扫描
right~n-1:全是2

在这里插入图片描述

nums[i]
为0时:交换left+1和i,i++,left++
为1时,i++
为2时:交换right-1,与i,i不变,right–
在这里插入图片描述
在这里插入图片描述
初始化:left=-1,right=n

class Solution {void swap(int[] nums, int i, int j) {int tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;}public void sortColors(int[] nums) {int left = -1 , right = nums.length , i =0;while(i<right){if(nums[i]==1){i++;}else if(nums[i]==0){swap(nums,i,left+1);left++;i++;}else if(nums[i]==2){swap(nums,i,right-1);right--;}}}
}

7.2 快速排序

快速排序

在这里插入图片描述
在这里插入图片描述
但是如果数据都一样的话,那么就是n^2的复杂度

我们可以分为三个区域
一个小于,一个等于key,一个大于key

在这里插入图片描述
优化选择基准元素:随机选择基准元素:随机数
在这里插入图片描述
坐标随机生成就可以了

class Solution {public int[] sortArray(int[] nums) {qsort(nums,0,nums.length-1);return nums;}public void qsort(int[] nums,int l,int r){if(l>=r)return ;int left  = l-1 ,right = r+1,i=l;int key = nums[new Random().nextInt(r-l+1) + l];while(i<right){if(nums[i]<key){swap(nums,++left,i++);}else if(nums[i]==key){i++;}else{swap(nums,--right,i);}}qsort(nums,l,left);qsort(nums,right,r);}void swap(int[]nums,int i,int j){int tmp = nums[i];nums[i]=nums[j];nums[j]=tmp;}}

7.3 快速选择算法

快速选择算法

在这里插入图片描述

在这里插入图片描述

a,b,c是三个区间的元素个数

就是对数组进行快速排序,然后一次快速排序的结果进行处理,看三个区间的长度,如果第三个区间长度大于k,说明第k大的就在第三个区间
如果第二和第三区间大于k,说明第三个区间小于k,第k大肯定不在第三区间,而第二和第三区间大于k,说明第k大就在这两个区间,所以就在第二个区间
当然整个区间长度肯定大于k,上面两个都不满足,说明第k大肯定在第一区间,所以就在第一区间找k-b-c大的

class Solution {public int findKthLargest(int[] nums, int k) {return sort(nums, 0, nums.length-1,k);}public int sort(int[]nums ,int l,int r ,int k){int key  = nums[new Random().nextInt(r-l+1)+l];int left = l-1,right = r+1,i=l;//快速排序while(i<right){if(nums[i]<key) swap(nums,i++,++left);else if(nums[i]==key)i++;else swap(nums,i,--right);}//三个区间,,,,l,left   left+1,right-1  right,rint b = right-left-1,c = r-right+1;if(c>=k)return sort(nums,right,r,k);else if((b+c)>=k)return nums[right-1];else  return sort(nums,l,left,k-b-c);}void swap(int[]nums,int i,int j){int tmp = nums[i];nums[i]=nums[j];nums[j]=tmp;}
}

7.4 最⼩的 k 个数

最⼩的 k 个数

在这里插入图片描述

第一可以创建一个大根堆
第二就是排序
第三就是快速选择算法

就是三个区间中找左边的数据
区间长度分别为a,b,c
a>k:最小的k个在第一个区间
a+b>=k:最小的k个,就是第一个区间,和剩余的k-a个第二个区间里面的东西
前面两个都不满足的话:那么前面两个区间都是最小的,然后在剩下的right,r区间里面找k-a-b个最小的
在这里插入图片描述

可以把k的大小一直变化,放入图中,就知道为什么这样了,将k的大小长度放入图中进行比较,就一目了然了

class Solution {public int[] inventoryManagement(int[] nums, int k) {qsort(nums,0,nums.length-1,k);//将最小的k个放前面int[] ret = new int[k];for(int i=0;i<k;i++){ret[i] = nums[i];}return ret;}public void qsort(int[]nums , int l,int r,int k){if(l>=r)return;//如果这里不返回的话,当l==r的时候,还会继续执行,然后l就<r了int key = nums[new Random().nextInt(r-l+1) + l];int left = l-1,right = r+1,i=l;while(i<right){if(nums[i]<key) swap(nums,i++,++left);else if(nums[i]==key) i++;else swap(nums,i,--right);}//一次快速排序完成int a = left -l +1;int b = right -left -1;if(a>=k) qsort(nums,l,left,k);else if(a+b>=k) return ;else qsort(nums,right,r,k-a-b);}public void swap(int[]nums,int i,int j){int tmp = nums[i];nums[i]=nums[j];nums[j]=tmp;}
}

8. 分治-归并

8.1 归并排序

归并排序

class Solution {int[]tmp;public int[] sortArray(int[] nums) {tmp= new int[nums.length];mergeSort(nums,0,nums.length-1);return nums;}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,x=left;while(i<=mid&&j<=right){tmp[x++] = nums[i]<nums[j]?nums[i++]:nums[j++];}while(i<=mid){tmp[x++]=nums[i++];}while(j<=right){tmp[x++]=nums[j++];}for(int y = left;y<=right;y++){nums[y]=tmp[y];}}
}

8.2 数组中的逆序对

数组中的逆序对

暴力枚举:n^2

可以这样,先在左区间挑选出逆序对,然后是右区间,然后是左右区间一起的逆序对,最后在一起排序
就是相当于左区间选两个,右区间选两个,然后左右区间各选一个,这样就可以了

找一个区间的某个数有多少个比它大的时候,可以用升序的归并排序
如果找多少个小的,那么就用降序的归并排序

class Solution {int[]tmp;public int reversePairs(int[] nums) {tmp=new int[nums.length];return mergeSort(nums,0,nums.length-1);}int mergeSort(int[]nums,int left,int right){if(left>=right)return 0;int ret = 0;int mid = (left+right)/2;ret+=mergeSort(nums,left,mid);ret+=mergeSort(nums,mid+1,right);//mergeSort的作用是返回这两个区间的逆序对,然后还要把这两个区间排好序//先求出左右区间一起的逆序对,一边求,一边排序int cur1= left,cur2=mid+1,i=left;while(cur1<=mid&&cur2<=right){//主要是找左边比右边大的if(nums[cur1]>nums[cur2]){tmp[i++]=nums[cur2++];ret+=mid-cur1+1;//那么cur1后面的都比cur2大,所以可以找到很多个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;}
}

这个是升序的归并排序
当然也可以弄降序的归并排序

// class Solution {
//     int[]tmp;//     public int reversePairs(int[] nums) {
//         tmp=new int[nums.length];
//         return mergeSort(nums,0,nums.length-1);
//     }//     int mergeSort(int[]nums,int left,int right){
//         if(left>=right)return 0;
//         int ret = 0;
//         int mid = (left+right)/2;
//         ret+=mergeSort(nums,left,mid);
//         ret+=mergeSort(nums,mid+1,right);
//         //mergeSort的作用是返回这两个区间的逆序对,然后还要把这两个区间排好序
//         //先求出左右区间一起的逆序对,一边求,一边排序
//         int cur1= left,cur2=mid+1,i=left;
//         while(cur1<=mid&&cur2<=right){//主要是找左边比右边大的
//             if(nums[cur1]>nums[cur2]){
//                 tmp[i++]=nums[cur2++];
//                 ret+=mid-cur1+1;//那么cur1后面的都比cur2大,所以可以找到很多个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;
//     }
// }class Solution {int[]tmp;public int reversePairs(int[] nums) {tmp=new int[nums.length];return mergeSort(nums,0,nums.length-1);}int mergeSort(int[]nums,int left,int right){if(left>=right)return 0;int ret = 0;int mid = (left+right)/2;ret+=mergeSort(nums,left,mid);ret+=mergeSort(nums,mid+1,right);//mergeSort的作用是返回这两个区间的逆序对,然后还要把这两个区间排好序//先求出左右区间一起的逆序对,一边求,一边排序int cur1= left,cur2=mid+1,i=left;while(cur1<=mid&&cur2<=right){//主要是找左边比右边大的if(nums[cur1]>nums[cur2]){tmp[i++]=nums[cur1++];ret+=right-cur2+1;//那么cur2后面的都比cur1小,所以可以找到很多个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;}
}

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

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

和逆序对是差不多了
找多少个比它小的—》可以用降序的归并排序
先找左边的,在找右边的
最后是一左一右的

但是要返回数组
那么就是数组对应元素+=right-cur2+1
但是这个已经排完序了,不是以前对应的位置了
怎么加呢,怎么找到以前对应下标的位置呢
我们可以设置一个下标数组index,0,1,2,3,4
让这个数组和nums数组一起变化,那么对应的下标也会一起变化了,这样就可以找到了
所以辅助数组tmp也要两个了

class Solution {int ret[];int index[];int indexTmp[];int numsTmp[];public List<Integer> countSmaller(int[] nums) {int n  = nums.length;ret = new int[n];index = new int[n];indexTmp= new int[n];numsTmp = new int[n];for(int i=0;i<n;i++){index[i]=i;//index与nums同步变化,那么nums排序后的数组也可以找到以前的下标了}mergeSort(nums,0,n-1);List<Integer> l = new ArrayList<Integer>();for(int x : ret){l.add(x);}return l;}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 ,i =left;while(cur1<=mid&&cur2<=right){if(nums[cur1]>nums[cur2]){//说明cur1比cur2后面的都要大。这样就找到了一左一右的时候,比cur1小的了ret[index[cur1]] += (right-cur2+1);numsTmp[i]=nums[cur1];//降序的indexTmp[i++] = index[cur1++];}else{numsTmp[i] = nums[cur2];indexTmp[i++] =index[cur2++];}}while(cur1<=mid){numsTmp[i]=nums[cur1];indexTmp[i++] = index[cur1++];}while(cur2<=right){numsTmp[i] = nums[cur2];indexTmp[i++] =index[cur2++];}for(int j=left;j<=right;j++){index[j] = indexTmp[j];nums[j]=numsTmp[j];}}
}

8.4 翻转对

翻转对
降序
nums[i]>2nums[j]
说明i比j后面的两倍都要大,
但是nums[i]>2
nums[j]不能作为条件去排序了
所以ret的计算和排序要分开来
在这里插入图片描述

class Solution {int tmp[];public int reversePairs(int[] nums) {tmp = new int[nums.length];return mergeSort(nums,0,nums.length-1);}public int mergeSort(int[]nums,int left ,int right){if(left>=right) return 0;int ret = 0;int mid = (left+right)/2;ret+=mergeSort(nums,left,mid);ret+=mergeSort(nums,mid+1,right);//降序int cur1 = left ,cur2= mid+1,i=left;while(cur1<=mid&&cur2<=right){// if(nums[cur1]>2*nums[cur2]){//不能等于if(nums[cur1]/2.0>nums[cur2]){//防止越界,但是不要/2,要/2.0,这样就可以除的清了,和原来一模一样的意思//cur1肯定也比cur2后面的要大ret+=right-cur2+1;//以cur1开始的翻转对就找完了cur1++;}else{cur2++;}}cur1 = left ;cur2= mid+1;i=left;while(cur1<=mid&&cur2<=right){//现在开始排序tmp[i++] = nums[cur1]>=nums[cur2] ? nums[cur1++]: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;}
}

9. 链表

9.1 链表常用技巧和操作总结

第一就是不要吝啬定义变量
第二就是引入虚拟头结点
第三就是注意要判断null
第三就是定义尾指针
在这里插入图片描述
在这里插入图片描述

9.2 两数相加

两数相加

在这里插入图片描述
这个链表是逆序的,刚刚好久满足加法,从低位开始相加

如果不是逆序的,那么就要先逆序

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode() {}*     ListNode(int val) { this.val = val; }*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {ListNode head =new ListNode();//虚拟头结点ListNode cur1 = l1 , cur2 = l2;ListNode tail = head;//指向尾节点int t =0;//记录相加结果while(cur1!=null || cur2!= null || t!=0){if(cur1!=null){t+=cur1.val;cur1 = cur1.next;}if(cur2!=null){t+=cur2.val;cur2 = cur2.next;}ListNode tmp =new ListNode(t%10);tail.next = tmp;tail=tmp;t=t/10;}return head.next;}
}

9.3 两两交换链表中的节点

两两交换链表中的节点

定义一个虚拟头结点的话,就不用单独处理头结点的改变了
然后就是多定义变量就很简单了
因为要涉及四个节点next的指向的变化,所以我们定义四个变量
就是node1~4
node指向newHead,所以下次node1就指向node2了,因为node2和node3已经交换了,可以自己画图试试
在这里插入图片描述

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode() {}*     ListNode(int val) { this.val = val; }*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode swapPairs(ListNode head) {if(head==null || head.next == null)return head;//空链表或者只有一个节点ListNode newHead = new ListNode();//先定义一个虚拟头结点newHead.next = head;//连起来///定义四个变量ListNode node1 , node2 ,node3 ,node4;node1 = newHead;node2 = node1.next;//取next的时候,要防止为nullnode3 = node2.next;//node3==head.nextnode4 = node3.next;while(node1!=null&&node2!=null&node3!=null){//node4为null也可以交换//开始交换node1.next=node3;node3.next = node2;node2.next=node4;//node1~node4往后推进node1 = node2;//node2 = node1.next; if(node2!=null )node3 = node2.next; else node3=null;if(node3!=null) node4 = node3.next; else node4=null;}return newHead.next;}
}

10. 哈希表

10.1 简介

就是查找某个元素,看某个元素在不在哈希表,可以用o(1)的时间复杂度

一般用一个数组来模拟简易的哈希表,一般是数据量比较小,比如1到103到107
有负数的时候不要用哈希表
在这里插入图片描述

10.2 两数之和

两数之和

暴力解法:
在这里插入图片描述
解法二用哈希表,比如遍历到了11,我们可以用9-11=-2,然后找一下数组中是不是存在-2-----》哈希
从左向右遍历,一边遍历,一边把数丢进哈希表中,然后一边判断存不存在9-num[i],是往前面判断的,这个是可行的,是判断前面的数据在不在哈希表
如果不这样,一边丢入,一边判断
那么就可能出现这种情况,如果先全部丢入的话,那么如果target=8,nums[i]=4,那么就可能把这个元素找两次
我们存入的是,nums[i],i

class Solution {public int[] twoSum(int[] nums, int target) {Map<Integer,Integer> map  = new HashMap<>();//nums[i],ifor(int i=0;i<nums.length;i++){int x = target - nums[i];if(map.containsKey(x)){return new int[]{i,map.get(x)};}map.put(nums[i],i);}return new int[]{-1,-1};}
}

注意map的containsKey和get和put方法

10.3 判断是否互为字符重排

判断是否互为字符重排

分析一下
就是判断每个字符的个数是不是一样的–>用hash表
我们可以用数组来模拟
用容器呢—》要比较多次
只有小写–》开26大小
统计第一个串的时候,统计次数
统计第二个的时候,减去个数,看最后是不是全0,或者减为负数的时候,也是错误的
还有就是两个字符串长度不一样的时候,一定false

class Solution {public boolean CheckPermutation(String s1, String s2) {if(s1.length()!=s2.length())return false;int[] arr = new int[26];for(int i=0;i<s1.length();i++){arr[s1.charAt(i)-'a']++;}for(int i=0;i<s2.length();i++){arr[s2.charAt(i)-'a']--;if(arr[s2.charAt(i)-'a']<0)return false;}return  true;}
}

记得,如果字符串长度不相等的话,那么也是错误的

就怕s1的长度太长了,然后出错了

总结

http://www.lryc.cn/news/613999.html

相关文章:

  • Redis中String数据结构为什么以长度44为embstr和raw实现的分界线?
  • 【JavaEE】(10) JavaEE 简介
  • 多级缓存架构:新品咖啡上线引发的数据库压力风暴与高并发实战化解方案
  • Spring Boot Redis 缓存完全指南
  • 破解 Django N+1 查询困境:使用 select_related 与 prefetch_related 实践指南
  • sqlite的sql语法与技术架构研究
  • http请求响应
  • npm run 常见脚本
  • token过期为了保证安全,refresh token不过期,那么拿到refresh token就可以获取token,不还是不安全吗
  • C/C++与JavaScript的WebAssembly协作开发指南
  • 【科研绘图系列】R语言绘制气泡图
  • 【优选算法】多源BFS
  • CALL与 RET指令及C#抽象函数和虚函数执行过程解析
  • 【代码随想录day 14】 力扣 111.二叉树的最小深度
  • 集成电路学习:什么是URDF统一机器人描述格式
  • Spring MVC 父子容器深度解析:原理、实战与优化
  • Pytest项目_day09(skip、skipif跳过)
  • iOS 签名证书全流程详解,申请、管理与上架实战
  • 三方相机问题分析七:【datespace导致GPU异常】facebook 黑块和Instagram花图问题
  • 【性能测试】-2- JMeter工具的使用
  • 网吧在线选座系统|基于java和小程序的网吧在线选座小程序系统设计与实现(源码+数据库+文档)
  • 【Jmeter】设置线程组运行顺序的方法
  • Baumer相机如何通过YoloV8深度学习模型实现危险区域人员的实时检测识别(C#代码UI界面版)
  • 利用千眼狼sCMOS相机开展冷离子云成像与测量实验
  • 平板探测器的主要技术指标
  • Spring Boot 优雅配置InfluxDB3客户端指南:@Configuration + @Bean + yml实战
  • C# 异步编程(GUI程序中的异步操作)
  • 从浅拷贝到深拷贝:C++赋值运算符重载的核心技术
  • 【设计模式】抽象工厂模式 (工具(Kit)模式)
  • 【接口自动化】-2- request模块及通过变量实现接口关联