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

【day10】分治

1、快速排序

题目:912. 排序数组 - 力扣(LeetCode)

分析:朴素的快排是将数组分两类:大于等于 key 和 小于 key。 时间复杂度:N*树高,理想情况(基准点最最后在中间) O(nlogn);最差情况(基本有序,基准点最后在边上)O(n^2)。

        为了尽量避免最差情况带来的低效率,我们做出以下优化:

  • 类似于 day6 的颜色分类,把数组分三类:小于 key、等于 key、大于 key。目的是当存在大量重复数字时,直接省去对等于 key 的子数组递归。
  • 随机选取基准,让基准最后的位置递归的左右子数组划分长度更均匀(让树高尽量低)。

        时间复杂度:更接近 O(nlogn)

代码

class Solution {private static final Random random = new Random();private static void swap(int[] arr, int i, int j) {int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}private void qsort(int[] arr, int l, int r) {if(l>=r) return; // 空数组或者只有一个数,不用再排序int key = arr[l+random.nextInt(r-l+1)]; // 随机获取一个基准 l+(0~n-1)// 实现三划分int left = l-1, right = r+1, i = l;while(i < right) {if(arr[i] < key) swap(arr, ++left, i++);else if(arr[i] == key) i++;else swap(arr, --right, i);}// 递归,继续排序子数组qsort(arr, l, left);qsort(arr, right, r);return;}public int[] sortArray(int[] arr) {qsort(arr, 0, arr.length-1);return arr;}
}

2、数组中的第K个最大元素(快速选择)hot

题目:215. 数组中的第K个最大元素 - 力扣(LeetCode)

分析

法一:基于堆排序的选择。建立大根堆,做 k-1 次删除堆顶后,堆顶元素就是第 k 大。时间复杂度:建堆 O(n)+删 k 次堆顶O(klogn)=O(n+klogn)=O(n+nlogn)=O(nlogn);空间复杂度:树高 O(logn),因为若是树根向下调整最多调整树高次子树,即递归树高次。

法二:基于快速排序的选择。三划分,然后去对应区间找第 K 大。

  • K <= c:递归 [right, r]。
  • c < K <= c+b:肯定是 key,直接返回 key。
  • c+b < K <= c+b+a:递归 [l, left],更新 K=K-(b+c)。

        时间复杂度:O(n),这是我背的,想证明得两页多;空间复杂度:O(logn)。

代码

class Solution {private static Random random = new Random();private void swap(int[] nums, int a, int b) {int tmp = nums[a];nums[a] = nums[b];nums[b] = tmp;}public int findKthLargest(int[] nums, int k) {return findK(nums, 0, nums.length-1, k);}private int findK(int[] nums, int l, int r, int k) {if (l == r) return nums[l]; // 最终都会找到第 k 大数,停止递归// 三划分int left = l-1, right = r+1, i = l;int key = nums[l + random.nextInt(r-l+1)];while(i < right) {if (nums[i] < key) swap(nums, ++left, i++);else if (nums[i] == key) i++;else swap(nums, --right, i);}// 到对应区间找第 K 大int c = r-right+1;int b = right-left-1;int a = left-l+1;if (k <= c) return findK(nums, right, r, k);else if (k <= c+b) return key;else return findK(nums, l, left, k-(b+c));}
}

3、最小的 k 个数(快速选择)

题目:面试题 17.14. 最小K个数 - 力扣(LeetCode)

分析

法一:从小到大排序,然后获得前 k 个,时间复杂度 O(nlogn) 空间复杂度:快速O(1) 但时间复杂度最坏O(n^2);堆递归O(logn)。

法二:基于堆排序的选择。先构建大小为 k 的大根堆,遍历剩下的数,有比堆顶小的就删除堆顶,然后入堆。遍历结束后,堆中的 k 个元素就是前 k 小。 时间复杂度:插入/删除操作O(logk),最坏情况 n 个数都进行了插入删除,O(nlogk)。空间复杂度:堆的空间 O(k)+递归的空间O(logk) = O(k)

法三:基于快速排序的选择。每个子数组:随机选择基准+三划分。

