写在前面
- 最近觉得leetcode好多题解看起来很费劲,于是想想该恶补恶补算法了,很多大佬推荐左神左程云的算法课,所以有了这篇笔记。
- 今天是左神迷妹的一天。
- 有需要学习资源啥的小伙伴可以评论私信都行。
01 | 冒泡排序
- 时间复杂度O(n*n),空间复杂度O(1)
public void bubbleSort(int[] a){
if(a == null || a.length < 2){
return;
}
for(int i = a.length-1; i > 0; i--){
for(int j = 0; j < i; j++){
if(a[j] > a[j+1]){
swap(a, j, j+1);
}
}
}
}
public void swap(int[] a, int i, int j){
a[i] = a[i] ^ a[j];
a[j] = a[i] ^ a[j];
a[i] = a[i] ^ a[j];
}
02 | 选择排序
- 时间复杂度O(n*n),空间复杂度O(1)
public void selectSort(int[] a){
if(a == null || a.length < 2){
return;
}
for(int i = 0; i < a.length-1; i++){
int minIndex = i;
for(int j = i+1; j < a.length; j++){
minIndex = a[j] < a[minIndex] ? j : minIndex;
}
if( minIndex != i ){
swap(a, i, minIndex);
}
}
}
03 | 插入排序(整牌)
- 时间复杂度:最坏情况O(n*n),最好情况O(n),空间复杂度O(1)
public void insertSort(int[] a){
if(a == null || a.length < 2){
return;
}
for(int i = 1; i < a.length; i++){
for(int j = i-1; j >= 0 && a[j] > a[j+1]; j-- )
swap(a, j, j+1);
}
}
04 | 对数器
- 用于验证你写的算法是否正确:
- 有一个你想要测的方法a,
- 实现一个绝对正确但是复杂度不好的方法b
- 实现一个随机样本产生器
- 实现比对的方法
- 把方法a和方法b比对很多次来验证方法a是否正确。
- 如果有一个样本使得比对出错,打印样本分析是哪个方法出 错
- 当样本数量很多时比对测试依然正确,可以确定方法a已经正确
- 笔试需准备模板,如,数组、二叉树等对数器的模板
- 堆、排序等也有模板
package class_01;
import java.util.Arrays;
public class BubbleSort {
//1.你想要测的方法a
public static void bubbleSort(int[] arr){
if (arr == null || arr.length < 2){
return;
}
for (int i = arr.length-1; i > 0; i--){
for (int j = 0; j < i; j++ ){
if (arr[j] > arr[j+1]){
swap(arr, j, j+1);
}
}
}
}
public static void swap(int[] arr, int i, int j){
if(i == j)
return; //防止&a,&b指向同一个地址;那样结果会错误,导致结果为0。
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
//2.绝对正确但是复杂度不好的算法
public static void comparator(int[] arr){
Arrays.sort(arr);
}
//3.实现一个随机样本产生器
public static int[] generateRandomArray(int maxSize, int maxValue){
int[] arr = new int[(int)((maxSize + 1) * Math.random())];
for(int i = 0; i < arr.length; i++){
arr[i] = (int)((maxValue + 1) * Math.random()) - (int)(maxValue * Math.random());
}
return arr;
}
//4.实现比对的方法
public static boolean isEqual(int[] arr1, int[] arr2){
if((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)){
return false;
}
if(arr1 == null && arr2 == null){
return true;
}
if(arr1.length != arr2.length){
return false;
}
for(int i = 0; i < arr1.length; i++){
if(arr1[i] != arr2[i]){
return false;
}
}
return true;
}
//复制数组
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++){
int[] arr1 = generateRandomArray(maxSize,maxValue);
int[] arr2 = copyArray(arr1);
bubbleSort(arr1);
comparator(arr2);
if (!isEqual(arr1,arr2)){
succeed = false;
printArray(arr1);
printArray(arr2);
break;
}
}
System.out.println( succeed ? "Nice!" : "辣鸡");
}
}
05 | 递归
-
递归:系统在帮你压栈,系统保护了函数的所有东西,等递归回来的时候,将数据原样还原。具体可参考:递归图解
-
递归复杂度分析:
-
master公式的使用:前提:子过程规模需一致!!
T(N) = a*T(N/b) + O(N^d)
其中:N为父问题的样本量,N/b为子过程样本量,a为子过程发生次数,O(N^d) 为剩余过程的时间复杂度
-
复杂度计算方法:
- log(b,a) > d : 复杂度为O(N^log(b,a))
- log(b,a) = d : 复杂度为O(N^d * logN)
- log(b,a) < d : 复杂度为O(N^d)
-
-
举个栗子:给定数组,求出最大值
-
递归求法:把一个数组拆分成两个部分,左部分和右部分,然后分别对每个部分求最大值,最后两个部分进行比较。
public static int getmax(int[] arr,int left,int right){ if (left==right) { return arr[left]; } int mid=left+(right-left)>>2; int leftmax=getmax(arr, left, mid); int rightmax=getmax(arr, mid+1, right); return Math.max(leftmax, rightmax); }
-
利用master公式对其进行复杂度分析:
两个同等规模的子过程(两次递归)===》a=2
将过程分为两个子过程 ====》b=2
剩余过程只有比较 =====》 d=0
因此:T(N) = 2*T(N/2) + O(1)
复杂度为O(logN)
-
06 | 归并排序
- 该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案”修补”在一起,即分而治之)。
public class MergeSort {
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 left, int mid, int right) {
int[] res = new int[right - left + 1];
int i = 0;
//双指针
int p1 = left, p2 = mid + 1;
while (p1 <= mid && p2 <= right) {
res[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
res[i++] = arr[p1++];
}
while (p2 <= right) {
res[i++] = arr[p2++];
}
for (int j = 0; j < res.length; j++) {
arr[left + j] = res[j];
}
}
public static void main(String[] args) {
…………
mergeSort(arr1);
…………
}
}
- 复杂度分析同递归,T(N) = 2*T(N/2) + O(N) = O(NlogN)
07 | 小和问题
-
在一个数组中,每一个元素左边比当前元素值小的元素值累加起来,叫做这个数组的小和
[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思路:归并方法将将时间复杂度降到O(nlogn),这道题换个角度来想,题目要求的是每个数左边有哪些数比自己小,其实不就是右边有多少个数比自己大,那么产生的小和就是当前值乘以多少个
1. 先将其分组为如下所示
-
过程如下:
1和3对比,1比3小,res+1=1,同时将1放入help数组;
1、3只剩3,将3放入help数组;
1、3与4对比,1比4小,res+1=2, 同时指针移动到3;
3与4对比,3比4小,res+3=5;此时两组对比完成,将4放入help数组;
目前help数组为 [1,3,4]
同理,右半部分比较完成时,help数组为[1,3,4,2,5]; res=7;
现在,将左半部分与右半部分进行比较(采用双指针);
p1指针指向数字1,p2指针指向数字2,发现1<2,于是res+1*2=9,
接着p1前进指向数字3;经过比对,p1>p2,所以p2前进指向数字5,此时p1=3<p2=5,因此res+3=12;
p1继续前进,指向数字4,此时p1=4<p2=5,因此res+4=16,至此比对结束,小数和为16。 -
关于下列代码,对
res += arr[p1] < arr[p2] ? (right-p2+1) * arr[p1] : 0;
的理解:
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 left, int right){ if(left == right){ return 0; } int mid = left + ((right - left) >> 1); return mergeSort(arr, left, mid) + mergeSort(arr, mid+1, right) + merge(arr,left,mid,right); } public static int merge(int[] arr, int left, int middle, int right){ int[] help = new int[right - left + 1]; int i = 0; int p1 = left, p2 = middle + 1; int res = 0; while (p1 <= middle && p2 <= right){ res += arr[p1] < arr[p2] ? (right-p2+1) * arr[p1] : 0; help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; } while (p1 <= middle){ help[i++] = arr[p1++]; } while (p2 <= right){ help[i++] = arr[p2++]; } for (i = 0; i < help.length; i++) { arr[left+i] = help[i]; } return res; }
08 | 逆序对问题
- 在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,请打印所有逆序 对。
- 举个栗子:
[4,5,8,3,2,1]
全部逆序对:(3,2)(3,1)(2,1)(8,3)(8,2)(8,1)(5,3)(5,2)(5,1)(4,3)(4,2)(4,1)
package class_01;
public class Inverse {
public static void InversePair(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); //T(N/2)
mergeSort(arr,mid+1,R); //T(N/2)
merge(arr,L,mid,R);
}
public static void merge(int[] arr,int L,int M,int R){
int[] temp = new int[R-L+1];
int i = 0;
int p1 = L;
int p2 = M+1;
while(p1<=M && p2<=R){
for(int j=0;j+p2<=R;j++){
System.out.print(arr[p1] > arr[p2] ? "("+arr[p1]+","+arr[p2+j]+")" : "");
}
temp[i++] = arr[p1] > arr[p2] ? arr[p1++] : arr[p2++];
}
//只有一个越界
while(p1 <= M){
temp[i++] = arr[p1++];
}
while(p2 <= R){
temp[i++] = arr[p2++];
}
for(i =0;i<temp.length;i++){
arr[L+i] = temp[i];
}
}
public static void main(String[] args) {
int[] arr = {4,5,8,3,2,1};
InversePair(arr);
}
}
- 初级班第一节课完成。