5.25 力扣 排序(冒泡、快速、桶) 二分

排序


冒泡排序:依次比较相邻元素,进行len-1次冒泡,第K次冒泡将倒数第k个元素排好序

for i in range(len(s)):
	for j in range(len(s)-i):
		if s[j]>s[j+1]:
			s[j],s[j+1]=s[j+1],s[j]

快速排序: 递归加分治 215
不稳定排序,中间交换过程会打乱顺序
时间复杂度:最差O(N^ 2),平均O(N^logN)
取中间值作为基准,数列从第一个值开始比较,小于基准值放入左边分区,大于基准值放入右边分区
然后依次对左右两个分区进行再分区,直到最后只有一个元素
分解完成再一层一层返回,返回规则是:左边分区+基准值+右边分区

def quick_sort(nums):
	if len(nums)<2:
		return nums
	left,right=[],[]
	mid=nums[len(nums)//2]
	# 从原始数组中移除基准值
	nums.remove(mid)
	for num in nums:
		if num<mid:
			left.append(num)
		else:
			right.append(num)
	return quick_sort(left)+[mid]+quick_sort(right)

算法导论中的快排
快排实现原地排序,期望时间复杂度只有O(nlogn)。其中n是数组的长度。在最不差的情况下,快排的时间复杂度会退化到O(n2). 但是这种情况出现的概率很小
1.选取关键字,2.将关键字放在正确的位置上:关键字左边的数都比它小,右边的数都比它大。这样,这个关键字在接下来的排序中都不会改变位置。
i是一个关键的位置:i是比值关键字A[r]小的序列的末尾位置。i+1则是比A[r]大的数的区间的最左边的位置。i从整个区间最左端p-1开始不断增加。在整个过程结束之后,A[r]会和A[i+1]交换,就是和比A[r]大的序列的最左边的数交换,来达到A[r]的正确位置。(h步中的8和4交换)
A[j]小于等于x时,要讲A[j]交换到前面,因为【i+1,j】是比关键字大的区间,所以i的值增加,同时交换此时i和j的值。但是当A[j]的值大于x时,这个位置上的数不应该在x的左边区间,因此i不动,j继续右移。[i+1,j]就是比关键字大的数的区间。[p,i]是比关键字x小的区间
因为选取的关键字A[r]在区间的最右边,这个位置应该放比关键字大的数。因此,最后一步是将区间[i+1,j]的最左边的位置上的数值和关键字交换。
快排保证了每次排序关键字的位置一定正确,关键字左侧数据小于关键字,但顺序未知,关键字右侧数据大于关键字,顺序未知
下面代码是是升序有序,就是右边的数大于等于左边的数,这里由array[j] <= x决定。如果想要非增序排序,则i向右移动的条件是array[j] >= x
快排可以跳过一些元素的比较,在对一个区间进行划分之后,形成了两个区间。前一个区间里的数是没有必要和后面区间里面的数比较的。划分完成之后,不仅给关键字找到了最终的位置,而且还隔离了比较域。
https://blog.csdn.net/uestc_my/article/details/45600499?utm_source=copy在这里插入图片描述在这里插入图片描述

def quick_sort(array, l, r):
  if l < r:
  #一趟排序后,q左边的值都比q小,q右边的值都比q大
    q = partition(array, l, r)
    quick_sort(array, l, q - 1)
    quick_sort(array, q + 1, r)
 #原地排序,不需要返回值
def partition(array, l, r):
#选择区间尾作为基准
  x = array[r]
  i = l - 1
  for j in range(l, r):
    if array[j] <= x:
    #当J位置值比基准值小时,i,j一起移动
      i += 1
      array[i], array[j] = array[j], array[i]
  array[i + 1], array[r] = array[r], array[i+1]
  return i + 1

归并排序
O(N^logN)
首先归并排序使用了二分法,归根到底的思想还是分而治之。拿到一个长数组,将其不停的分为左边和右边两份,然后以此递归分下去。然后再将她们按照两个有序数组的样子合并起来
将其分割成只剩单一元素,在进行比较合并

#合并
        def merge(l,r):
            i,j=0,0
            c=[]
            while i<len(l) and j<len(r):
                if l[i]<r[j]:
                    c.append(l[i])
                    i+=1
                else:
                    c.append(r[j])
                    j+=1
            if i<len(l):
                c.extend(l[i:])
            if j<len(r):
                c.extend(r[j:])
            return c
#分割
        def merge_sort(nums):
            if len(nums)<2:
                return nums
            mid=len(nums)//2
            left=merge_sort(nums[:mid])
            right=merge_sort(nums[mid:])
            return merge(left,right)

桶排序
分桶,合并
1.基数排序在这里插入图片描述
2.次数排序
统计每一个数字出现的次数,输出次数次即可
桶排序也叫计数排序,简单来说,就是将数据集里面所有元素按顺序列举出来,然后统计元素出现的次数。最后按顺序输出数据集里面的元素。
在这里插入图片描述
比较占用内存,如果数组范围较大,那么需要申请一个很大的空间,但是速度很快

def bucketSort(nums):
    # 选择一个最大的数
    max_num = max(nums)
    # 创建一个元素全是0的列表, 当做桶
    bucket = [0]*(max_num+1)
    # 把所有元素放入桶中, 即把对应元素个数加一
    for i in nums:
        bucket[i] += 1
 
    # 存储排序好的元素
    sort_nums = []
    # 取出桶中的元素
    for j in range(len(bucket)):
        if bucket[j] != 0:
            for y in range(bucket[j]):
                sort_nums.append(j)
 
    return sort_nums

在这里插入图片描述
912. 排序数组
在这里插入图片描述
快速排序:
在这里插入图片描述

class Solution:
    def sortArray(self, nums: List[int]) -> List[int]:
        if len(nums)<2:
            return nums
        left,right=[],[]
        mid=nums[len(nums)//2]
        nums.remove(mid)
        for num in nums:
            if num<=mid:
                left.append(num)
            else:
                right.append(num)
        return self.sortArray(left)+[mid]+self.sortArray(right)

归并排序
时间复杂度:O(N^logN)
首先归并排序使用了二分法,归根到底的思想还是分而治之。拿到一个长数组,将其不停的分为左边和右边两份,然后以此递归分下去。然后再将她们按照两个有序数组的样子合并起来

class Solution:
    def sortArray(self, nums: List[int]) -> List[int]:
        def merge(l,r):
            i,j=0,0
            c=[]
            while i<len(l) and j<len(r):
                if l[i]<r[j]:
                    c.append(l[i])
                    i+=1
                else:
                    c.append(r[j])
                    j+=1
            if i<len(l):
                c.extend(l[i:])
            if j<len(r):
                c.extend(r[j:])
            return c

        def merge_sort(nums):
            if len(nums)<2:
                return nums
            mid=len(nums)//2
            left=merge_sort(nums[:mid])
            right=merge_sort(nums[mid:])
            return merge(left,right)
        return merge_sort(nums)

桶排序
164. 最大间距
在这里插入图片描述
本题不需要真正将所有元素严格排序,只需要求出最大的间隔即可,考虑桶排序。将元素分配到不同的桶中,桶的大小一样,桶内的元素无序,且桶有序
n个元素的数组有n-1个间隔,所有元素间隔一致时的间隔是最小的最大间隔(因为缩小任意一个间隔,一定有另一个间隔增大),所以按平均分配间隔来建立桶,平均间隔为margin=max(1, (max-min)//(n-1))
桶的个数b=ceil((max-min)/margin) ceil:向上取整
桶间间隔由后面桶的最小值减去前面桶的最大值得到,跳过没有元素的桶。

如果区间里面的数字都是等间隔排列的,那最大的间隔值一定是最小的,这种情况下,任何一个数值不论增大还是减小,都只可能会造成最大间隔变大,不可能让情况变更好,把这个等间隔作为桶的大小(margin=max(1, (max-min)//(n-1))),最大间隔一定比这个桶大小要大,所以分在一个桶里面的数值之间差值不可能是最大间隔,最大间隔只可能产生在相邻的桶之间,一定值前一个桶的最大值和后一个桶的最小值的差值,所以只需要维护每一个桶里面的最大值和最小值,最后把相邻的桶遍历一遍即可,时间复杂度是线性的
通过( num-minnum)//margin判断属于哪个桶
所有元素遍历完后,比较k-1个相邻桶间距的最大值即可
在这里插入图片描述
在这里插入图片描述
对于n个元素的数组,有n+1个桶,那么至少会有一个空桶,那么最大间距一定是最大桶间间距(跳过空桶)

class Solution:
    def maximumGap(self, nums: List[int]) -> int:
        if len(nums)<2:
            return 0
        maxm,minm=max(nums),min(nums)
        n=len(nums)
        #平均间隔,用于计算每个数据属于哪个桶
        margin=max(1,(maxm-minm)//n-1)
        #桶的个数
        b=(maxm-minm)//margin+1
        # 保存桶内最小值和最大值
        bucke=[[float('inf'),float('-inf')]for i in range(b)]
        for num in nums:
            loc=(num-minm)//margin
            #维护桶内最大最小值
            bucke[loc][0]=min(num,bucke[loc][0])
            bucke[loc][1]=max(num,bucke[loc][1])
        res=float('-inf')
        premin=minm
        for x,y in bucke:
            #跨过空桶
            if x==float('inf'):
                continue
            #后一个桶的最小值减去前一个桶的最大值
            res=max(res,x-premin)
            premin=y
        res=max(res,maxm-premin)
        return res

220 存在重复元素 III
在这里插入图片描述
在这里插入图片描述
将元素按顺序分配到桶内,分配规则是按照 nums[i] // (t + 1),桶的大小是t,同一个桶内元素最大差值是t,桶间元素最大差值是2*t+1,
0-t t+1-2t+1 2t+2-3t+2 …
例如 t=0,说明一个元素一个桶
如果命中同一个桶,则直接返回True
如果命中相邻桶,判断差值是否<=t
否则返回False

由于题目有索引相差k的要求,因此要维护一个大小为k的窗口,定期清除桶中过期的数字
哈希表的key作为桶的编号,Value是元素

class Solution:
    def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
        if t<0:
            return False
        #将元素分到桶内,桶的大小为t,则桶内数据最大差值是t
        bucket=defaultdict(list)
        for i in range(len(nums)):
            loc=nums[i]//(t+1)
            #说明桶内已经有数据,符合条件索引差小于等于k,元素差小于等于t,返回True
            if loc in bucket:
                return True 
            #桶内没有元素,则比较相邻桶之间数据差是否满足要求
            if loc-1 in bucket and abs(bucket[loc-1]-nums[i])<=t:
                return True
            if loc+1 in bucket and abs(bucket[loc+1]-nums[i])<=t:
                return True
            bucket[loc]=nums[i]
            #定期删除索引范围差超过K的元素,为了下一次循环准备桶,保证bucket中最多只有k个桶(key)
            if i>=k:
                bucket.pop(nums[i-k]//(t+1))
        return False

451. 根据字符出现频率排序
在这里插入图片描述
按照元素出现次数分桶,出现次数相同的放到一个桶里,从大编号(出现次数多的)桶到小编号桶输出

class Solution:
    def frequencySort(self, s: str) -> str:
        n=len(s)
        res=[]
        bucket=[[] for i in range(n+1)]
        map=defaultdict(int)
        for i in s:
            map[i]+=1
        # 按照字符出现次数分桶,桶内元素是字符
        for i in map:
            bucket[map[i]].extend(i*map[i])
        #按照桶由大到小输出
        for m in bucket[::-1]:
            if m:
                res.extend(m)
        return ''.join(res)

归并排序
能够看到非常明显效果的阶段性排序结果
面试题51. 数组中的逆序对
在这里插入图片描述
在归并算法中,左右子序列都是有序序列,因此,当归并时,出现左子序列的某一元素
大于右子序列的某一元素时,说明左子序列剩下的所有元素都比右子序列的该元素大,因此
在归并中就可得到所有跨界逆序对数,即 len(left)-i ,只有当右子序列指针需要移动时才计算(说明次数右子序列j的元素小于左子序列i)
右边元素归并回去的时候,将左边元素还没归并回去的数目加到计数器变量上
计算逆序对个数发生在合并时候
在这里插入图片描述

class Solution:
    def reversePairs(self, nums: List[int]) -> int:
        if len(nums)<=1:
            return 0
        def merge(left,right):
            i,j=0,0
            m,n=len(left),len(right)
            tmp=[]
            while i<len(left) and j<len(right):
                if left[i]<=right[j]:
                    tmp.append(left[i])
                    i+=1
                else:
                    #当右边小于左边元素时,更新逆序对数量
                    self.count+=(m-i)
                    tmp.append(right[j])
                    j+=1
            if i<m:
                tmp.extend(left[i:])
            if j<n:
                tmp.extend(right[j:])
            return tmp
        def merge_sort(nums):
            if len(nums)<2:
                return nums
            mid=len(nums)//2
            left=merge_sort(nums[:mid])
            right=merge_sort(nums[mid:])
            return merge(left,right)
        self.count=0
        merge_sort(nums)
        return self.count

方法同理,只是每次修改Nums,虽然速度并没有起来,

class Solution:
    def reversePairs(self, nums: List[int]) -> int:
        self.cnt = 0
        def merge(nums, start, mid, end, temp):
            i, j = start, mid + 1
            while i <= mid and j <= end:
                if nums[i] <= nums[j]:
                    temp.append(nums[i])
                    i += 1
                else:
                    self.cnt += mid - i + 1
                    temp.append(nums[j])
                    j += 1
            while i <= mid:
                temp.append(nums[i])
                i += 1
            while j <= end:
                temp.append(nums[j])
                j += 1
            
            for i in range(len(temp)):
                nums[start + i] = temp[i]
            temp.clear()
        def mergeSort(nums, start, end, temp):
            if start >= end: return
            mid = (start + end) >> 1
            mergeSort(nums, start, mid, temp)
            mergeSort(nums, mid + 1, end, temp)
            merge(nums, start, mid,  end, temp)
        mergeSort(nums, 0, len(nums) - 1, [])
        return self.cnt

315. 计算右侧小于当前元素的个数 类似上题
在这里插入图片描述
降序排序,当左边元素i大于右边元素j时,说明i大于剩下的len(right)-j个元素
为了保证排序过程中索引变换导致数据相加错误,用索引数组排序,不用原始数组

class Solution:
    def countSmaller(self, nums: List[int]) -> List[int]:
        self.count=[0 for i in range(len(nums))]
        def merge(left,right):
            i,j=0,0
            m,n=len(left),len(right)
            tmp=[]
            while i<m and j<n:
                if nums[left[i]]>nums[right[j]]:
                    self.count[left[i]]+=n-j
                    tmp.append(left[i])
                    i+=1
                else:
                    tmp.append(right[j])
                    j+=1
            if i<m:
                tmp.extend(left[i:])
            if j<n:
                tmp.extend(right[j:])
            return tmp
        def merge_sort(nums):
            if len(nums)<2: 
                return nums
            mid=(len(nums))//2
            left=merge_sort(nums[:mid])
            right=merge_sort(nums[mid:])
            return merge(left,right)
        list=[i for i in range(len(nums))]
        merge_sort(list)
        return self.count

327. 区间和的个数
在这里插入图片描述
前缀和+归并排序
在排序过程中,每次将left和right列表合并前,为left中的每一个元素确定right中符合条件的值的范围。也就是说在right中设两个指针lo和up,其中lo指向right中第一个使right[lo] - left[i] >= lower的元素,而up指向right中最后一个使right[up] - left[i] <= upper的元素,将up-lo加到最后的结果上即可
由于可能存在重叠的数据,所以对于下界我们找左插入点,上界我们找右插入点。
归并排序的前提是合并时候,两边都是有序的,所以可以根据上下界求区间
代码根据这个写的
https://leetcode-cn.com/problems/count-of-range-sum/solution/327qu-jian-he-de-ge-shu-ti-jie-zong-he-by-xu-yuan-/
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class Solution:
    def countRangeSum(self, nums: List[int], lower: int, upper: int) -> int:
        def merge_sort(nums):
            if len(nums)<2:
                return nums
            mid=len(nums)//2
            left=merge_sort(nums[:mid])
            right=merge_sort(nums[mid:])
            return merge(left,right)
        def merge(left,right):
            i,j,lo,up=0,0,0,0
            m,n=len(left),len(right)
            #固定左边i,求右边j
            for le in range(m):
                while lo<n and right[lo]-left[le]<lower:
                    lo+=1
                while up<n and right[up]-left[le]<=upper:
                    up+=1
                self.count+=up-lo
            tmp=[]
            while i<m and j<n:
                if left[i]<=right[j]:
                    tmp.append(left[i])
                    i+=1
                else:
                    tmp.append(right[j])
                    j+=1
            if i<m:
                tmp.extend(left[i:])
            if j<n:
                tmp.extend(right[j:])
            return tmp
        self.count=0
        #求前缀和
        s=[0]*(len(nums)+1)
        for i in range(1,len(nums)+1):
            s[i]=s[i-1]+nums[i-1]
        merge_sort(s)
        return self.count

493. 翻转对
在这里插入图片描述
同找逆序对,用归并排序,在合并的时候进行翻转对的判断和数量相加
时间复杂度:O(Nlog(n))
空间复杂度:O(N)

class Solution:
    def reversePairs(self, nums: List[int]) -> int:
        self.count=0
        def merge(nums):
            if len(nums)<2:
                return nums
            mid=len(nums)//2
            left=merge(nums[:mid])
            right=merge(nums[mid:])
            return merge_sort(left,right)
        def merge_sort(left,right):
            i,j=0,0
            tmp=[]
            m,n=len(left),len(right)
            r=0
            for l in range(m):
                #固定i,右移符合条件的j,即找到最右边的r,符合left[l]>2*right[r]
                while r<n and left[l]>2*right[r]:
                    r+=1
                self.count+=r
            while i<m and j<n:
                if left[i]<right[j]:
                    tmp.append(left[i])
                    i+=1
                else:
                    tmp.append(right[j])
                    j+=1
            if i<m:
                tmp.extend(left[i:])
            if j<n:
                tmp.extend(right[j:])
            return tmp
        merge(nums)
        return self.count

148 排序链表
在这里插入图片描述
nlog(n):要求归并排序
在这里插入图片描述
分割:找到链表中点处断开,使用快慢指针,奇数个节点找到中点,偶数个节点找到中点左边的节点,找到中点后,设置右端头结点,同时断开左右两段链表,
递归分割时,输入当前链表左端点 head 和中心节点 slow 的下一个节点 tmp(因为链表是从 slow 切断的)。
cut 递归终止条件: 当head.next == None时,说明只有一个节点了,直接返回此节点
合并:谁小接谁
返回:头结点

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def sortList(self, head: ListNode) -> ListNode:
        if not head or not head.next:
            return head
        #找中点
        fast,slow=head.next,head
        while fast and fast.next:
            fast,slow=fast.next.next,slow.next
        mid=slow.next
        slow.next=None
        left=self.sortList(head)
        right=self.sortList(mid)
        tmp=dummy=ListNode(0)
        while left and right:
            if left.val<right.val:
                tmp.next=left
                left=left.next
            else:
                tmp.next=right
                right=right.next
            tmp=tmp.next
        tmp.next=left if left else right
        return dummy.next

215 数组中第K个最大元素
在这里插入图片描述
O(nlogn) 利用快速排序,每次判断基准值位置是不是第k个,利用非递增顺序排列,不是的话改变左右区间 边界,每次都缩小第k大数所在区间的边界

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        #快速排序 倒着排
        def partition(nums,l,r):
            pivort=nums[r]
            i=l-1
            for j in range(l,r):
                # 非递增排序,将大的值换到前面俩
                if nums[j]>=pivort:
                    i+=1
                    nums[i],nums[j]=nums[j],nums[i]
                #选取的关键字在区间的最右边,这个位置应该放比关键字小的数字,因此最后交换i+1和r位置的值
            nums[i+1],nums[r]=nums[r],nums[i+1]
            return i+1
        l,r=0,len(nums)-1
        while True:
            p=partition(nums,l,r)
            #如果关键字位置就是第k个元素,因为是降序排序,所以是第K大元素
            if p+1==k:
                return nums[p]
            # 说明p前面大的数不足k个,因为第k大元素去P的右侧区间找
            elif p+1<k:
                l=p+1
            else:
                r=p-1

219. 存在重复元素 II
在这里插入图片描述

class Solution:
    def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
        if k==0:
            return False
        #滑动窗口大小为K
        hash=defaultdict(int)
        for r in range(len(nums)):
            if nums[r] in hash:
                return True
            if r>=k:
                hash.pop(nums[r-k])
            hash[nums[r]]=r
        return False

217. 存在重复元素
在这里插入图片描述

class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        hash=set()
        for i in range(len(nums)):
            if nums[i] in hash:
                return True
            hash.add(nums[i])
        return False
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值