  • k <= a:在 < key 的子数组里找前 k 小的数。
  • a < k <= a+b:返回 < key 的子数组+(k-a)个key,即数组中前 k 个数。
  • a+b < k <= a+b+c:确定前 a+b 小的数,在 > key 的子数组里找前 k-(a+b) 小的数。 

时间复杂度:背的 O(n)

空间复杂度:最坏O(n),通常最理想O(logn);也有可能 O(1),数字的所有元素相同的情况。

代码

class Solution {private static Random random = new Random();private void swap(int[] arr, int a, int b) {int tmp = arr[a];arr[a] = arr[b];arr[b] = tmp;}private void qsort(int[] arr, int l, int r, int k) {if (l >= r) return; // 没子数组了,不用排了// 随机选取基准+三划分int key = arr[l+random.nextInt(r-l+1)];int left = l-1, right = r+1, i = l;while(i < right) {if (arr[i] < key) swap(arr, ++left, i++);else if (arr[i] == key) i++;else swap(arr, --right, i);}// 分情况找前 k 小int a = left-l+1;int b = right-left-1;int c = r-right+1;if (k <= a) qsort(arr, l, left, k);else if (k <= a+b) return;else qsort(arr, right, r, k-(a+b));}public int[] smallestK(int[] arr, int k) {qsort(arr, 0, arr.length-1, k);int[] ret = new int[k];for(int i = 0; i < k; i++) ret[i] = arr[i];return ret;}
}

4、归并排序

题目:912. 排序数组 - 力扣(LeetCode)

分析:前面的快速排序:先排序,后划分(类似二叉树先序遍历);归并排序:先划分,后合并时排序(类似二叉树后序遍历)。时间复杂度:树高 O(logn) * 每层遍历个数 O(n) = O(nlogn)。空间复杂度:递归深度 O(logn) + 额外数组存放合并后的数组 O(n) = O(n)

代码

class Solution {private static int[] tmp;// 归并排序public int[] sortArray(int[] arr) {tmp = new int[arr.length];qsort(arr, 0, arr.length-1);return arr;}private void qsort(int[] arr, int l, int r) {if (l >= r) return; // 终止条件,没有子数组了不再需要排序// 先划分int mid = l+(r-l)/2;qsort(arr, l, mid);qsort(arr, mid+1, r);// 再合并merge(arr, l, mid, r);}private void merge(int[] arr, int l, int mid, int r) {int i = l, j = mid+1, k = 0;while(i <= mid && j <= r)tmp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];while(i <= mid) tmp[k++] = arr[i++];while(j <= r) tmp[k++] = arr[j++];// 复位for(int m = l; m <= r; m++) {arr[m] = tmp[m-l];}}
}

5、数组中的逆序对(基于归并排序)

题目:LCR 170. 交易逆序对的总数 - 力扣(LeetCode)

分析

暴力解法:固定一个数,然后遍历后续数字查找。时间复杂度 O(n^2)。

基于归并排序:先计数左子数组中的逆序对,再计数右子数组的逆序对,最后计数一左一右的逆序对。

若是升序排序:

 

① 以 cur1 为准:找谁比我小。

  • cur1>cur2,cur2 及 cur2 前的都比 cur1 小,计数 (cur2-(mid+1)+1),cur2 之后的不确定,cur2++,但后续会重复计数。行不通。

② 以 cur2 为准:谁比我大。

  • cur1 > cur2,cur1 之前的都比 cur2 小(升序),cur1 及 cur1 之后的都比 cur2 大,计数(mid-cur1+1),此时的 cur2 已找完比它大的,找下一个 cur2,cur2++。
  • cur1 <= cur2,cur1 及其之前的都小于等于 cur2 ,不需要统计,但 cur1 之后的还不确定,cur1++。

若是降序排序:

① 以 cur1 为准:谁比我小

