B站左程云算法视频笔记02

递归

问题:求数组中的最大值

//arr[L..R]范围上的最大值
public static int process(int[] arr, int L, int R){
    if (L == R){
        return arr[L];
    }//若只有一个数,直接返回
    int mid = L +((R - L) >> 1);//求出中点,防止溢出,如L+R溢出,也可写成mid=L+(R-L)/2
    int leftMax = process(arr, L, mid);
    int rightMax = process(arr, mid + 1, R);
    return Math.max(leftMax, rightMax);
}

举例:

将递归过程理解为一个多叉树,计算所有数节点的过程可理解为利用栈进行了一次后续遍历,每个节点都通过子节点的汇总的信息进行向上返回。栈空间是整棵树的高度。

master公式

T(N) = a* T(N/b)+o(N^{d})

T(N):母问题数据量为N;

a:调用次数

子问题的规模都是N/b

o(Nd):出去子问题的调用外剩下的过程的时间复杂度

例如刚才举的例子T(N) =2*T(N/2)+O(1)

log_a b < d          O(N^{2})

log_a b>d           O(^{}N^{log_a b})

log_a b = d         O(N^{d}*logN)

归并排序

即就是一个简单递归,左边排好序,右边排好序,让整体有序,让整体有序的过程利用了外排序的方法。

public static void process(int[] arr, int L, int R){
    if (L == R){
    return;
    }
    int mid = L + ((R - L) >> 1);
    process(arr, L, mid);//左侧排好序
    process(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++){
        arr[L + i] = help[i];
    }
}

利用master公式: T(N) = 2T(N/2) + O(N)

a=2 b = 2 d =1,  即   log_a b = d   时间复杂度O(N*logN)   空间复杂度O(N)

 选择排序、插入排序和冒泡排序的时间复杂度都是O(N2),在每次比较后只搞定了一个数,比较是独立的,浪费了大量的比较行为。归并排序没有浪费比较行为,比较行为成为一个整体有序的部分,下一步生成更长的整体有序的部分。

小和问题

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和

 暴力解法:遍历,复杂度O(N2)

代码:

public static int smallSum(int[] arr) {
    if(arr == null || arr.length < 2){
        return 0;
    }
    return prcess(arr, 0, arr.length - 1);

}

// arr[L..R]既要做好排序,也要做好小和
public static int process(int[] arr, int l, int r){
    if (l == r){
        return 0;
    }
    int mid = l + (r-l) >> 1;
    return process(arr, l, mid)
            + process(arr, mid + 1, r)
            + merge(arr, l , mid, r);//三块的小和相加才是数组的小和
}

public static int merge(int[] arr, int L, int m, int r){
    int[] help = new help[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;
}

 逆序对(剑指offer 51)

在一个数组中,左边的数如果比右边大,则这两个数构成一个逆序对,请打印所有的逆序对。

 

 荷兰国旗问题

问题1:给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边,要求空间复杂度O(1),时间复杂度O(N)

分析:把一个数组分成俩块,左边小于等于num,右边大于num,不一定有序。

1) arr[i]<=num,把 arr[i]和小于等于区的下一个数做交换,然后小于等于区往右扩一个位置,i++

2)arr[i]>num,i++

i越界,停止

问题1:给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边,要求空间复杂度O(1),时间复杂度O(N)

分析:采用双指针,分成三块,大于区,等于区,小于区

1)[i]<num,[i]和<区下一个做交换,<区右扩,i++

2)[i]==num,i++

3)  [i]>num,[i]和>区前一个做交换,>区左扩大,i原地不变

快排

快排1.0 O(N2)

拿最后一个数做num,将前面的一段(不包括num)分为小于等于区在左边,大于区在右边,然后将num与大于区的第一个数做交换。接着让左侧和右侧重塑这个行为,递归

快排2.0 O(N2)

利用荷兰国旗问题,拿最后一个数做num,前面的区域分为小于num的在左边,之间是等于num的一段,右边是大于num的,然后将num与大于num的第一个区域做交换,接着小于num的和大于num的递归。

相比1.0,搞定了一批等于num,稍快一些

快排3.0 O(NlogN)

在数组的排序时候,选择num做划分时随机选择某位置上的数,不局限于最后一个位置

随机选择划分值,好情况和坏情况是概率事件,有1/N的权重,所以时间复杂度为O(NlogN)

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};
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值