【归并排序】Leetcode 82 合并两个有序数组

leetcode 82

归并排序示意图:
在这里插入图片描述


#自定义merge函数
def merge(L,R):
    n = len(L) + len(R)
    L.append(float("inf"))
    R.append(float("inf"))
    i = 0
    j = 0
    A = []
    for k in range(0,n):
        if L[i]<=R[j]:
            A.append(L[i])
            i = i+1
        else:
            A.append(R[j])
            j = j+1
    return A

merge([1,5,6],[2,3])    

#自定义merge_sort函数
def merge_sort(A):
    l = len(A)
    if l<=1:
        return A
    else:
        mid = l//2
        print(mid)
        left = merge_sort(A[0:mid])
        right = merge_sort(A[mid:])
        return merge(left,right)
#测试merge_sort函数
merge_sort([3,2,1,0,5,8,7,2,-5,9,6,11])

力扣88题:
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3][2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。

归并排序利用了递归的思想,88题的题解代码如下,不算典型的归并排序,但是也运用了类似的归并排序思想,实际上是一种倒序排列的方法

class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
        """
        Do not return anything, modify nums1 in-place instead.
        """
        i, j = m-1, n-1
        curr = m+n-1
        while curr >= 0:
            num1 = nums1[i] if i >= 0 else float('-inf')
            num2 = nums2[j] if j >= 0 else float('-inf')
            if num1 >= num2:
                nums1[curr] = num1
                i -= 1
            else:
                nums1[curr] = num2
                j -= 1
            curr -= 1
        return

剑指offer 51剑指offer51

归并思路:
「归并排序」与「逆序对」是息息相关的。归并排序体现了 “分而治之” 的算法思想,具体为:

分: 不断将数组从中点位置划分开(即二分法),将整个数组的排序问题转化为子数组的排序问题;
治: 划分到子数组长度为 1 时,开始向上合并,不断将 较短排序数组 合并为 较长排序数组,直至合并至原数组时完成排序;

在这里插入图片描述
合并阶段 本质上是 合并两个排序数组 的过程,而每当遇到 左子数组当前元素 > 右子数组当前元素 时,意味着 「左子数组当前元素 至 末尾元素」 与 「右子数组当前元素」 构成了若干 「逆序对」

算法流程:题解

class Solution:
    def reversePairs(self, nums: List[int]) -> int:
        def merge_sort(l, r):
            # 终止条件
            if l >= r: return 0
            # 递归划分
            m = (l + r) // 2
            res = merge_sort(l, m) + merge_sort(m + 1, r)
            # 合并阶段
            i, j = l, m + 1
            tmp[l:r + 1] = nums[l:r + 1]
            for k in range(l, r + 1):
                if i == m + 1:
                    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 += m - i + 1 # 统计逆序对
            return res
        
        tmp = [0] * len(nums)
        return merge_sort(0, len(nums) - 1)

剑指offer51题更好的一个解法新题解

class Solution:
    def reversePairs(self, nums: List[int]) -> int:

        def mergeSort(nums, low, high):
            if low >= high:     # 递归终止
                return 0        
            
            ans = 0             # 记录当前逆序对数目

            '''递归排序'''
            mid = low+(high-low)//2     
            ans += mergeSort(nums, low, mid)        # 左半部分逆序对数目
            ans += mergeSort(nums, mid+1, high)     # 右半部分逆序对数目

            '''nums[low, mid] 和 nums[mid+1, high] 已排序好'''
            tmp = []        # 记录nums[low, high]排序结果
            left, right = low, mid+1
            while left<=mid and right<=high:
                if nums[left] <= nums[right]:
                    tmp.append(nums[left])
                    left += 1
                else:       # 后半部分值较小,出现了逆序
                    tmp.append(nums[right])
                    right += 1
                    ans += mid+1-left       # 当前值 nums[right] 贡献的逆序对个数为 mid+1-left
                    '''解释:若nums[left] > nums[right],
                       则nums[left, mid] > nums[right]均成立,共 mid+1-left 项'''
            
            '''左或右数组需遍历完(最多只有一个未遍历完)'''
            while left<=mid:
                tmp.append(nums[left])
                left += 1

            while right<=high:
                tmp.append(nums[right])
                right += 1
                # ans += mid+1-left     # 此时,前半部分一定已经遍历完了,即left=mid+1,因此无需再更新结果
            
            nums[low:high+1] = tmp
            return ans
        
        '''主程序'''
        return mergeSort(nums, 0, len(nums)-1)

这里while的用法比上一个的判断语句更加简洁

类似使用归并排序求解逆序对的题目:
力扣315题

题解为315题解

代码如下:

class Solution:
    def countSmaller(self, nums: List[int]) -> List[int]:

        '''根据nums[*][0]进行排序,对应的index随之移动'''
        def mergeSort(nums, low, high):
            if low >= high:     # 递归终止
                return 0

            '''递归排序'''
            mid = low + (high-low)//2     
            mergeSort(nums, low, mid)           # 左半部分逆序对数目
            mergeSort(nums, mid+1, high)        # 右半部分逆序对数目

            '''nums[low, mid] 和 nums[mid+1, high] 已排序好'''
            tmp = []                            # 记录nums[low, high]排序结果
            left, right = low, mid+1
            while left<=mid and right<=high:
                if nums[left][0] <= nums[right][0]:         # 根据nums[*][0]进行排序
                    tmp.append(nums[left])
                    res[nums[left][1]] += right-(mid+1)     # 记录逆序对数目【对应坐标nums[*][1]处】
                    left += 1
                else:
                    tmp.append(nums[right])
                    right += 1
            
            '''左或右数组需遍历完(最多只有一个未遍历完)'''
            while left<=mid:
                tmp.append(nums[left])
                res[nums[left][1]] += right -(mid+1)    # 记录逆序对数目【对应坐标nums[*][1]处】
                left += 1

            while right<=high:
                tmp.append(nums[right])
                right += 1
            
            nums[low:high+1] = tmp


        '''主程序'''
        n = len(nums)
        res = [0] * n               # 存储结果
        nums = [(num, idx) for idx, num in enumerate(nums)] 
        # 每个数值附上其对应的索引:
        # 此时,nums[i][0]表示原来的数值,而nums[i][1]则表示原数值对应的索引(方便定位)

        mergeSort(nums, 0, n-1)     # 归并排序
        return res

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值