从递归开始说起
在一个数组中查找最大值,先在left侧找max,在right侧找max,更大者就是整体的max;
使用递归就是使用系统栈,系统栈会自动帮我们把之前所有的信息压入栈中记录下来,从实际代码调试中看一看
public static void main(String[] args) {
int[] nums = {3,6,1,21,7,4};
System.out.println(getMax(nums, 0, nums.length-1));
}
public static int getMax(int[] nums,int left,int right){
if(left==right)
return nums[left];
int middle = (right-left)/2+left;
int maxL = getMax(nums, left, middle);
int maxR = getMax(nums, middle+1, right);
return maxL>maxR?maxL:maxR;
}
当递归到left==right,即结束条件的时候,也就是left指针和right指针汇合了到一个点了,返回该值,就是一个数范围内的最大值;此时是getMax(nums,0,1)这个函数中的第18行执行完毕,返回maxL=3;
继续执行的是getMax(nums,0,1)这个函数中的第19行即maxR=getMax(nums,1,1),别忘了之前入栈的时候所有信息都被记录到了系统栈中~,返回maxR=6
执行完返回getMax(nums,0,1)这个函数的整体max=6;栈中最顶上的函数推栈,但是别忘了,此时栈顶函数是getMax(nums,0,1),是通过getMax(nums,0,2)调用的,其结果执行完返回到getMax(nums,0,2)中的maxL=6,栈顶元素退栈
然后执行getMax(nums,0,2)中的maxR,即getMax(nums,2,2),返回maxR=1,getMax(nums,0,2)的整体max就是6~getMax(nums,0,2)执行完推栈,此时仍需记得,getMax(nums,0,2)仅仅是getMax(nums,0,5)左侧部分的max,即getMax(nums,0,5)函数中maxL=6~
此时完成的就是left侧最大值,然后继续进行的就是getMax(nums,0,5)这个函数求右侧max的过程~maxR=getMax(nums,3,5),再走一遍递归最后可以求得getMax(nums,0,5)的maxR=21,然后整体max就是21,所有函数都执行完毕,推栈结束。
递归函数的时间复杂度公式
- N 样本量
- b 样本量被切分为几个子部分样本量,考虑的是规模,不具体到常数项,差几个无所谓
- a 发生几次子过程
-
O(Nd)
O
(
N
d
)
除去子过程之外的时间复杂度
T(N)=aT(Nb)+O(Nd) T ( N ) = a T ( N b ) + O ( N d )
本题的时间复杂度就是 T(N)=2T(N2)+O(1) T ( N ) = 2 T ( N 2 ) + O ( 1 ) - logba>d→O(Nlogba) l o g b a > d → O ( N l o g b a ) 比如 T(N)=2T(N2)+O(1) T ( N ) = 2 T ( N 2 ) + O ( 1 )
- logba=d→O(NdlogN) l o g b a = d → O ( N d l o g N ) 比如 T(N)=2T(N2)+O(N) T ( N ) = 2 T ( N 2 ) + O ( N )
归并排序
先把左侧排好序,再把右侧排好序,整体使用外排排好序列
归并排序代码
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
mergeSort(arr, 0, arr.length - 1);
}
public static void mergeSort(int[] arr, int l, int r) {
if (l == r) {
return;
}
int mid = l + ((r - l) >> 1);
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
public static void merge(int[] arr, int l, int m, int r) {
int[] help = new int[r - l + 1];
int i = 0;
int p1 = l;
int p2 = m + 1;
while (p1 <= m && p2 <= r) {
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= m) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
//l+i,不然排右半部分的时候就崩了
arr[l + i] = help[i];
}
}
排序部分在归并的过程中,先把(0,1)的元素排序+归并,然后把(2,2)的元素排序+归并,然后在(0,2)范围内整体排序+归并,此时完成左半部分的排序,然后是右半部分(3,5)归并排序,在(3,4)归并排序,然后是(5,5)归并排序,然后把有半部分整体排好序+归并。最后是整体大范围(0,5)范围内归并排序了~
归并排序时间复杂度 O(NlogN) O ( N l o g N )
除去子过程外要遍历数组,因此是O(N)
求数组小和
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。比如[1,3,4,2,5]的小和就是
1的左边比1小的数,没有 0;
3的左边比3小的数,1和为1;
4的左边比4小的数,1+3=4;
2的左边比2小的数,1;
5的左边比5小的数;1+2+3+4=10
总共小和为16
换个思路:
cur这个数右边有多少个比cur大,就加上几个cur;
1的右边有4个比1大,即+4;
3的右边有2个比3大,即+(3*2)
4的右边有1个比4大,即+4
2的右边有1个比2大,即+2
5的右边没有+0;
整体小和为4+6+4+2=16
在归并的过程中,先看1怎么算得,最开始就是1,然后(1,3)排序合并,然后(1,3)和4排序合并,然后还有(1,3,4)和(2,5)合并,这样就把1算完毕;然后看3怎么算得,(1,3)和4排序合并,然后还有(1,3,4)和(2,5)合并,这样3就算完毕;然后看4怎么算,(1,3,4)和(2,5)合并;
然后看2怎么算,(2,5),最后到5结束。。。。
这个过程其实和归并排序的那个归并过程是一样的,只不过在归并的时候算了个累加;
public static int smallSum(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return mergeSort(arr, 0, arr.length - 1);
}
public static int mergeSort(int[] arr, int l, int r) {
if (l == r) {
return 0;
}
int mid = l + ((r - l) >> 1);
return mergeSort(arr, l, mid) + mergeSort(arr, mid + 1, r) + merge(arr, l, mid, r);
}
public static int merge(int[] arr, int l, int m, int r) {
int[] help = new int[r - l + 1];
int i = 0;
int p1 = l;
int p2 = m + 1;
int res = 0;
while (p1 <= m && p2 <= r) {
res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0;
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= m) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[l + i] = help[i];
}
return res;
}