  • cur1 > cur2,cur2 之前的都比 cur1 大,cur2 及其之后的都比 cur1 小,已找完此时 cur1 在右子数组中所有比他小的,统计(r-cur2+1),cur1++。
  • cur1 < cur2,cur2 及其之前的都比 cur1 大,之后的不确定,cur2++,继续找。

② 以 cur2 为准,行不通。

时间复杂度:O(nlogn),空间复杂度 O(n)

代码

class Solution {private static int[] tmp;public int reversePairs(int[] record) {tmp = new int[record.length];return sort(record, 0, record.length-1);}private int sort(int[] nums, int l, int r) {if (l >= r) return 0; // 子数组不够两个数,没有逆序对// 先排序,左子数组逆序对 + 右子数组逆序对int mid = l+(r-l)/2;int ret = sort(nums, l, mid) + sort(nums, mid+1, r);// 合并,同时找出一左一右的逆序对个数int i = l, j = mid+1, k = 0;while (i <= mid && j <= r) {if (nums[i] > nums[j]) {ret += mid-i+1;tmp[k++] = nums[j++];} else tmp[k++] = nums[i++];}while(i <= mid) tmp[k++] = nums[i++];while(j <= r) tmp[k++] = nums[j++];// 复位for(int m=l; m <= r; m++)nums[m] = tmp[m-l];return ret;}
}

6、计算右侧小于当前元素的个数

题目:315. 计算右侧小于当前元素的个数 - 力扣(LeetCode)

分析

暴力解法:O(n^2)

基于归并排序:左子数组计数+右子数组计数+一左一右计数。

一左一右计数算法,符合合并时的规律:

以左子树的 cur1 为基准,谁比我小。

① 降序排序:

  • cur1 > cur2,cur2 之前的都比 cur1 大,cur2 及其之后的都比 cur1 小,计数 (r-cur2+1),已找完 cur1 所有满足条件的右子数组元素,cur1++。
  • cur1 <= cur2,cur2 及其之前的都大于等于 cur1,cur2 之后的不确定,目前没有满足条件的不计数,cur2++。

② 升序排序:

  • cur1 > cur2,cur2 及其之前的都比 cur1 小,计数 (cur2-(mid+1)+1),cur2 之后的不确定,继续 cur2++,但后续会重复计数,不可行。

        注意:需要用 counts 数组计数原数组各位置元素满足条件的右元素个数,但是排序时已经把顺序打乱了,所以需要先用 index 数组记录每个元素的位置,然后位置同元素一起排序(元素与位置绑定),计数时通过 index 获取索引 i 后,计数 count[i]。

代码

class Solution {private static int[] index; // 元素原索引private static int[] tmp; // 暂存放合并后的子数组private static int[] tmp2; // 暂存放合并后的子数组原索引private static int[] counts; // 计数原数组每个元素其右满足条件的元素个数public List<Integer> countSmaller(int[] nums) {int n = nums.length;index = new int[n];tmp = new int[n];tmp2 = new int[n];counts = new int[n];// 初始化 index 数组for(int i = 0; i < nums.length; i++) index[i] = i;sort(nums, 0, nums.length-1);List<Integer> ret = new ArrayList<>();for(int count : counts) ret.add(count);return ret;}private void sort(int[] nums, int l, int r) {if (l >= r) return; // 子数组不够两个数,没有满足条件的// 先排序,统计左子数组、统计右子数组int mid = l+(r-l)/2;sort(nums, l, mid);sort(nums, mid+1, r);// 合并,统计一左一右int i = l, j = mid+1, k = 0;while (i <= mid && j <= r) {if (nums[i] > nums[j]) {counts[index[i]] += r-j+1; // 统计tmp2[k] = index[i];tmp[k++] = nums[i++];} else {tmp2[k] = index[j];tmp[k++] = nums[j++];}}while(i <= mid) {tmp2[k] = index[i];tmp[k++] = nums[i++];}while(j <= r) {tmp2[k] = index[j];tmp[k++] = nums[j++];}// 复位for(int m=l; m <= r; m++) {index[m] = tmp2[m-l];nums[m] = tmp[m-l];}   }
}

7、翻转对

题目:493. 翻转对 - 力扣(LeetCode)

 

分析:暴力解法 O(n^2)

基于归并排序:

以 cur1 为基准,找右子数组中满足条件的。

降序:

  • cur1 <= 2*cur2,cur2 之前的都大于 cur2,因此 cur2 及其之前的 2 倍肯定大于等于 cur1,不计数;cur2 之后的不确定,cur2++。
  • cur1 > 2*cur2(cur2 一直++后,出现的第一个 cur2,其2倍比 cur1 小),cur2 之前的2倍都大于等于 cur1,不计数。cur2 及其之后的 2 倍肯定比 cur1 小,计数 (r-cur2+1),此时的 cur1 与之匹配的 cur2 个数已全部统计,cur1++。(cur1++后,cur1变小,当前cur2 之前的2倍都比上一个 cur1大,因此cur2之前的2倍肯定比当前 cur1 大不满足条件,因此 cur2 不需要回退)
  • 但因为归并排序的判断条件是 cur1 与 cur2 的比较;而翻转对的判断是 cur1 与 2*cur2 的比较,因此不能把翻转对的统计放到合并时执行。
  • 我们希望利用一左一右数组的有序性,因此在合并之前统计翻转对。

以 cur2 为准,找左子数组中满足 cur1*1/2 > cur2

升序:

