【重点!!!】【有序数组+二分搜索】315.计算右侧小于当前元素的个数

本文介绍了两种利用归并排序解决数组中逆序对问题的方法,分别是构造索引类和索引数组,详细展示了代码实现和计数过程。

题目
方法1:有序数组+二分搜索
方法2:归并排序
类似另一个经典题目:数组中的逆序对

Python

有序数组+二分搜索

最佳答案:参考
bisect库函数介绍:
在 Python 中,bisect_left 是 bisect 模块中的一个函数,用于在已排序的序列中快速找到元素的插入位置,以保持序列的有序性。

# 示例 1:基本用法
aa = [1, 3, 4, 4]
xx = 4
pos1 = bisect.bisect_left(aa, xx)
pos2 = bisect.bisect_right(aa, xx)

print(pos1)  # 输出 2
print(pos2)  # 输出 4
# 版本1:使用库函数bisect.bisect_left
class Solution:
    def countSmaller(self, nums: List[int]) -> List[int]:
        sort_list = []
        n = len(nums)
        res = [0] * n
        for i in range(n-1, -1, -1):
            idx = bisect.bisect_left(sort_list, nums[i])
            res[i] = idx
            sort_list.insert(idx, nums[i])
        
        return res
 
# 版本2:自己写二分搜索
class Solution:
    def countSmaller(self, nums: List[int]) -> List[int]:
        sort_list = []
        n = len(nums)
        res = [0] * n
        for i in range(n-1, -1, -1):
            # idx = bisect.bisect_left(sort_list, nums[i])
            idx = self.my_bisect_left(sort_list, nums[i])
            res[i] = idx
            sort_list.insert(idx, nums[i])
        
        return res

    def my_bisect_left(self, sort_list, target):
        l, r = 0, len(sort_list) - 1
        while l <= r:
            mid = (l + r) // 2
            if sort_list[mid] < target:
                l = mid + 1
            else:
                r = mid - 1
        return l

Java

法1:归并排序—构造索引类

这种写法更容易理解。

class Solution {
    List<Integer> ans = new ArrayList<>();
    int[] count;     // 记录题目中所求的count[i]
    Pair[] tmp;      // 临时数组, 存储一次归并过程中排序好的【两段有序的元素】

    public List<Integer> countSmaller(int[] nums) {
        int n = nums.length;
        Pair[] pairArray = new Pair[n];
        count = new int[n];
        tmp = new Pair[n];
        for (int i = 0; i < n; i++) {
            pairArray[i] = new Pair(nums[i], i);
        }
        mergeSort(pairArray, 0, n - 1);
        for (int i = 0; i < n; i++) {
            ans.add(count[i]);
        }
        return ans;
    }

    private void mergeSort(Pair[] pairArray, int start, int end) {
        if (start < end) {
            int mid = start + (end - start) / 2;
            mergeSort(pairArray, start, mid);  
            mergeSort(pairArray, mid + 1, end); 
            merge(pairArray, start, mid, end);  // 再将两个有序数组合并,只不过这里要承接统计count[i]的功能
        }
    }

    // 注意: 降序merge!!!
    private void merge(Pair[] pairArray, int start, int mid, int end) {
        int left = start;
        int right = mid + 1;
        int cur = left;

        for (int i = start; i <= end; ++i) {
            tmp[i] = pairArray[i];
        }

        while (left <= mid || right <= end) {
            if (left > mid) { // 这种情况无需再重复赋值, 直接break
                break;
            } else if (right > end) {
                pairArray[cur] = tmp[left];
                ++left;
            } else if (tmp[left].val > tmp[right].val) {
                count[tmp[left].idx] += end - right + 1; // 右半部分tmp[right, end]都 < nums[left]
                pairArray[cur] = tmp[left];
                ++left;
            } else { // tmp[left].val <= tmp[right].val
                pairArray[cur] = tmp[right];
                ++right;
            }
            ++cur;
        }
    }

    class Pair {
        public int val;
        public int idx;
        public Pair(int val, int idx) {
            this.val = val;
            this.idx = idx;
        }
    }
}

法2:归并排序—索引数组

必须掌握的基础算法!!!

class Solution {

    List<Integer> ans = new ArrayList<>();
    int[] index;    // 原数组的索引数组,存储着原数组中每个元素对应的下标
    int[] count;    // 记录题目中所求的count[i]
    int[] tmp;      // 临时数组, 存储一次归并过程中排序好的元素
    int[] tmpIndex; // 临时索引数组, 存储一次归并过程中排序好的元素对应原始索引

    //入口
    public List<Integer> countSmaller(int[] nums) {
        int n = nums.length;
        index = new int[n];
        count = new int[n];
        tmp = new int[n];
        tmpIndex = new int[n];
        for (int i = 0; i < n; i++) {
            index[i] = i;
        }
        mergeSort(nums, 0, n - 1);
        for (int i = 0; i < n; i++) {
            ans.add(count[i]);
        }
        return ans;
    }

    private void mergeSort(int[] nums, int start, int end) {
        if (start < end) {
            int mid = start + (end - start) / 2;
            mergeSort(nums, start, mid);  
            mergeSort(nums, mid + 1, end); 
            merge(nums, start, mid, end);  // 再将两个有序数组合并,只不过这里要承接统计count[i]的功能
        }
    }

    // 注意: 降序merge!!!
    private void merge(int[] nums, int start, int mid, int end) {
        int left = start;
        int right = mid + 1;
        int cur = left;

        while (left <= mid && right <= end) {
            if (nums[left] > nums[right]) {
                count[index[left]] += end - right + 1; // 右半部分小于nums[left]元素的数目
                tmpIndex[cur] = index[left];           // 记录元素位置的改变
                tmp[cur] = nums[left];
                ++left;
            } else {
                tmp[cur] = nums[right];
                tmpIndex[cur] = index[right];
                ++right;
            }
            ++cur;
        }
        while (left <= mid) {
            tmp[cur] = nums[left];
            tmpIndex[cur] = index[left];
            ++left;
            ++cur;
        }
        while (right <= end) {
            tmp[cur] = nums[right];
            tmpIndex[cur] = index[right];
            ++right;
            ++cur;
        }
        for (int i = start; i <= end; i++) {
            nums[i] = tmp[i];
            index[i] = tmpIndex[i];
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值