python版leetcode_ch2_O(NlogN) 排序算法

本博客题目来源b站左程云的视频。使用python手敲。并记录了一些思路,后续会持续更新

ch2 O(N logN) 排序算法

2.1 归并排序

题目描述:

使用选择排序的方法对数组进行排序。

思路:

归并排序是一种递归思路,递归的让左边排好序,右边排好序,然后将左右归并在一起。

代码:

class Mergesort:
    def merge_sort(self, nums: List[int]):
        if len(nums) < 2:
            return
        left, right = 0, len(nums)-1
        self.process(nums, left, right)
    def process(self, nums: List[int], left, right):
        if left == right:
            return
        mid = ((right - left) >> 1) + left
        self.process(nums,left, mid) # 递归的左边有序
        self.process(nums, mid+1, right) # 递归的右边有序
        self.merge(nums, left, mid, right) # merge到一起

    def merge(self, nums: List[int], left: int, mid: int, right: int):
        #辅助数组
        result = []
        i, j = left, mid+1
        while i <= mid and j <= right:
            if nums[i] < nums[j]:
                result.append(nums[i])
                i += 1
            else:
                result.append(nums[j])
                j += 1

        # result.extend(nums[i:mid+1]) # 也可以这么写
        # result.extend(nums[j:right+1])
        while i <= mid:
            result.append(nums[i])
            i += 1
        while j <= right:
            result.append(nums[j])
            j += 1

        for i in range(len(result)):
            # 一定要注意是left+i!!!因为要写回原数组!!!
            nums[left + i] = result[i]

m = Mergesort()
arr = [1, 3, 6, 2, 4, 5]
m.merge_sort(arr)
print(arr)

2.2 小和问题

题目描述:

求数组中所有小和。对于nums:[1,3,4,2,5] 小和的定义为,该元素左边有多少比它小的数,则这些数都算小和,例如对于nums ,1没有小和,3的小和为1,4的小和为1+3,2的小和为1,5的小和为1+3+4+2。所以所有小和为16。

思路:

思路1:对于每个数,遍历其左边所有数。根据平方差公式,时间复杂度为O(N^2)

思路2:

  • 首先小和可以换个思路来看,nums[i]左边有多少比它小的数产生小和,可以变为nums[i]右边有多少比它大的数,则产生这么多个数 * nums[i] 个小和,例如,对于元素1,一共产生4*1个小和,3产生 2*3,4产生1*4 ,5没有。
  • 所以可以参考归并排序的思路,因为左右两边都是有序的,如果左边的数比右边的数小,则直接可以计算出产生多少小和,这样在归并排序的同时,将产生的小和结果累加,时间复杂度为O(NlogN)

代码:

