分治
基础模板
合并两个有序数组
java
public void merge(int[] nums1, int m, int[] nums2, int n) {
int i = m-1;
int j = n-1;
int k = m+n-1;
while(i>=0 && j>=0){
if(nums1[i] > nums2[j]){
nums1[k--] = nums1[i--];
}else{
nums1[k--] = nums2[j--];
}
}
while(j>=0) nums1[k--] = nums2[j--];
}
合并两个有序链表
java
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
while(l1!=null && l2!=null){
if(l1.val < l2.val){
cur.next = l1;
l1 = l1.next;
}else{
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1 == null ? l2 : l1;
return dummy.next;
}
分治 - 快速排序
75. 颜色分类
🎯 核心思路
用 left 指针 :左边全放 0(红色)
用 right 指针 :右边全放 2(蓝色)
用 cur 指针:从头遍历到尾
遇到 0 → 和 left 交换,left++、cur++
遇到 1 → 不管,cur++
遇到 2 → 和 right 交换,right--、cur 不动
🧠 为什么遇到 2,cur 不移动?
因为从右边换过来的数可能是 0,你必须再检查一遍,不能直接往前走!
分析:
- 三指针:left /right/cur
- 时间 O (n),空间 O (1)
- 交换 2 时 cur 不移动
java
class Solution {
public void sortColors(int[] nums) {
//left是0的右边界 right是2的左边界
int left=0;
int right=nums.length-1;
int cur=0;
while(cur<=right){
if(nums[cur]==0){ //把0移到左边
swap(nums,left,cur);
cur++;
left++;
}else if(nums[cur]==1){ //1不动
cur++;
}else{//2移动到右边
swap(nums,right,cur);
right--;
// cur++ 注意:不能移动,因为移动过来的可能是0,必须再次判断
}
}
}
public void swap(int[] nums,int a,int b){
int tmp=nums[a];
nums[a]=nums[b];
nums[b]=tmp;
}
}
912. 排序数组(快排)
注意:
- 固定选左 / 右基准 /三数取中 → 遇到有序数组直接超时
- 随机选基准(移动到最左边) → 数学上保证时间复杂度 O(n log n)
- 面试快排必写随机基准! 这是满分细节!
java
class Solution {
public int[] sortArray(int[] nums) {
quickSort(nums,0,nums.length-1);
return nums;
}
public void quickSort(int[] nums,int left,int right){
//相等可以哦,不然死循环
if(left>=right) return;
int num=new Random().nextInt(right-left+1)+left; //随机取
// int num=(right-left)/2+left; //三数取中
swap(nums, left, num); // 把随机数换到最左边
int base=left; //左当基准值,右则先动
int l=left,r=right; //不修改原来的左/右区间
while(l<r){
//右先找小,左找大(等于最好加进去)
while(l<r&&nums[r]>=nums[base]) r--;
while(l<r&&nums[l]<=nums[base]) l++;
//交换
swap(nums,l,r);
}
swap(nums,base,l);//此时left=right
//划分区间
quickSort(nums,left,l-1); //左半
quickSort(nums,l+1,right);//右半
}
public void swap(int[] nums,int a,int b){
int tmp=nums[a];
nums[a]=nums[b];
nums[b]=tmp;
}
}
小优化,就是当数组有序的时候,快排时间复杂度O(n的2次方)
java
public int[] sortArray(int[] nums) {
//优化,判断数组是不是升序
if(isSort(nums)) return nums;
quickSort(nums,0,nums.length-1);
return nums;
}
public boolean isSort(int[] nums){
boolean flag=true; //默认是升序
for(int i=1;i<nums.length;i++){
if(nums[i]<nums[i-1]) flag=false;
}
return flag;
}
215. 数组中的第K个最大元素
核心思路(快排思考)
- 快速选择 = 用快排的分区思想 + 只搜一边 + 找第 K 大
- 快排
partition一次,就能把基准值放到最终正确位置 - 基准下标
pos正好是第 N 大元素 - 比快排更快:只递归一边,不用全排
为什么比快排快?
-
纯快排:递归左右两边 O(n log n)
-
**目前使用基于快排的快速选择:**只递归一边 O (n),面试最优解!
-
- 用了 partition(快排灵魂)
- 没有递归两边
- 不断缩小范围,只找目标位置
- 找到就直接返回,不排序整个数组
java
class Solution {
public int findKthLargest(int[] nums, int k) {
int n=nums.length;
//第k大的数,就在 排好下标 n-k 下
int TopK=n-k;
int left=0,right=n-1;
while(true){
int pos=partition(nums,left,right); //获取排好元素的下标
if(pos==TopK) return nums[TopK];
else if(pos>TopK) right=pos-1; //排好元素在右边,往左分
else left=pos+1; //往右分
}
}
//一次分区
public int partition(int[] nums,int left,int right){
// 一次分区,可以不用这个 递归判断
//if(left>=right) return;
int num=(right-left)/2+left;
int l=left,r=right;
swap(nums,left,num);
int base=left; //基准值下标
while(l<r){
//有先走
while(l<r&&nums[r]>=nums[base]) r--;
while(l<r&&nums[l]<=nums[base]) l++;
swap(nums,r,l);
}
swap(nums,base,l); //排好基准值
return l; //基准值下标(先不着急递归)
}
public void swap(int[] nums,int a,int b){
int tmp=nums[a];
nums[a]=nums[b];
nums[b]=tmp;
}
}
LCR 159. 库存管理 III(原:剑指 Offer . 最小的k个数)
忽略,和前面差不多,只修改了一点地方,下面是演示修改,不是全部代码
java
int left = 0;
int right = arr.length - 1;
int target = k - 1; // 🔥 最小k个数:找到下标 k-1 就行
while (true) {
int pos = partition(arr, left, right);
if (pos == target) {
// 🔥 前k个直接返回!
int[] res = new int[k];
for (int i = 0; i < k; i++) res[i] = arr[i]; //收集
return res;
} else if (pos > target) {
right = pos - 1;
} else {
left = pos + 1;
}
}
分治 - 归并排序
912. 排序数组(归并)
归并排序 核心思想:分治法
分:把数组从中间切成两半
治:递归把左右两半都排好序
合 :双指针合并两个有序数组(最关键)
- 开辟临时数组
- 记得合并后,拷贝回来
左:left ~ mid-1
右:mid ~ right
为什么错?右边永远会多一个元素,递归永远切不完!直接死循环 → 栈溢出!
归并拆分必须是:
左:left ~ mid
右:mid+1 ~ right
java
class Solution {
public int[] sortArray(int[] nums) {
mergeSort(nums,0,nums.length-1);
return nums;
}
public void mergeSort(int[] nums,int left,int right){
if(left>=right) return; //没有/一个元素 不用进行了
//分:帮数组分半
int mid=(right-left)/2+left;
//治:递归把左右两半都排好序
mergeSort(nums,left,mid);
mergeSort(nums,mid+1,right);
//合并
merge(nums,left,mid,right);
}
//合并 [left,mid] [mid+1,right]
public void merge(int[] nums,int left,int mid,int right){
int[] tmp=new int[right-left+1]; //临时数组
int i=left,j=mid+1;
int cur=0; //记录临沭数组指针
//谁小加谁
while(i<=mid&&j<=right){
if(nums[i]<nums[j]) tmp[cur++]=nums[i++];
else tmp[cur++]=nums[j++];
}
//把剩下的放进来
while(i<=mid) tmp[cur++]=nums[i++];
while(j<=right) tmp[cur++]=nums[j++];
//拷贝回来到原数组
for(int p=0;p<tmp.length;p++) nums[left+p]=tmp[p];
}
public void swap(int[] nums,int a,int b){
int tmp=nums[a];
nums[a]=nums[b];
nums[b]=tmp;
}
}
LCR 170. 交易逆序对的总数(原:剑指 Offer . 数组中的逆序对)
核心思路:本质就在归并排序基础上合并nums[i]>nums[j]处统计逆序对个数
什么是逆序对?
前面的数 > 后面的数 → 算 1 个逆序对 [7,5,6,4] → 逆序对:(7,5),(7,6),(7,4),(5,4),(6,4) → 共 5 个
为什么用归并?
归并在合并两个有序数组 时,可以直接一次性算出一堆逆序对 ,不用暴力枚举!时间复杂度:O(n log n)
java
class Solution {
int cnt=0; //全局统计元素
public int reversePairs(int[] record) {
mergeSort(record,0,record.length-1);
return cnt;
}
public void mergeSort(int[] nums,int left,int right){
if(left>=right) return; //没有/一个元素 不用进行了
//分:帮数组分半
int mid=(right-left)/2+left;
//治:递归把左右两半都排好序
mergeSort(nums,left,mid);
mergeSort(nums,mid+1,right);
//合并
merge(nums,left,mid,right);
}
//合并 [left,mid] [mid+1,right]
public void merge(int[] nums,int left,int mid,int right){
int[] tmp=new int[right-left+1]; //临时数组
int i=left,j=mid+1;
int cur=0; //记录临沭数组指针
//谁小加谁
while(i<=mid&&j<=right){
if(nums[i]<=nums[j]) tmp[cur++]=nums[i++];
//在这里添加点逻辑
else{
//核心:统计逆序对,i到mid 全都大于nums[j]
cnt+=mid-i+1;
tmp[cur++]=nums[j++];
}
}
//把剩下的放进来
while(i<=mid) tmp[cur++]=nums[i++];
while(j<=right) tmp[cur++]=nums[j++];
//拷贝回来到原数组
for(int p=0;p<tmp.length;p++) nums[left+p]=tmp[p];
}
public void swap(int[] nums,int a,int b){
int tmp=nums[a];
nums[a]=nums[b];
nums[b]=tmp;
}
}
其他
有空可以了解