  • cur1*1/2 <= cur2,cur1 之前的都小于 cur1,cur1及其之前的 1/2 都小于等于 cur2,不满足条件,不统计;cur1 之后的1/2 不确定,因此 cur1++。
  • cur1*1/2 > cur2(左子数组中找到的第一个 cur1 与此时的 cur2 匹配),cur1 之前的都不匹配,cur1 之后的肯定比 cur1 大,都匹配,计数 (mid-cur1+1),与当前 cur2 匹配的 cur1 已全部统计,找下一个 cur2 与之匹配的 cur1 个数,cur2++。

        时间复杂度:O(2n*logn) = O(nlogn);空间复杂度:O(n)

代码

class Solution {private static int[] tmp;public int reversePairs(int[] nums) {tmp = new int[nums.length];return sort(nums, 0, nums.length-1);}private int sort(int[] nums, int l, int r) {if (l >= r) return 0; // 子数组不够两个数,没有满足条件的// 先排序,统计左子数组+右子数组int mid = l+(r-l)/2;int ret = sort(nums, l, mid) + sort(nums, mid+1, r);// 统计一左一右int cur1 = l, cur2 = mid+1;while(cur1 <= mid && cur2 <= r) {// *2 转换为 /2,避免溢出if (nums[cur1]/2.0 <= nums[cur2]) cur2++;else {ret += r-cur2+1;cur1++;}}// 合并int i = l, j = mid+1, k = 0;while (i <= mid && j <= r) tmp[k++] = nums[i] >= nums[j] ? nums[i++] : nums[j++];while(i <= mid) tmp[k++] = nums[i++];while(j <= r) tmp[k++] = nums[j++];// 复位for(int m=l; m <= r; m++) nums[m] = tmp[m-l];return ret;  }
}
http://www.dtcms.com/a/528012.html

相关文章:

  • 【Go】C++转Go:数据结构练习(一)排序算法
  • 每天学习一个新注解——@SafeVarargs
  • valgrind交叉编译android版本
  • 公司网站开发设计题目来源怎么写佛山免费建站怎样
  • 构建AI智能体:七十四、探索AI新纪元:扣子平台让想法到智能应用的极简之旅
  • P2119 [NOIP 2016 普及组] 魔法阵
  • 数据结构13:排序
  • 网站搭建 里短信wordpress acf破解版
  • 【C/C++】数据在内存中的存储
  • 我们项目中如何运用vueuse
  • 【开发者导航】集成多引擎与离线查询的macOS开源翻译工具:Easydict
  • 龙岗客户 IBM x3650 M5服务器system board fault故障,上门快修分享
  • TENGJUN-TYPE-C 24PIN(JX24-BPS015-A)连接器深度技术解析
  • 10.23作业
  • 深入剖析 Vue Router History 路由刷新页面 404 问题:原因与解决之道
  • FreeP2W:一个PDF转Word的CLI工具
  • .NET - .NET Aspire的Command-Line和GitHub Copilot
  • 10月25日
  • 【电玩电脑杂志】超级整理合集PDF
  • 怎样做某个网站有更新的提醒成都网络优化网站
  • 计算机视觉:python车牌识别检测系统 YOLOv8 深度学习pytorch技术 LPRNet车牌识别算法 CCPD2020数据集 ✅
  • Qt 中如何操作 Excel 表格:主流开源库说明介绍与 QXlsx 库应用全解析
  • 基于单片机的全自动洗衣机控制器设计
  • 电子商务网站规划原则教师遭网课入侵直播录屏曝光口
  • 免费发广告网站攀枝花建设集团网站
  • Day 23 机器学习管道 pipeline
  • Vue CLI 插件开发完全指南:从原理到实战
  • Linux中内核和用户空间通信send_uevent函数的实现
  • Python设计模式实战:用Pythonic的方式实现单例、工厂模式
  • 智能规模效应:解读ChatGPT Atlas背后的数据边界之战