class SmallSum:
    def get_small_sum(self, nums: List[int])-> int:
        if len(nums) < 2:
            return 0
        left, right = 0, len(nums)-1
        return self.process(nums, left, right)
    def process(self, nums: List[int], left: int, right: int):
        if left == right:
            return 0
        mid = left + ((right - left) // 2)
				# 返回所有小和
        return self.process(nums, left, mid) \
            + self.process(nums, mid+1, right) \
            + self.merge(nums, left, mid, right)

		# 归并需要O(N)的额外空间
    def merge(self, nums: List[int], left: int, mid: int, right: int):
        help_arr = []
        result = 0

        i, j = left, mid+1
        while i <= mid and j <= right:
            if nums[i] < nums[j]:
                help_arr.append(nums[i])
                result += nums[i] * (right - j + 1)
                i += 1
            else:
                help_arr.append(nums[j])
                j += 1

        while i <= mid:
            help_arr.append(nums[i])
            i += 1

        while j <= right:
            help_arr.append(nums[j])
            j += 1

        for i in range(len(help_arr)):
            nums[left + i] = help_arr[i]

        return result

2.3 逆序对

题目描述:

打印数组中所有逆序对。逆序对的定义为,左边的元素比右边元素大,则构成逆序对。对于nums:[3,2,4,5,0] 所有逆序对为:3: (3,2)、(3, 0); 2:(2,0); 4:(4,0); 5:(5,0)

思路:

思路:利用归并排序的原理,归并时,当nums[i]比右边的数 nums[j]大时,则i到mid+1的数都对nums[j]产生逆序对。

代码:

class ReversePair:
    def print_reverse_pair(self, nums: List[int]):
        if len(nums) < 2:
            print(None)
        left, right = 0, len(nums)-1
        res = self.process(nums, left, right)
        print(res)
    def process(self, nums: List[int], left: int, right: int) -> List[int]:
        res = []
        if left == right:
            return res
        mid = left + (right - left) // 2
        res.extend(self.process(nums, left, mid))
        res.extend(self.process(nums,mid+1, right))
        res.extend(self. merge(nums, left, mid, right))
        return res

    def merge(self, nums: List[int], left: int, mid: int, right: int):
        help_arr = []
        reversed_pair_arr = []
        i, j = left, mid+1
        while i <= mid and j <= right:
            if nums[i] <= nums[j]:
                help_arr.append(nums[i])
                i += 1
            else:
                help_arr.append(nums[j])
                # 当nums[i]比右边的数 nums[j]大时,则i到mid+1的数都对nums[j]产生逆序对。
                for k in range(i, mid+1):
                    reversed_pair_arr.append((nums[k], nums[j]))
                j += 1

        while i <= mid:
            help_arr.append(nums[i])
            i += 1
        while j <= right:
            help_arr.append(nums[j])
            j += 1

        for i in range(len(help_arr)):
            nums[left+i] = help_arr[i]

        return reversed_pair_arr

2.4 荷兰国旗问题

题目描述:

在这里插入图片描述

思路:

问题1:划分出两部分,一部分是≤num的区域,另一部分是>num的区域

问题2:划分出三部分,一部分是<区,一部分是=区,一部分是>区,请注意,从>区换出来的数字,还需要再次判断!

代码:

class Flag1:
    def flag_arr(self, nums:List[int], target: int):
        if len(nums) < 2:
            return
        less_idx = -1
        for i in range(len(nums)):
            if nums[i] <= target:
                less_idx += 1
                nums[less_idx], nums[i] = nums[i], nums[less_idx]

class Flag2:
    def flag_arr(self, nums: List[int], target: int):
        if len(nums) < 2:
            return
        less_idx = -1
        more_idx = len(nums)
        i = 0
        while i < more_idx:
            if nums[i] < target:
                less_idx += 1
                nums[less_idx], nums[i] = nums[i], nums[less_idx]
                i += 1
            elif nums[i] > target:
                more_idx -= 1
                nums[more_idx], nums[i] = nums[i], nums[more_idx]
            else:
                i += 1

2.5 快排1.0

题目描述:

在这里插入图片描述

思路:

利用**两色荷兰国旗**问题的思路,每次拿最后一个数作为划分值,将小于该值的数放到左边,大于的放右边,最后把该数和大于区域的第一个数交换,这样该数就排好了,接下来递归做左右两边。

代码:

class QuickSortV1:
    def quick_sort_v1(self, nums: List[int]):
        if len(nums) < 2:
            return
        self.quick_sort(nums, 0, len(nums) -1) # 快排需要传入左右

    def quick_sort(self, nums: List[int], left: int, right: int):
        # 一定要注意左右边界的问题!!!
        if left >= right:
            return
        # 根据两色荷兰国旗的思想,找到小于等于target区的less,和大于区的more
        # 对于[3, 5, 2, 6, 4]。左边界为1,右边界为3(形如32|4|56)
        less, more = self.partition(nums, left, right)
        self.quick_sort(nums, left, less)
        self.quick_sort(nums, more, right)

    def partition(self, nums: List[int], left: int, right: int) -> (int, int):
        less = left - 1
        target = nums[right] # target就用最后一个数!
        for i in range(left, right):
            if nums[i] <= target:
                less += 1
                nums[i], nums[less] = nums[less], nums[i]
        nums[less+1], nums[right] = nums[right], nums[less+1] # 这里一定不能用target换。而要写成nums[right]!!!
        more = less + 2 # 大于区一定是less区边界+2,因为target换过来了
        return less, more

# q = QuickSortV1()
# a = [3, 2, 4, 6, 5, 5, 2, 1, 4, 6, 7]
# q.quick_sort_v1(a)
# print(a)

2.6 快排2.0

题目描述:

在这里插入图片描述

思路:

利用**三色荷兰国旗**问题的思路,每次拿最后一个数作为划分值,将小于该值的数放到左边,大于的放右边,等于的数放在中间。这样等于该数的数据就排好了,接下来递归做左右两边。

代码:

class QuickSortV2:
    def quick_sort_v2(self, nums: List[int]):
        if len(nums) < 2:
            return
        self.quick_sort(nums, 0, len(nums)-1)

    def quick_sort(self, nums: List[int], left: int, right: int):
        # 一定要注意左右边界的问题!!!
        if left >= right:
            return
        less, more = self.partition(nums, left, right)
        self.quick_sort(nums, left, less)
        self.quick_sort(nums, more, right)

    def partition(self, nums: List[int], left: int, right: int) -> (int, int):
        less = left - 1
        more = right + 1
        target = nums[right]
        while left < more:
            if nums[left] < target:
                less += 1
                nums[less], nums[left] = nums[left], nums[less]
                left += 1
            elif nums[left] == target:
                left += 1
            else:
                more -= 1
                nums[more], nums[left] = nums[left], nums[more]
        # 三色荷兰国旗问题后,无需交换!!因为区域已经排好了!!底下这句一定不能加
        # nums[more], nums[right] = nums[right], nums[more]
        return less, more

# q = QuickSortV2()
# a = [3, 2, 4, 6, 5, 5, 2, 1, 4, 6, 7, 4]
# q.quick_sort_v2(a)
# print(a)

2.7 快排3.0

题目描述:

在这里插入图片描述

思路:

思路和快排2.0完全一样,利用**三色荷兰国旗**问题的思路,唯一的区别就是随机选一个数作为划分值。这样复杂度期望会降到O(NlogN)

代码:

import random
class QuickSortV3:
    def quick_sort_v3(self, nums: List[int]):
        if len(nums) < 2:
            return
        self.quick_sort(nums, 0, len(nums)-1)

    def quick_sort(self, nums: List[int], left: int, right: int):
        # 一定要注意左右边界的问题!!!
        if left >= right:
            return
        less, more = self.partition(nums, left, right)
        self.quick_sort(nums, left, less)
        self.quick_sort(nums, more, right)

    def partition(self, nums: List[int], left: int, right: int) -> (int, int):
        less = left - 1
        more = right + 1
        # 随机选择一个元素!
        target = random.choice(nums[left:right])
        while left < more:
            if nums[left] < target:
                less += 1
                nums[less], nums[left] = nums[left], nums[less]
                left += 1
            elif nums[left] == target:
                left += 1
            else:
                more -= 1
                nums[more], nums[left] = nums[left], nums[more]
        # 三色荷兰国旗问题后,无需交换!!因为区域已经排好了!!底下这句一定不能加
        # nums[more], nums[right] = nums[right], nums[more]
        return less, more

# q = QuickSortV3()
# a = [3, 2, 4, 6, 5, 5, 2, 1, 4, 6, 7, 4]
# q.quick_sort_v3(a)
# print(a)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值