算法二——左程云

题目一、归并排序

1)整体就是一个简单递归,左边排好序、右边排好序、让其整体有序
2)让其整体有序的过程里用了外排序方法
3)利用master公式来求解时间复杂度
4)归并排序的实质
时间复杂度O(N logN),额外空间复杂度O(N)

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

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];
    }
}

外排序:两个指针,比较后的结果,拷贝到一个外部的数组中,再拷贝回原数组中。
在这里插入图片描述

leftMax, P(0, 4)
leftMax, P(0, 2)
leftMax, P(0, 1)
leftMax, P(0, 0)
-----返回当前值:9
rightMax, P(1, 1)
-----返回当前值:8
-----开始merge:[0]-[1]
9 8 7 6 5 4 3 2 1
help数组是:
8 9
rightMax, P(2, 2)
-----返回当前值:7
-----开始merge:[0]-[2]
8 9 7 6 5 4 3 2 1
help数组是:
7 8 9
rightMax, P(3, 4)
leftMax, P(3, 3)
-----返回当前值:6
rightMax, P(4, 4)
-----返回当前值:5
-----开始merge:[3]-[4]
7 8 9 6 5 4 3 2 1
help数组是:
5 6
-----开始merge:[0]-[4]
7 8 9 5 6 4 3 2 1
help数组是:
5 6 7 8 9
rightMax, P(5, 8)
leftMax, P(5, 6)
leftMax, P(5, 5)
-----返回当前值:4
rightMax, P(6, 6)
-----返回当前值:3
-----开始merge:[5]-[6]
5 6 7 8 9 4 3 2 1
help数组是:
3 4
rightMax, P(7, 8)
leftMax, P(7, 7)
-----返回当前值:2
rightMax, P(8, 8)
-----返回当前值:1
-----开始merge:[7]-[8]
5 6 7 8 9 3 4 2 1
help数组是:
1 2
-----开始merge:[5]-[8]
5 6 7 8 9 3 4 1 2
help数组是:
1 2 3 4
-----开始merge:[0]-[8]
5 6 7 8 9 1 2 3 4
help数组是:
1 2 3 4 5 6 7 8 9

O(N2):浪费了大量的比较行为。每一圈仅比较得出一个结果。
O(NlogN):没有浪费比较。merge了比较结果,放入数组中,下一次更大范围的比较基于了上一次的merge结果。

题目二、归并排序的扩展

小和问题和逆序对问题

  • 小和问题

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
[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
3右边比3大的数:2个3
4右边比4大的数:1个4
2右边比2大的数:1个2
5右边比5大的数:0个5
4 * 1 + 2 * 3 + 1 * 4 + 1 * 2 = 16

在这里插入图片描述
[1,3,4,2,5]
以1举例
1和3组成[1,3],找到3比1大
[1,3]和4组成[1,3,4],找到4比1大
[1,3,4]和[2,5]组成[1,2,3,4,5],找到2和5比1大,注意因为2比1大,所以直接确定了有2个数比1大,而不用1分别和2、5比较。

是要排序的!

这个merge和上面的那个经典merge有一点不一样
就是当左右指针的数字大小相同时,移动的是右边的指针。
因为要计算右边的大数字的个数

public static int smallSum(int[] arr) {
    if (arr == null || arr.length < 2) {
        return 0;
    }
    return process(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 - 1) >> 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 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;
}

题目六、荷兰国旗问题

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

在这里插入图片描述
总结:
在这里插入图片描述
当[i]<=num,[i]和‘<=区的下一个数’作交换,’<=区’外扩1
当[i]>num,i++
直到i++越界

在这里插入图片描述
当[i]<num,[i]和‘小于区间的下一个’交换,小于区右扩1,i++
当[i]=num,i++
当[i]>num,[i]和’大于区间的前一个’交换,大于区左扩1,i不动
直到当i遇见大于区

快排1.0版本
在这里插入图片描述
num是数组中最后一个数
排好后,num与大于区的第一个数作交换,且这个位置就是num的最终位置
然后两个区域分别重复以上行为

快排2.0版本
在这里插入图片描述
num与大于区第一个数交换,num的位置确定

快排1.0和2.0的时间复杂度都是O(N2)
最坏情况:12345(6)
最好情况:num是中间值

快排3.0版本
随机选一个数,交换,放在最右端,作为num
因为是概率事件,经过复杂的证明过程,时间复杂度O(NlogN)

快排的空间复杂度O(N)

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

    // arr[l..r]排好序
    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);// >区
        }
    }

    // 这是一个处理arr[l..r】的函数
    // 默认以arr[r]做划分,arr[r]->p   <p   ==p   >p
    // 返回等于区域(左边界,右边界),所以返回一个长度为2的数组res,res[0] res[1]
    public static int[] partition(int[] arr, int L, int R) {
        int less = L - 1;// <区右边界
        int more = R;// >区左边界
        while (L < more) {// L表示当前数的位置 arr[R]  -> 划分值
            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};
    }

partition
一定返回一个长度为2的数组,用于描述’=num区’的左右边界

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值