【算法训练】归并排序的应用

一、翻转对 493

在这里插入图片描述

1、分析

首先看到i<j这个条件,就想到了数组分为左右两个部分,然后分别在左右两个部分去找满足条件的翻转对。由于在归并排序的过程中每一步都会访问到不同的左右数组部分,所以考虑在归并排序的框架上进行操作。就在左右两个数组将要合并的时候,利用左右数组i肯定小于j,且左右两个数组都是有序的条件去解题。
具体看代码及注释。

2、代码

这里需要注意的就是在寻找i,j对的时候充分利用左右子数组的有序性,让end不要回退,从而降低时间复杂度!

class Solution:
    def reversePairs(self, nums: List[int]) -> int:
        n = len(nums)
        self.temp = [0]*n
        self.res = 0 #保存最终的结果
        self.sort_helper(nums,0,n-1)
        return self.res
    def sort_helper(self,nums,lo,hi): #归并排序的架子
        if lo==hi: #只有一个元素就不用排了
            return
        mid = lo+(hi-lo)//2
        self.sort_helper(nums,lo,mid) #左边数组排序
        self.sort_helper(nums,mid+1,hi) #右边数组排序
        self.merge(nums,lo,mid,hi) #合并左右有序数组
    def merge(self,nums,lo,mid,hi): #合并左右数组
        for i in range(lo,hi+1): #先把原数组存起来,方便后面排序替换
            self.temp[i] = nums[i]
        end = mid+1
        for i in range(lo,mid+1): #把i限制在左数组中,把end限制在右数组中,这样可以保证i<j这个条件
            while end<=hi and nums[i]>nums[end]*2: #这里一定要注意优化效率,注意到这里的end每次外层循环后是不会回退的,因为我们左右的数组都是有序的,所以当前i满足的end值,对于i+1元素肯定也是满足的,所以不用回退,这里的时间复杂度是O(n)
                end += 1
            self.res += end-mid-1
           
        l,r = lo,mid+1 
        for p in range(lo,hi+1): #这一步就是常规的利用双指针去合并左右有序子数组
            if l==mid+1:
                nums[p] = self.temp[r]
                r += 1
            elif r==hi+1:
                nums[p] = self.temp[l]
                l += 1
            elif self.temp[l]>self.temp[r]:
                nums[p] = self.temp[r]
                r += 1
            else:
                nums[p] = self.temp[l]
                l += 1

JS
JS就一定注意用好全局变量和块级变量!

/**
 * @param {number[]} nums
 * @return {number}
 */
var merge = function(nums,lo,mid,hi){
    for(let i=lo;i<=hi;i++){
        temp[i] = nums[i];
    }
    let end = mid+1;
    for(let i=lo;i<=mid;i++){
        while(end<=hi && nums[i]>2*nums[end]){
            end ++;
        }
        
        res += (end-mid-1);
    }
    let i=lo,j=mid+1;
    for(let p=lo;p<=hi;p++){
        if(i==mid+1){
            nums[p] = temp[j++];
        }else if(j==hi+1){
            nums[p] = temp[i++];
        }else if(temp[i]>temp[j]){
            nums[p] = temp[j++];
        }else{
            nums[p] = temp[i++];
        }
    }
}
var sort_helper = function(nums,lo,hi){
    if(lo==hi){
        return
    }
    let mid = parseInt(lo+(hi-lo)/2);
    sort_helper(nums,lo,mid);
    sort_helper(nums,mid+1,hi);
    merge(nums,lo,mid,hi);
}
var reversePairs = function(nums) {
    let n = nums.length;
    res = 0;
    temp = Array(n).fill(0);
    sort_helper(nums,0,n-1)
    return res;

};

二、区间和的个数 327

在这里插入图片描述

1、分析

首先看到区间和,我们想到前缀和可以很好的用于求区间和。然后也是i<j这个限制,所以自然而然想到归并排序的合并过程中可以得到有序的左右子数组。与上一题很像,就是排序的数组变成了前缀和数组,然后条件变成了一个区间。具体见代码及注释。

2、代码

class Solution:
    def countRangeSum(self, nums: List[int], lower: int, upper: int) -> int:
        n = len(nums)
        self.lower = lower
        self.upper = upper
        presum = [0]*(n+1)
        self.temp = [0]*(n+1)
        self.res = 0
        for i in range(n): #求前缀和,方便后续求区间和!
            presum[i+1] = nums[i]+presum[i]
        self.sort_helper(presum,0,n) #后面其实都是在对前缀和数组进行操作
        return self.res
    def sort_helper(self,nums,lo,hi): #常规归并排序的架子
        if lo==hi:
            return
        mid = lo+(hi-lo)//2
        self.sort_helper(nums,lo,mid)
        self.sort_helper(nums,mid+1,hi)
        self.merge(nums,lo,mid,hi)
    def merge(self,nums,lo,mid,hi):
        for i in range(lo,hi+1):
            self.temp[i] = nums[i]
        start = mid+1
        end=mid+1
        for i in range(lo,mid+1): #关键代码在这里!由于是求一个区间,所以把i限制在右子数组中,去找j的范围,注意到左右子数组都是有序的,所以这里的start和end也是不需要回退的,可以像滑动窗口一样,整体的向右移动!
            while start<=hi and nums[start]-nums[i]<self.lower:
                start += 1
            while end<=hi and nums[end]-nums[i]<=self.upper: #注意到是闭区间,所以结尾这个也要包含进去的,因此让end超过一个
                end += 1
            self.res += end-start
        i,j = lo,mid+1
        for p in range(lo,hi+1): #这里就是常规采用双指针来合并左右有序子数组的过程
            if i==mid+1:
                nums[p] = self.temp[j]
                j += 1
            elif j==hi+1:
                nums[p] = self.temp[i]
                i += 1
            elif self.temp[i]>self.temp[j]:
                nums[p] = self.temp[j]
                j += 1
            else:
                nums[p] = self.temp[i]
                i += 1

JS

/**
 * @param {number[]} nums
 * @param {number} lower
 * @param {number} upper
 * @return {number}
 */

var countRangeSum = function(nums, lower, upper) {
    let n = nums.length;
    let presum = Array(n+1).fill(0);
    let temp = Array(n+1).fill(0);
    let res = 0;
    for(let i=0;i<n;i++){
        presum[i+1] = presum[i]+nums[i];
    }
    var merge = function(nums,lo,mid,hi){
        for(let i=lo;i<=hi;i++){
            temp[i] = nums[i];
        }
        let start=mid+1,end=mid+1;
        for(let i=lo;i<=mid;i++){
            while(start<=hi && nums[start]-nums[i]<lower){
                start ++;
            }
            while(end<=hi && nums[end]-nums[i]<=upper){
                end ++;
            }
            res += end-start;
        }
        let i=lo,j=mid+1;
        for(let p=lo;p<=hi;p++){
            if(i==mid+1){
                nums[p] = temp[j++];
            }else if(j==hi+1){
                nums[p] = temp[i++];
            }else if(temp[i]>temp[j]){
                nums[p] = temp[j++];
            }else{
                nums[p] = temp[i++];
            }
        }
    };
    var sort_helper = function(nums,lo,hi){
        if(lo==hi){
            return
        }
        let mid = parseInt(lo+(hi-lo)/2);
        sort_helper(nums,lo,mid);
        sort_helper(nums,mid+1,hi);
        merge(nums,lo,mid,hi);
    };
    sort_helper(presum,0,n);
    return res;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值