【算法篇】6.分治

分治

基础模板

合并两个有序数组

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;
    }
}

其他

有空可以了解

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

493. 翻转对

相关推荐
自我意识的多元宇宙38 分钟前
树与二叉树--二叉树的存储结构
数据结构
u0109147601 小时前
CSS组件库如何快速扩展_通过Sass @extend继承基础布局
jvm·数据库·python
baidu_340998821 小时前
Golang怎么用go-noescape优化性能_Golang如何使用编译器指令控制逃逸分析行为【进阶】
jvm·数据库·python
m0_678485451 小时前
如何利用虚拟 DOM 实现无痕刷新?基于 VNode 对比的状态保持技巧
jvm·数据库·python
不吃香菜学java1 小时前
Redis的java客户端
java·开发语言·spring boot·redis·缓存
qq_342295821 小时前
CSS如何实现透明背景效果_通过RGBA色彩模式控制透明度
jvm·数据库·python
TechWayfarer1 小时前
知乎/微博的IP属地显示为什么偶尔错误?用IP归属地查询平台自检工具3步验证
网络·python·网络协议·tcp/ip·网络安全
Greyson12 小时前
CSS如何处理超长文本换行问题_结合word-wrap属性
jvm·数据库·python
码事漫谈2 小时前
大模型输出的“隐性结构塌缩”问题及对策
前端·后端
captain3762 小时前
事务___
java·数据库·mysql