002-算法随记
递归解决某些问题
等规模递归的时间复杂度
-
子问题满足等规模递归的问题都可以用master公式求解时间复杂度
\[T(N) = a \times T(\frac{N}{b}) + O(N^d) \]- 其中,N是母问题的数据量,a是子问题的调用次数,b是子问题等规模划分的块数,\(O(N^d)\)是除了递归操作之外的其他操作的时间复杂度
-
下面这种也属于是等规模递归
- 上图中b是\(\frac{2}{3}\)
-
时间复杂度计算结果(推论)
\[if(log_{b}a < d),res \longrightarrow 时间复杂度:O(N^d)\\ if(log_{b}a > d),res \longrightarrow 时间复杂度:O(N^{log_ba})\\ if(log_{b}a = d),res \longrightarrow 时间复杂度:O(N^d \times logN) \]
归并排序
-
以一个位置划分左右,让左侧排好序,再让右侧排好序,然后合并两边(左右分别从左到右遍历,哪边较小就先放哪边的数)
-
划分中点:为防止越界,不用相加除以二的方式拿中点。这样可以防止相加时突破类型最大值,右移一位相当于除以二
m = L + ((R - L) >> 1);
-
时:\(O(N\times logN)\)
-
空:\(O(N)\)
归并排序拓展:小和问题
-
题目:给定一个数组,求每个位置左边所有比它小的数的和,之后把这些和再求和就是小和
- 例如[1, 3, 4, 2, 5] -> [0, 1, 4, 1, 10] -> 16
-
暴力解法:每个位置左边遍历求和再求和,\(O(N^2)\)
-
递归解:转换为求当前数右侧有几个比他大,即小和中就有多少个该数。用归并排序的过程中,左右是始终保证各自有序的,此时只需要判断右组第几个数比左组当前数大,即可直接拿到右组有几个数比左组当前数大。
-
不可省略排序过程,因为:该过程只有保证左右都是有序的,才能一下就得到右组有几个比左组当前数大的值(右组该数及其右侧数都比左组当前数要大)
-
和归并排序的唯一不同点:组合的时候,当左右两边当前值相等时,要先把右组的数放进去,左组的数还要继续和右组后面的数继续比较才能确定从第几个数开始,右边比左边大
-
返回的是左组小和+右组小和+本身merge时产生的小和
-
特点:不重算、不遗漏
-
归并排序拓展:逆序对问题
- 题目:在一个数组中,左边的数如果比右边的数大(两个数可以在任何位置),则两个数构成一个逆序对,请求出逆序对数量
- 例如[3, 2, 4, 5, 0]中,逆序对有[3, 2]、[3, 0]、[2, 0]、[4, 0]、[5, 0]
- 和小和问题一样,看右边有几个比它小即可
快速排序相关
荷兰国旗问题1
-
题目:给定一个数组arr和一个数num,请把小于num的数放在数组左边,大于num的放在数组右边。要求时\(O(N)\),空\(O(1)\)
-
解法:定义一个小于等于区,初始时置于0位置以左,一个指针往右遍历,遇到小于等于num的就和小于等于区右边一个数交换,然后小于等于区扩容到下一个位置上
荷兰国旗问题2
-
题目:在问题1的基础上要求把等于num的数都放在中间
-
解法:定义小于区和大于区,分别置于-1和N。用i从左到右遍历。
- [i]<num,则和小于区下一个交换,小于区向后扩容,i++
- [i]=num,则i++
- [i]>num,则和大于区的前一个交换,大于区向前扩容,i不动
- i和大于区装上就停止
实质上就是小于区推着等于区往右走
快速排序
-
快排1.0:每次递归,总拿最后一个位置的数当做num,每一个递归层里都做荷兰国旗问题1的解法,然后把最后一个位置的那个num和小于区的下一个交换,然后左右进递归。\(O(N^2)\)
-
快排2.0:每次递归,总拿最后一个位置的数当做num,但像荷兰国旗2问题一样,在划分完小于等于大于三个区后,把最后位置的这个数和等于区的下一个数交换,就把这个数的等于区搞定了,然后左右的小于区和大于区进递归。\(O(N^2)\)
-
快排3.0:前两种方法的最差情况(\(O(N^2)\) ),最好情况(\(O(N\times log_2N)\)):划分值是最小值或最大值。
- 随机选一个数放到最后一个位置上,然后开始2.0的过程
- 把所有可能划分的比例的时间复杂度加权累加,再求长期期望,得到时间复杂度\(O(N\times log_2N)\)
-
代码:
class Solution { public int[] sortArray(int[] nums) { quickSort(nums, 0, nums.length - 1); return nums; } public void quickSort(int[] nums, int l, int r) { if (l >= r) return; swap(nums, l + (int)(Math.random() * (r - l + 1)), r); int[] p = partition(nums, l, r); quickSort(nums, l, p[0] - 1); quickSort(nums, p[1] + 1, r); } public int[] partition(int[] nums, int l, int r) { int lessArea = l - 1; int moreArea = r; while (l < moreArea) { if (nums[l] < nums[r]) { swap(nums, ++lessArea, l++); }else if(nums[l] > nums[r]) { swap(nums, l, --moreArea); }else { ++l; } } swap(nums, moreArea, r); int[] res = {lessArea + 1, moreArea}; return res; } public void swap(int[] nums, int a, int b) { int c = nums[a]; nums[a] = nums[b]; nums[b] = c; } }
-
空间复杂度:\(O(logN)\),最差情况\(O(N)\)