leetcode刷题记录:归并排序和快速排序

1. 快速排序

https://labuladong.github.io/algo/di-yi-zhan-da78c/shou-ba-sh-66994/kuai-su-pa-39aa2/

1.1 快排基础

先看核心代码

def sort(nums, lo, hi):
    if (lo >= hi):
        return
    p = partition(nums, lo, hi)
    sort(nums, lo, p-1)
    sort(nums, p+1, hi)

一句话总结快排:先将一个元素排好序(nums[p]),再将剩下的元素排好序

快排的核心是partition函数,其作用是在nums[lo, …, hi]中寻找一个切分点索引p, 让nums[lo,...,p] <= nums[p] < nums[p+1, ..., hi]注意这里的边界条件,是小于等于和大于。即把nums[p]放到正确的位置上;再用递归把左边和右边的部分都排好序即可,其实就是一个二叉树的前序遍历。

partition函数的结果类似一个二叉搜索树nums[p]是根节点,左边是左子树,右边是右子树。或者说,快排就是一个构造二叉搜索树的过程

但是有可能会碰到极端不平衡的情况,比如每次选出的p都在两端,导致左子树或者右子树为空,这样时间复杂度会大幅度上升,因此需要增加数组的随机性。

class Solution(object):
    def quickSort(self, nums, lo, hi):
        if (lo >= hi):
            return
        p = self.partition(nums, lo, hi)
        self.quickSort(nums, lo, p-1)
        self.quickSort(nums, p+1, hi)
    def partition(self, nums, lo, hi):
        import random
        pivot_idx = random.randint(lo, hi)   
        self.swap(nums, lo, pivot_idx)                # 随机选择pivot
        pivot = nums[lo]
        i, j = lo+1, hi
        while i <= j:            
            while i < hi and nums[i] <= pivot:
                i += 1
            while j > lo and nums[j] > pivot:
                j -= 1                
            if (i >= j):
                break                
            self.swap(nums, i, j)
        self.swap(nums, lo, j)
        return j
    def swap(self, nums, i, j):
        nums[i], nums[j] = nums[j], nums[i]
    def sortArray(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """
        self.quickSort(nums, 0, len(nums)-1)
        return nums

1.2 快排变体:三路快排解决相同元素的case

以上方法是传统的两路快排,缺点是leetcode上有个[2,2,2,2…]的相同元素case过不了(官方的c++题解都过不了)。这里可以用以下三路排序的方式来解决,把整个数组分成<pivot, =pivot 和 >pivot三段来解决。
代码如下,详细思路可以参考: https://www.runoob.com/data-structures/3way-qiuck-sort.html

class Solution(object):
    def quickSort(self, nums, l, r):
        import random
        if (l >= r):
            return
        self.swap(nums, l, random.randint(l, r))
        pivot = nums[l]
        # index
        lt, gt = l, r
        i = l + 1
        while (i <= gt):
            if (nums[i] < pivot):
                self.swap(nums, i, lt+1)
                i += 1
                lt += 1                
            elif (nums[i] > pivot):
                self.swap(nums, i, gt)
                gt -= 1
            else:
                i += 1
        self.swap(nums, l, lt)
        self.quickSort(nums, l, lt-1)
        self.quickSort(nums, gt+1, r)
    def swap(self, nums, i, j):
        nums[i], nums[j] = nums[j], nums[i]
    def sortArray(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """
        self.quickSort(nums, 0, len(nums)-1)
        return nums

时间复杂度
主要的时间复杂度在partition循环里。第一次partition函数的执行时间最长,是数组总的元素数,所以partition的时间复杂度是O(N).
假设切分点每次都落在中间,类比一个二叉树,数的深度是logN,一共要执行logN次partion函数。所以理想的时间复杂度是O(NlogN)

空间复杂度:空间复杂度就是递归栈的深度,即O(logN)

假设数组的分布不均匀,每次partition的长度从N开始递减,时间复杂度是
N + N-1 + N-2 + … + N-(N-1) = N^2 - (N-1)N/2 = N^2/2+N/2 => O(N^2)
这个时候树的深度是N,因此空间复杂度是O(N)

1.3 快排衍生:快速选择 Quick Select

快速选择是快排的变体,例子是leetcode215题:数组中的第k个最大元素 https://leetcode.cn/problems/kth-largest-element-in-an-array/

解法一:二叉堆(优先队列)

一些基本的定义:

  • 堆:一颗用数组表示的特殊的树。我们主要讨论二叉堆,即对应的是二叉树。这颗树需要满足以下条件
    • 完全二叉树:除了最后一层,其他层的节点个数都是满的,最后一层的节点都集中在左部连续位置
    • 堆中每一个节点的值都必须大于等于(或小于等于)其左右子节点的值 。前者叫大顶堆,后者叫小顶堆
  • 大顶堆:即任何一个父节点的值,都 大于等于 它左右孩子节点的值。
  • 小顶堆:即任何一个父节点的值,都 小于等于 它左右孩子节点的值。
  • 堆的建立:这里先跳过
  • 堆的操作:删除、添加
  • 优先队列:优先队列也是一种队列,只不过其入队和出队顺序是按照优先级来的;支持插入和删除最小值操作(返回并删除最小元素)或删除最大值操作(返回并删除最大元素);对应的数据结构就是小顶堆和大顶堆
    本题用二叉堆的解法:

解法二:快速选择

先抄一个解法,有个case会超时,明天再看下。再不休息就要死了啊啊啊啊

class Solution(object):
    def findKthLargest(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: int
        """
        def quickSelect(nums, lo, hi, k):
            lt, gt = partition(nums, lo, hi)
            if lt > k:
                return quickSelect(nums, lo, lt-1, k)
            elif gt < k:
                return quickSelect(nums, gt+1, hi, k)
            else:
                return nums[lt]
            
        def partition(nums, lo, hi):
            import random
            p = random.randint(lo, hi)
            pivot = nums[p]
            swap(nums, lo, p)
            lt, gt, i = lo, hi+1, lo+1
            # print("before:", nums, lt, gt, i)
            while i < gt:
                if nums[i] < pivot:
                    swap(nums, i, lt+1)
                    i += 1
                    lt += 1
                elif nums[i] > pivot:
                    gt -= 1
                    swap(nums, i, gt)
                else:
                    i += 1
            swap(nums, lo, lt)
            # print("after:", nums, lt, gt, i)
            return lt, gt-1
        def swap(nums, i, j):
            nums[i], nums[j] = nums[j], nums[i]
        # print("nums:", nums)
        if not nums:
            return 
        k = len(nums) - k
        return quickSelect(nums, 0, len(nums)-1, k)

2. 归并排序

归并排序采用分而治之(divide-and-conquer)的思想。

  • 分:将问题分解成一个个小的子问题然后递归求解
  • 治:将解决好的问题merge在一起。
    即先让每个子序列有序,再让子序列之间有序,即可完成数组的排序。
    图片示例:https://blog.csdn.net/python_tian/article/details/122086920

归并排序的思路和代码框架

归并排序就是先把左半边数组排好序,再把右半边数组排好序,然后把两半数组合并。

def sort(nums, lo, hi):
    if lo == hi:
        return nums
    mid = lo + (hi-lo)//2
    sort(nums, lo, mid)
    sort(nums, mid+1, hi)
    # 后序位置
    merge(nums, lo, mid, hi)
def merge(nums, lo, mid, hi):
    pass

在二叉树的纲领篇(记得放link)我们说过,二叉树问题有两种解法,一是遍历一遍二叉树,而是分解问题。从上面这段代码可以看出,归并排序就是分解二叉树的解法。
归并排序的逻辑可以抽象成一个二叉树的后序遍历。树上每个结点的值是nums[lo:hi], 其中左子树是nums[lo:mid-1], 右子树是nums[mid+1:hi],根节点是nums[mid].
然后在每个节点的后序位置执行merge函数,合并两个子节点上的子数组。
时间复杂度O(nlogn), 空间O(n)

class Solution(object):
    def sortArray(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """        
        n = len(nums)
        if n <= 1:
            return nums
        self.temp = [0] * n
        self.mergeSort(nums, 0, n-1)
        return nums
    def mergeSort(self, nums, lo, hi):
        if lo >= hi:
            return
        mid = lo + (hi-lo)//2
        self.mergeSort(nums, lo, mid)
        self.mergeSort(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]
        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

归并排序的应用:计算右侧小于当前元素的个数

leetcode315 https://leetcode.cn/problems/count-of-smaller-numbers-after-self/description/
构造一个pair class,key是元素值,value是index
merge函数中arr[i] = temp[p1]时更新count

class Solution(object):
    def countSmaller(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """
        class Pair(object):
            def __init__(self, key, val):
                self.key = key
                self.val = val
        def mergeSort(arr, lo, hi):
            if lo >= hi:
                return
            mid = lo  + (hi-lo)//2
            mergeSort(arr, lo, mid)
            mergeSort(arr, mid+1, hi)
            merge(arr, lo, mid, hi)
        def merge(arr, lo, mid, hi):
            idx = lo
            p1, p2 = lo, mid+1
            for i in range(lo, hi+1):
                temp[i] = arr[i]
            for i in range(lo, hi+1):
                if p1 == mid+1:
                    arr[i] = temp[p2]
                    p2 += 1
                elif p2 == hi+1:
                    arr[i] = temp[p1]
                    count[temp[p1].val] += p2-mid-1
                    # print(temp[p1].val, count)
                    p1 += 1 
                elif temp[p1].key <= temp[p2].key:
                    arr[i] = temp[p1]
                    count[temp[p1].val] += p2-mid-1
                    p1 += 1                    
                else:
                    arr[i] = temp[p2]
                    p2 += 1            
        arr = [Pair(nums[i], i) for i in range(len(nums))]
        temp = [Pair(0, i) for i in range(len(nums))]
        count = [0 for _ in range(len(nums))]
        mergeSort(arr, 0, len(arr)-1)
        return count

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/585023.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

XL520无线接收芯片,2.2ms超低启动时间,-110dBm高接收灵敏度

XL520接收芯片采用SOP8封装&#xff0c;适用于300MHz- 440MHz频率范围&#xff0c;正常工作电压范围2.0~5.5V&#xff0c;工作电流在3.0~3.2mA之间。它具有快速的启动时间&#xff08;2.2ms&#xff09;和高达-110dBm的接收灵敏度&#xff0c;非常适合对低功耗要求严格的设备。…

测试工程师——招聘分析

测试工程师 随着互联网行业的高速发展,快速高质量的产品版本迭代成为企业始终立于不败之地的迫切需求,而在短期迭代的快节奏中,传统测试工作面对更大压力,无法持续提供高效率高质量的人力支撑,所以越来越多的企业需要技术更为全面的测试开发工程师。测试开发 本质上属于测…

Web安全的最后一道防线:细谈Gobuster的目录/文件/Vhost/DNS子域名暴力破解艺术

一、前言 Gobuster是一款用go语言编写的对于网站目录/文件、DNS子域、虚拟主机vhost进行暴力穷举的开源工具&#xff0c;常用于安全领域&#xff0c;其常用的暴力破解模式到目前为止&#xff08;3.6版本&#xff09;有如下几种&#xff1a; 模式含义dir最经典的文件路径/目录破…

深入Rust标准库:必备的Rust语言高级指南

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【工具大全】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…

力扣---二叉树的右视图

给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 示例 1: 输入: [1,2,3,null,5,null,4] 输出: [1,3,4]示例 2: 输入: [1,null,3] 输出: [1,3]示例 3: 输入: [] 输出: []实现方法&…

网络安全漏洞分析之远程代码执行

介绍 Apache Flume 是一个分布式的&#xff0c;可靠的&#xff0c;并且可用于高效地收集&#xff0c;汇总和移动大量日志数据的软件。它具有基于流数据流的简单而灵活的体系结构。它具有可调的可靠性机制以及许多故障转移和恢复机制&#xff0c;并且具有健壮性和容错性。它使用…

CSS @keyframes 动画:颜色变化、背景旋转与放大缩小

在CSS中&#xff0c;keyframes 是一个强大的工具&#xff0c;它允许我们创建复杂的动画效果。今天&#xff0c;我们将一起探索如何使用 keyframes 来实现颜色变化、背景旋转以及放大缩小的动画效果。 动画会在 2 秒内循环播放&#xff0c;并在不同的时间点改变盒子的背景颜色和…

JTextField限制只能输入特定字符

1. 背景 最近写了一个公司内部用的通用MQTT协议JMeter自定义采样器&#xff0c;自定义表达式的处理手法与《JMeter通用Http采样器》https://blog.csdn.net/camelials/article/details/127135630 一致。不同的是协议变了、荷载构造方式变了等。另外&#xff0c;由于结合了自身应…

第三方软件测试机构的优势

软件测试机构在软件开发和验收过程中扮演着至关重要的角色&#xff0c;其优势主要体现在以下几个方面&#xff1a; 专业性&#xff1a;软件测试机构通常拥有专业的测试团队&#xff0c;这些团队成员具备丰富的测试经验和深厚的专业知识&#xff0c;能够准确识别软件中的潜在问…

Three.js杂记(十五)—— 汽车展览(下)

在上一篇文章Three.js杂记&#xff08;十四&#xff09;—— 汽车展览上 - 掘金 (juejin.cn)中主要对切换相机不同位置和鼠标拖拽移动相机焦点做了简单的应用。 那么现在聊聊该如何实现汽车模型自带的三种动画展示了&#xff0c;实际上可以是两种汽车前后盖打开和汽车4车门打开…

大模型实战:如何使用图数据库提高向量搜索精确度?

文本嵌入和向量搜索技术可以帮助我们根据文档的含义及其相似性来检索文档。但当需要根据日期或类别等特定标准来筛选信息时&#xff0c;这些技术就显得力不从心。 为了解决这个问题&#xff0c;我们可以引入元数据过滤或过滤向量搜索&#xff0c;这允许我们根据用户的特定需求…

开源AI智能名片商城小程序:深度解读IMC(IP、MarTech、Content)视角

在数字化浪潮中&#xff0c;私域流量的运营已成为企业不可或缺的增长引擎。而开源AI智能名片商城小程序&#xff0c;则是以一种全新的视角——IMC&#xff08;IP、MarTech、Content&#xff09;&#xff0c;为企业打开私域流量运营的新篇章。今天&#xff0c;我们就来一起深入解…

Leetcode-17.04. 消失的数字

面试题 17.04. 消失的数字 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/missing-number-lcci/ 目录 面试题 17.04. 消失的数字 - 力扣&#xff08;LeetCode&#xff09; 题目 解题(注释) 第一种方法 第二种方法 第三种方法 题目 数组nums包含…

【GAMES 101】图形学入门——着色(Shading)

定义&#xff1a;将不同材质内容应用于不同物体对象上的过程。着色只考虑着色点的存在&#xff0c;不考虑其他物体的遮挡等&#xff0c;因此不考虑阴影处理 一些前期内容的定义&#xff1a; 着色点&#xff08;Shading Point&#xff09;观测方向&#xff08;Viewer Directio…

vue 脚手架 创建vue3项目

创建项目 命令&#xff1a;vue create vue-element-plus 选择配置模式&#xff1a;手动选择模式 (上下键回车) 选择配置&#xff08;上下键空格回车&#xff09; 选择代码规范、规则检查和格式化方式: 选择语法检查方式 lint on save (保存就检查) 代码文件中有代码不符合 l…

抄表自动化的实现与优势

1.界定与简述 抄表自动化是一种当代关键技术&#xff0c;致力于取代传统的手动式抄表方法&#xff0c;通过远程数据数据采集解决&#xff0c;完成电力工程、水、气等公用事业电力仪表的全自动载入。这一系统利用先进的感应器、物联网技术(IoT)设备及数据数据分析工具&#xff…

西圣全新磁吸无线充电宝强势上线:打开充电新方式,摆脱续航焦虑

在移动互联时代&#xff0c;智能手机、平板电脑等电子设备已经成为我们生活不可或缺的一部分。但随之而来的是电池续航问题的困扰&#xff0c;用户往往需要在户外、旅途或日常生活中频繁地充电。为了解决这一问题&#xff0c;充电宝作为便携式的移动充电设备&#xff0c;已经成…

leetCode61. 旋转链表

leetCode61. 旋转链表 题目思路&#xff1a;见如图所示 代码展示 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* Li…

触发器的启用和禁用

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 在 Oracle 数据库中&#xff0c;所创建的触发器可以根据情况&#xff0c;灵活修改它的状态&#xff0c;使其有效或者无效&#xff0c;即启用或者禁用。 其语法格式如下所示。…
最新文章