目录
一、剖析递归行为和递归行为时间复杂度的估算
master公式的使用(满足子问题等规模)
T(N)=a*T(N/b)+O(N^d)
T(N/b)表示我调用子问题时规模是否等量
a代表子问题是等规模下被调用了多少次
O(N^d)表示除去调用子规模外,剩下过程的时间复杂度
1) log(b,a)>d->复杂度为O(N^log(b,a))
2) log(b,a)=d->复杂度为O(N^d*logN)
3) log(b,a)<d->复杂度为O(N^d)
例子:求整个数组的最大值
代码:
public class A {
public static int getMax(int[] arr){
return process(arr,0,arr.length-1);
}
public static int process(int[] arr,int l,int r){
if(l==r) return arr[l];
int mid=l+((r-l)>>1);
int leftMax=process(arr,l,mid);
int rightMax=process(arr,mid+1,r);
return Math.max(leftMax,rightMax);
}
}
上述过程由master公式得 T(N)=2*T(N/2)+O(1)->log(b,a)>d->复杂度为O(N^log(b,a))
二、归并排序
1)整体就是一个简单递归,左边排好序、右边排好序、让其整体有序
2)让其整体有序的过程里用了外排序方法
3)利用master公式来求解时间复杂度
4)归并排序的实质时间复杂度O(N*logN),额外空间复杂度O(N)
流程:l,r范围上排序,先求中点,先让左侧排好序,再让右侧排好序
例:数组为3,2,1,5,6,2 左侧排好序为1,2,3右侧排好序为2,5,6
左侧下标指向1,右侧下标指向2,左侧如果<=右侧先拷贝左侧的,相反则拷贝右侧的
准备一个辅助空间用来放拷贝的数字,然后将数组放回
代码为:
题目链接:归并排序
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int l=0;
tmp.reserve(nums.size()+1);
int r=nums.size()-1;
possess(nums,l,r);
return nums;
}
void possess(vector<int>&nums,int l,int r)
{
if(l>=r) return;
int mid=(r+l)/2;
possess(nums,l,mid);
possess(nums,mid+1,r);
merge(nums,l,mid,r);
}
vector<int>tmp;
void merge(vector<int>&nums,int l,int mid,int r)
{
int p1=l;
int p2=mid+1;
int i=0;
while(p1<=mid&&p2<=r)
{
if(nums[p1]<nums[p2]) tmp[i++]=nums[p1++];
else tmp[i++]=nums[p2++];
}
while(p1<=mid) tmp[i++]=nums[p1++];
while(p2<=r) tmp[i++]=nums[p2++];
int j = 0;
for (i = l,j; i <= r; ++i, ++j) {
nums[i] = tmp[j];
}
}
};
时间复杂度O(N*logN),额外空间复杂度O(N)
归并排序的扩展
小和问题和逆序对问题
小和问题
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。例子:[1,3,4,2,5]1左边比1小的数,没有;3左边比3小的数,1;4左边比4小的数,1、3;2左边比2小的数,1;5左边比5小的数,1、3、4、2;所以小和为1+1+3+1+1+3+4+2=16
分析:相求一个数左边有多少个数比它小,就等同于在一个数组中右边有多少个数比它大,就产生多少个小和,比如1右边有四个数比1大则产生4*1的小和,以此类推,但要注意在左侧等于右侧时应该先将右侧值拷贝
代码为:
class Solution {
public:
int reversePairs(vector<int>& nums) {
if(!nums.size()) return 0;
int l=0,r=nums.size()-1;
tmp.reserve(nums.size()+1);
return possess(nums,l,r);
}
int possess(vector<int>&nums,int l,int r)
{
if(r==l) return 0;
int mid=l+((r-l)>>1);
return possess(nums,l,mid)+possess(nums,mid+1,r)+merge(nums,l,mid,r);
}
vector<int>tmp;
int merge(vector<int>&nums,int l,int mid,int r)
{
int p1=l;
int p2=mid+1;
int res=0,i=0;
while(p1<=mid&&p2<=r)
{
if(nums[p1]<nums[p2]) tmp[i++]=nums[p1++],res+=(r-p2+1)*nums[p1];
else tmp[i++]=nums[p2++];
}
while(p1<=mid) tmp[i++]=nums[p1++];
while(p2<=r) tmp[i++]=nums[p2++];
int j = 0;
for (i = l,j; i <= r; ++i, ++j) {
nums[i] = tmp[j];
}
return res;
}
};
逆序对问题
在一个数组中,左边的数如果比右边的数大,则折两个数构成一个逆序对,请打印所有逆序对。
题目链接:逆序对数量
代码为:
class Solution {
public:
int reversePairs(vector<int>& nums) {
if(!nums.size()) return 0;
int l=0,r=nums.size()-1;
tmp.reserve(nums.size()+1);
return possess(nums,l,r);
}
int possess(vector<int>&nums,int l,int r)
{
if(r==l) return 0;
int mid=l+((r-l)>>1);
return possess(nums,l,mid)+possess(nums,mid+1,r)+merge(nums,l,mid,r);
}
vector<int>tmp;
int merge(vector<int>&nums,int l,int mid,int r)
{
int p1=l;
int p2=mid+1;
int res=0,i=0;
while(p1<=mid&&p2<=r)
{
if(nums[p1]>nums[p2]) tmp[i++]=nums[p1++],res+=(r-p2+1);
else tmp[i++]=nums[p2++];
}
while(p1<=mid) tmp[i++]=nums[p1++];
while(p2<=r) tmp[i++]=nums[p2++];
int j = 0;
for (i = l,j; i <= r; ++i, ++j) {
nums[i] = tmp[j];
}
return res;
}
};
三、荷兰国旗问题
问题一
给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(N)
分析:设一个变量表示<=区域的右边界
分两种情况1)当[i]<=num,把[i]和小于等于区域的下一个数做交换,然后小于等于区域向 右扩且i++
2)当[i]>num,则i++
问题二
(荷兰国旗问题)给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(N)
分析:设两个变量,一个表示<区域的右边界,一个表示>区域的左边界
分三种情况1)当[i]<num,把[i]和小于等于区域的下一个数做交换,然后小于等于区域向 右扩且i++
2)当[i]=num,则i++
3)当[i]>num,把[i]和大于区域的前一个做交换,>区域左扩且i不变(当前交换过去的数还没有验,所以i不变)
题目链接:荷兰国旗
代码为:
class Solution {
public:
void sortColors(vector<int>& nums) {
int l=-1,r=nums.size();
int ans=1;
int i=0;
while(i<r)
{
if(nums[i]<ans)
{
int tmp=nums[i];
nums[i]=nums[l+1];
nums[l+1]=tmp;
i++,l++;
}
else if(nums[i]==ans) i++;
else
{
int tmp=nums[i];
nums[i]=nums[r-1];
nums[r-1]=tmp;
r--;
}
}
}
};
四、快速排序
不改进的快速排序
1)把数组范围中的最后一个数作为划分值,然后把数组通过荷兰国旗问题分成三个部分:左侧<划分值、中间==划分值、右侧>划分值
2)对左侧范围和右侧范围,递归执行
分析
1)划分值越靠近两侧,复杂度越高;划分值越靠近中间,复杂度越低
2)可以轻而易举的举出最差的例子,所以不改进的快速排序时间复杂度为O(N^2)
代码为:
随机快速排序(改进的快速排序)
1)在数组范围中,等概率随机选一个数作为划分值,然后把数组通过荷兰国旗问题分成三个部分:左侧<划分值、中间==划分值、右侧>划分值
2)对左侧范围和右侧范围,递归执行
3)时间复杂度为O(N*logN)
代码为:
public class Code06_QuickSort {
public static void quickSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr, int l, int r) {
if (l < r) {
swap(arr, l + (int) (Math.random() * (r - l + 1)), r);
int[] p = partition(arr, l, r);
quickSort(arr, l, p[0] - 1);
quickSort(arr, p[1] + 1, r);
}
}
public static int[] partition(int[] arr, int l, int r) {
int less = l - 1;
int more = r;
while (l < more) {
if (arr[l] < arr[r]) {
swap(arr, ++less, l++);
} else if (arr[l] > arr[r]) {
swap(arr, --more, l);
} else {
l++;
}
}
swap(arr, more, r);
return new int[] { less + 1, more };
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
结束,撒花!!!