算法笔记15:计算右侧小于当前元素的个数

201 阅读1分钟

315. 计算右侧小于当前元素的个数

题目看起来很简单,但一试就知道 O(N^2) 的暴力解法是会超时的。所以我想到了肯定是需要降低复杂度到 O(N log N) ,但苦于猪脑过载实在想不出来什么方法。想到可能会使用二叉搜索树,但是感觉是需要维持平衡的,遂放弃。

看了这篇答案之后理解了一下才明白其中的奥秘。这个方法实际上是通过在归并排序的过程中,找到右侧数组中比左侧数组中元素小的个数来计算,因为理想状态下右侧的所有元素都应该比左侧大才对。

现在看来想要降低复杂度也是有几个思路,但大多都是二分、归并这种方法。这道题目之所以是困难,主要也是在于即便想到二分,可能也没有头绪发现这个规律吧。

代码如下:

const countSmaller = nums => {
    const newArr = nums.map((n, i) => ([n, i]));
    const result = Array(nums.length).fill(0);
    
    mergeSortAndCount(newArr, 0, nums.length - 1, result);
    
    return result;
};

const mergeSortAndCount = (nums, start, end, result) => {
    if (start >= end) return;
    
    const mid = Math.floor((start + end) / 2);
    mergeSortAndCount(nums, start, mid, result);
    mergeSortAndCount(nums, mid + 1, end, result);
    
    let left = start;
    let right = mid + 1;
    let numRightLessLeft = 0;
    const merged = [];
    
    while(left < mid + 1 && right <= end) {
        if (nums[left][0] > nums[right][0]) {
            numRightLessLeft++;
            merged.push(nums[right]);
            right++;
        } else {
            merged.push(nums[left]);
            result[nums[left][1]] += numRightLessLeft;
            left++;
        }
    }
    
    while (left < mid + 1) {
        merged.push(nums[left]);
        result[nums[left][1]] += numRightLessLeft;
        left++;
    }
    while (right <= end) {
        merged.push(nums[right]);
        right++;
    }
    
    merged.forEach((tuple, index) => {
        nums[start + index] = tuple;
    })
}