【算法篇】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. 翻转对

相关推荐
Fcy6482 小时前
算法竞赛有关数据结构的补充(3)—— 二叉树、堆和哈希表的静态实现(包括红黑树和AVL树动态实现)
数据结构·算法·散列表
Elastic 中国社区官方博客2 小时前
我们如何修复 OpenTelemetry 中基于 head 的采样
大数据·开发语言·python·elasticsearch·搜索引擎
biubiubiu07062 小时前
Spring Boot 中如何自定义一个 Starter
java·spring boot·后端
TechPioneer_lp2 小时前
2026微软SDE LeetCode高频题:208道,按频度排序,含备考建议
算法·leetcode·microsoft·leetcode刷题·大厂算法刷题·微软sde·微软笔试题
科德航空的张先生2 小时前
空管模拟器在塔台指挥训练中的应用与效能分析
人工智能·算法
15Moonlight2 小时前
Java基础篇
java·intellij-idea
鸽鸽程序猿2 小时前
【JavaEE】【SpringAI】图像模型与语音模型
java·java-ee
飞鱼计划2 小时前
EasyExcel 3.3.2 模板方式写入数据完整指南
java·开发语言
梦因you而美2 小时前
Python自动化复制Excel sheet表(openpyxl+win32com双方案,完美保留格式)
python·自动化·excel·win32com·openpyxl