剑指51刷题记录-逆序数

# 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
# 输入: [7,5,6,4]
# 输出: 5
# 限制:
# 0 <= 数组长度 <= 50000
# 1、显然,可以使用O(n^2)复杂度的暴力解法
# 但是50000的数据大小,显然是不行的
# 2、逆序对问题的求解,和归并息息相关,归并的主要思想是将两个数组分开,
# 将问题转换成两个有序序列的合并问题,合并过程是将左右序列更小那个放入新序列
# 当右边最小数比左边最小数更小时,意味着左边序列的所有数,可以和当前右边最小数形成逆序对

首先看看基础的归并

def mergeSort(arr):
    if len(arr) < 2:
        return arr
    # mid = math.floor(len(arr) / 2)
    mid = len(arr) // 2
    # 注意裁剪时,mid不需要+1 arr[:mid]是arr[mid]之前的,
    # arr[mid:]是包括arr[mid]和它之后
    l, r = arr[0: mid], arr[mid:]
    return merge(mergeSort(l), mergeSort(r))


def merge1(l, r):
    res = []
    while l and r:
        if l[0] <= r[0]:
            # 此处不能使用pop,因为应当删去list第一个元素
            # 使用deque传参会变成list,因此用裁剪解决
            res.append(l[0])
            l = l[1:]
        else:
            res.append(r[0])
            r = r[1:]
    if l:
        res += l
    if r:
        res += r
    return res

解题

# 以下是我的写法,虽然的确能跑,但50k数据量还是超时了
# (大概是裁剪有严重效率问题,裁剪一次复制一次,hhh)
class Solution:
    count = 0  # 注意类的全局变量需在此处定义

    def reversePairs(self, nums: List[int]) -> int:
        self.count = 0
        self.mergeSort(nums)
        return self.count

    def mergeSort(self, arr):
        if len(arr) < 2:
            return arr
        mid = len(arr) // 2
        l, r = arr[:mid], arr[mid:]
        return self.merge(self.mergeSort(l), self.mergeSort(r))

    def merge(self, l, r):
        res = []
        while l and r:
            if l[0] <= r[0]:
                res.append(l[0])
                l = l[1:]
            else:
                res.append(r[0])
                r = r[1:]
        if l:
            res += l
        elif r:
            res += r
        return res

这玩意跑是能跑,但是跑50000数据量超时了,因为裁剪去掉list首元素,py3裁剪操作是复制操作,于是把merge函数改成使用下标操作

    def merge(self, l, r):
        res = []
        i, j = 0, 0
        l_l, r_l = len(l), len(r)
        while i < l_l and j < r_l:
            if l[i] <= r[j]:
                res.append(l[i])
                i += 1
            else:
                self.count += l_l - i  # 长度减下标,不需要+1
                res.append(r[j])
                j += 1
        if i < l_l:
            res += l[i:]
        elif j < r_l:
            res += r[j:]
        return res

顺利通过

对比一下大佬的写法

# 大佬的写法
class Solution1:
    def reversePairs(self, nums: List[int]) -> int:
        # 与之前的写法不同的是,这里l r是边界的上下标
        def mergeSort(l: int, r: int) -> int:
            if l >= r:
                return 0
            mid = (l + r) // 2
            res = mergeSort(l, mid) + mergeSort(mid + 1, r)
            i, j = l, mid + 1
            tmp[l: r + 1] = nums[l: r + 1]  # 重复利用tmp进行复制
            for k in range(l, r + 1):  # 因为nums[r]也要参与运算,所以此处r+1
                if i == mid + 1:  # k表示的是排序后数组的下标,通过tmp比较,直接复制回nums
                    # 左边已遍历完
                    nums[k] = tmp[j]
                    j += 1
                elif j == r + 1 or tmp[i] <= tmp[j]:
                    # 右边已遍历完或左边比较小
                    nums[k] = tmp[i]
                    i += 1
                else:
                    # 右边比较大
                    nums[k] = tmp[j]
                    j += 1
                    res += mid - i + 1  # 下标相减,是需要+1的
            return res

        tmp = [0] * len(nums)
        return mergeSort(0, len(nums) - 1)

大佬写法重复利用tmp,似乎是更优的写法,但是提交后看统计是我的写法更少内存和时间,可能是我拼接剩下的list是直接+=,大佬一个个复制,所以更好

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值