算法 | 归并排序 | 小和、逆序对问题

一、小和问题

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个给定数组的小和。
例子:数组为:[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

思路:找每一个数右边比当前数大的个数,(个数 * 当前数) 的累加和就是结果。
这咋和归并排序联系上的呢,仔细想想,在左组和右组merge的时候,会比较数的大小,这时就可以在右组找到比左组当前数大的个数。

public class SmallSum {
    public static void main(String[] args) {
        int[] arr = {1, 3, 4, 2, 5};
        int smallSumValue =  mergeSortAndCalculate(arr, 0, arr.length - 1);
        System.out.println("小和为:" + smallSumValue);
    }
    
    private static int mergeSortAndCalculate(int[] arr, int left, int right) {
        if (left == right) {
            return 0;
        }
        int mid = left + ((right - left) >> 1);
        return mergeSortAndCalculate(arr, left, mid) +
                mergeSortAndCalculate(arr, mid + 1, right) +
                mergeAndCalculate(arr, left, mid, right);
    }

    private static int mergeAndCalculate(int[] arr, int left, int mid, int right) {
        int[] temp = new int[right - left + 1];
        int p1 = left;
        int p2 = mid + 1;
        int i = 0;
        int res = 0;
        while (p1 <= mid && p2 <= right) {
            if (arr[p1] < arr[p2]) {
                //因为右边是排序好的,arr[p2]大,后边的肯定大,只需要求个数就行了
                // 数*右边的个数
                res += arr[p1] * (right - p2 + 1);
                temp[i++] = arr[p1++];
            } else {
                temp[i++] = arr[p2++];
            }
        }
        while (p1 <= mid) {
            temp[i++] = arr[p1++];
        }
        while (p2 <= right) {
            temp[i++] = arr[p2++];
        }
        for (i = 0; i < temp.length; i++) {
            arr[left + i] = temp[i];
        }
        return res;
    }
}

逆序对问题

设有一个数组 [a1, a2, a3,… an],对于数组中任意两个元素ai,aj,若i<j,ai>aj,则说明ai和aj是一对逆序对。求一个给定数组的逆序对个数。

例子:3 5 2 1 0 4 9

所有逆序对是:(3,2),(3,1),(3,0),(5,2),(5,1),(5,0),(5,4),(2,1),(2,0),(1,0)。逆序对个数为10。

思路:
合并的时候,从右往左合并,(此时右组位置 - mid位置) 的累加和 即是逆序对个数。

这又咋和归并排序联系上的呢,仔细想想,在左组和右组merge的时候,会比较数的大小,但是我要找到的是右边更小的,所以可以采用从右往左合并的方式;同时在处理相等的时候,需要先拷贝右组的,这样才能准确找出右组小的个数。

public class InversionCount {
    public static void main(String[] args) {
        int[] array = {2, 4, 3, 1, 5};
        int inversionCount = mergeSortAndCount(array, 0, array.length - 1);
        System.out.println("逆序对的数量为:" + inversionCount);
    }

    private static int mergeSortAndCount(int[] arr, int left, int right) {
        if (left >= right) {
            return 0;
        }
        int mid = left + (right - left) / 2;
        int leftInversions = mergeSortAndCount(arr, left, mid);
        int rightInversions = mergeSortAndCount(arr, mid + 1, right);
        int mergeInversions = mergeAndCount(arr, left, mid, right);
        return leftInversions + rightInversions + mergeInversions;
    }

    private static int mergeAndCount(int[] arr, int left, int mid, int right) {
        int[] temp = new int[right - left + 1];
        int i = left;
        int j = mid + 1;
        int k = 0;
        int inversions = 0;
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
                inversions += mid - i + 1;
            }
        }
        while (i <= mid) {
            temp[k++] = arr[i++];
        }
        while (j <= right) {
            temp[k++] = arr[j++];
        }
        for (int l = 0; l < temp.length; l++) {
            arr[left + l] = temp[l];
        }
        return inversions;
    }
}
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值