leetcode(3)排序系列题目

(1)912. 排序数组

python

快排(Quick Sort)

基本步骤为:

  • 挑选基准值:从数列中挑出一个元素,称为“基准”(pivot);
  • 分割(partition):重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,对基准值的排序就已经完成;
  • 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。

递归到最底部的判断条件是数列的大小是零或一,此时该数列显然已经有序。

基于“填坑”思路的实现:

  • 以(子)数组最左端元素为pivot;
  • 每次先从右侧(以 right 指代)开始找到小于pivot的元素,并将其填充到左侧空出的“坑”处,再从左侧(以 left 指代)开始找到大于pivot的元素,将其填充到右侧空出的“坑”处;
  • 当左右指针相遇时,将pivot放置于 left/right 处,此时得到了一个有效的分割:小于pivot的元素均在其左侧,大于pivot的元素均在其右侧(等于pivot的元素可放置于任何一边);
  • 对pivot两侧的子数组递归排序,直至子数组无法再分割。

    注意:随机化选择基准值pivot能够提高时间性能
归并排序 (Merge Sort)

归并排序采用经典的分治(divide-and-conquer)策略来对序列进行排序:

  • 「分」的阶段首先将序列一步步分解成小的子序列进行分段排序;
  • 「治」的阶段则将分段有序的子序列合并在一起,使得整个序列变得有序。

    以最后一步合并分段有序的子序列为例:

    设立两个指针 leftleft 和 rightright,分别指向左右两个待合并的已有序的子数组 nums[low,mid] 和 nums[mid+1,high]。 如图中所示,在合并左右两个子数组时,若 nums[right] < nums[left],则将当前更小的 nums[right]放入排序结果中。依此类推,即可得到最终排好序的数组。
堆排序(Heap Sort)

堆可看作是一个「完全二叉树」的结构

  • 大根堆/大顶堆:每个节点的值均大于等于其左右孩子节点的值;
  • 小根堆/小顶堆:每个节点的值均小于等于其左右孩子节点的值。


基本步骤:

  • 建堆:将待排序的数组初始化为大根堆(小根堆)。此时,堆顶的元素(即根节点)即为整个数组中的最大值(最小值)。
  • 交换和调整:将堆顶元素与末尾元素进行交换,此时末尾即为最大值(最小值)。除去末尾元素后,将其他 n−1 个元素重新构造成一个大根堆(小根堆),如此便可得到原数组 n个元素中的次大值(次小值)。
  • 重复步骤二,直至堆中仅剩一个元素,如此便可得到一个有序序列了。

对于「升序排列」数组,需用到大根堆;
对于「降序排列」数组,则需用到小根堆。

I. 构造大根堆
从最后一个「非叶子节点」为根节点的子树出发,从右往左、从下往上进行调整操作。

对于以某个非叶子节点的子树而言,其基本的调整操作包括:

  • 如果该节点大于等于其左右两个子节点,则无需再对该节点进行调整,因为它的子树已经是堆有序的;
  • 如果该节点小于其左右两个子节点中的较大者,则将该节点与子节点中的较大者进行交换,并从刚刚较大者的位置出发继续进行调整操作,直至堆有序。

例:对于 nums=[5,2,1,9,6,8],其包含 n=length(nums)=6 个元素,第一个非叶子节点为 n/2−1=2,对应的基本建堆(大根堆)步骤如下:

  1. 第一个非叶子节点 2:nums[2]<nums[5],即节点 2 小于其左子节点 5(其右子节点不存在),需要调整交换两者。如下图所示:
  2. 第二个非叶子节点 1:nums[1]<nums[3] 且 nums[1]<nums[4],即节点 1 均小于其左右子节点,但其左子节点 3 更大,因此需要调整交换节点 1 与较大的子节点 3。如下图所示:
  3. 第三个非叶子节点 0:nums[0]<nums[1] 且 nums[0]<nums[2],即节点 0 均小于其左右子节点,但其左子节点 1 更大,因此需要调整交换节点 0 与较大的子节点 1。如下图所示:

    然而,调整完节点 0 与 节点 1 后原子树的堆序已被打破,此时 nums[1]<nums[4],即节点 1 小于其右子节点 4,因此还需要继续对以节点 1 为根结点的子树继续进行调整,如下图:

    至此,全部的调整完毕,建立起了一个大根堆 nums=[9,6,8,2,5,1]

II. 排序
建立起一个大根堆后,便可以对数组中的元素进行排序了。总结来看,将堆顶元素与末尾元素进行交换,此时末尾即为最大值。除去末尾元素后,将其他 n−1n−1 个元素重新构造成一个大根堆,继续将堆顶元素与末尾元素进行交换,如此便可得到原数组 nn 个元素中的次大值。如此反复进行交换、重建、交换、重建,便可得到一个「升序排列」的数组。

对于大根堆 nums=[9,6,8,2,5,1],其堆排序基本步骤如下:

  1. 最大元素:此时堆顶元素为最大值,将其交换到末尾,如下所示:

    交换完成后,除去末尾最大元素,此时需要对堆进行重建,使得剩余元素继续满足大根堆的要求。如下所示:
  2. 次大元素:此时堆顶元素为待排序元素中的最大值(即原数组中的次大值),将堆顶元素交换到末尾,如下所示:

    交换完成后,除去末尾最大元素,此时需要对堆进行重建,使得剩余元素继续满足大根堆的要求(省略)。
  3. 第三大元素:此时堆顶元素为待排序元素中的最大值(即原数组中的第三大值),将堆顶元素交换到末尾,如下所示:

    交换完成后,除去末尾最大元素,此时需要对堆进行重建,使得剩余元素继续满足大根堆的要求(省略)。
  4. 第四大元素:此时堆顶元素为待排序元素中的最大值(即原数组中的第四大值),将堆顶元素交换到末尾,如下所示:

    交换完成后,除去末尾最大元素,此时需要对堆进行重建,使得剩余元素继续满足大根堆的要求(省略)。
  5. 次小元素(第五大元素):此时堆顶元素为待排序元素中的最大值(即原数组中的次小元素或第五大元素),将堆顶元素交换到末尾,如下所示:

    交换完成后,除去末尾最大元素,此时堆中仅剩一个元素,即为原数组中的最小值。
    至此,基于大根堆的升序排列完成,

c++

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();
        SelectSort(nums,n);
        //quickSort(nums,0,n-1,n);
        return nums;
    }
    //插入
    void InsertSort(vector<int>& nums,int n) {
        for(int i=0;i<n;i++) {
            int temp = nums[i];
            int j = i-1;
            while(j >= 0 && nums[j] >temp) {
                nums[j+1] = nums[j];
                j--;
            }
            nums[j+1] = temp;
        }
    }
    //折半插入
    void HInsertSort(vector<int>& nums,int n) {
        int i,j,low,high,mid;
        for( i=0;i<n;i++ ){
            int tmp = nums[i];
            low = 0;high = i-1;
            while(low<=high) {
                mid = low+(high-low)/2;
                if(nums[mid] > tmp){
                    high = mid - 1;
                }else{
                    low = mid + 1;
                }
            }
            for(j=i-1;j>=high+1;j--){
                nums[j+1] = nums[j];
            }
            nums[high+1] = tmp;
        }
    }
    //希尔
    void ShellSort(vector<int>& nums,int n){
        for(int dk = n/2;dk>=1;dk=dk/2){
            for(int i=dk;i<n;++i) {
                if(nums[i]<nums[i-dk]){
                    int tmp = nums[i],j;
                    for(j = i-dk;j>=0&&tmp<nums[j];j-=dk){
                        nums[j+dk] = nums[j];
                    }
                    nums[j+dk]=tmp;
                }
            }
        }
    }
    //冒泡
    void BubbleSort(vector<int>& nums,int n){
        for(int i=0;i<n-1;i++) {
            bool flag = false;
            for(int j=n-1;j>i;j--) {
                if(nums[j-1]>nums[j]){
                    swap(nums[j-1],nums[j]);
                    flag = true;
                }
            }
            if(flag == false){
                return ;
            }
        }
    }
    //快排
    void quickSort(vector<int>&nums ,int left,int right,int n){
        if(left<right){
            int pivot=patition(nums,left,right);
            quickSort(nums,left,pivot-1,n);
            quickSort(nums,pivot+1,right,n);
        }
    }
    int partition(vector<int>&nums,int low,int high){
        // int pivot=nums[low];//数组的第一个数为主元,会超时
        int pivot=random()%(high-low+1)+low;//随机选择主元的位置,注意是主元的位置
        int tmp=nums[low];
        nums[low]=nums[pivot];
        nums[pivot]=tmp;       //交换主元和第一个元素
        pivot=nums[low];      //注意这里才是主元
        while(low<high){
            //从后向前找比主元小的数
            while(low<high && nums[high]>=pivot)--high;
            nums[low]=nums[high];//把比主元小的数移到前面
            //从前向后找比主元大的数
            while(low<high && nums[low]<=pivot)++low;
            nums[high]=nums[low];//把比主元大的数移到后面
        }
        nums[low]=pivot;
        return low;
    }

    //简单选择
    void SelectSort(vector<int>& nums,int n) {
        for(int i=0;i<n-1;i++) {
            int min = i;
            for(int j=i+1;j<n;j++) {
                if(nums[j]<nums[min]) min = j;
               
            }
            if(min!=i) swap(nums[i],nums[min]);
        }
    }
    //堆排序
   void adjust(vector<int> &nums, int len, int index){
        int left = 2*index + 1; // index的左子节点
        int right = 2*index + 2;// index的右子节点

        int maxIdx = index;
        if(left<len && nums[left] > nums[maxIdx])     maxIdx = left;
        if(right<len && nums[right] > nums[maxIdx])     maxIdx = right;
 
    if(maxIdx != index)
    {
        swap(nums[maxIdx], nums[index]);
        adjust(nums, len, maxIdx);
    }
 
}
 
// 堆排序
    void HeapSort(vector<int> &nums, int size){
       for(int i=size/2 - 1; i >= 0; i--){
            adjust(nums, size, i);
        }
         for(int i = size - 1; i >= 1; i--){
                swap(nums[0], nums[i]);        
                adjust(nums, i, 0);              
            }
        }
};

(2)剑指 Offer 45. 把数组排成最小的数

1、冒泡排序
2、选择排序
3、插入排序
4、希尔排序
5、归并排序-自顶向下
6、归并排序-自底向上
7、快速排序
8、快速排序-三向切分

(3)剑指 Offer 40. 最小的k个数

2023.06.08腾讯一面
快排,按顺序返回的写法,时间复杂度是O(nlogn):
子数组长度为 1 时终止递归

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        def quickSort(arr, l, r):
            if l >= r: return
            i, j = l, r
            while i < j:
                while i < j and arr[j] >= arr[l]: j -= 1
                while i < j and arr[i] <= arr[l]: i += 1
                arr[i], arr[j] = arr[j], arr[i]
            arr[l], arr[i] = arr[i], arr[l]
            quickSort(arr, l, i - 1)
            quickSort(arr, i + 1, r)
        quickSort(arr, 0, len(arr) - 1)
        return arr[:k]

不用按顺序返回的写法,时间复杂度是O(n):

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        if k >= len(arr): return arr
        def quickSort(l, r):
            i, j = l, r
            while i < j:
                while i < j and arr[j] >= arr[l]: j -= 1
                while i < j and arr[i] <= arr[l]: i += 1
                arr[i], arr[j] = arr[j], arr[i]
            arr[l], arr[i] = arr[i], arr[l]
            if k < i: quickSort(l, i - 1)
            if k > i: quickSort(i + 1, r)
            return arr[:k]
        return quickSort(0, len(arr) - 1)

(4)215. 数组中的第K个最大元素

1.快排,因为要取第k大的,从大到小排列,左边是大的右边是小的,

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        n = len(nums)
        left, right = 0, n - 1
        while True:
            idx = self.partition(nums, left, right)
            if idx == k - 1:
                return nums[idx]
            elif idx < k - 1:
                left = idx + 1
            else:
                right = idx - 1

    def partition(self, nums, left, right):
        begin = left
        pivot = nums[left]
        while left < right:
            while left < right and nums[right] <= pivot:
                right -= 1
            while left < right and nums[left] >= pivot:
                left += 1
            if left < right:
                nums[left], nums[right] = nums[right], nums[left]
        nums[left], nums[begin] = nums[begin], nums[left]
        return left

2.使用优先队列构造小根堆

3.直接调用大根堆

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        q = []
        for n in nums:
            heappush(q, -n)
        while k:
            res = -heappop(q)
            k -= 1
        return res

969. 煎饼排序

数组 arr 元素由 1 到 n(n 为数组长度)内的所有自然数组成,对数组 arr 进行排序后,数组中每个元素 i 的位置是确定的,即 i - 1。于是,我们可以从大 (n) 到小 (1),将元素 i (i = n, n - 1, ……, 1) 放置到最终的排序位置 i - 1。对每个元素 i,我们最多可由两次翻转实现:

  • 当元素 i 的索引为 i - 1,即元素 i 位于最终的排序位置时,此时不做翻转,continue 进入下次循环,对元素 i - 1 进行排序。
  • 当元素 i 的索引不是 i - 1 时,此时我们需要进行如下翻转:
对元素 5 进行排序,现在我们将原数组 arr = [1, 2, 5, 3, 4, 6, 7] 分成三部分看:
一部分是已排序部分 [6, 7],一部分是待排序元素及其之前的部分 [1, 2, 5],最后剩余部分为 [3, 4]
    我们对比翻转前和翻转后的数组:
    原数组:arr = [1, 2, 5,   3, 4,   6, 7]
    翻转后:arr = [4, 3,   1, 2, 5,   6, 7]
    不难发现,由原数组到翻转后数组,可由两步到达,
    第一步,将 [3, 4] 翻转,得到 [4, 3],放到数组的最前面
    第二步, [6, 7] 不动,将 [1, 2, 5] 整体后移,最终得到 [4, 3, 1, 2, 5, 6, 7]
class Solution:
    def pancakeSort(self, arr: List[int]) -> List[int]:
        n = len(arr)
        res = []
        for i in range(n, 0, -1):
            idx = arr.index(i)
            if idx == i - 1:
                continue
            res += [idx + 1, i]
            arr = arr[idx + 1: i][::-1] + arr[0:idx + 1] + arr[i + 1:]
        return res

参考资料:
写一下常见的排序算法吧!!!
912.排序数组 题目评论
各种常用排序算法的时间复杂度和空间复杂度
经典排序算法的时间复杂度和空间复杂度
『 3种排序一网打尽 』 快速排序、归并排序和堆排序详解
数据结构可视化
【宫水三叶】冒泡排序运用题

posted @ 2022-05-06 15:17  YTT77  阅读(230)  评论(0编辑  收藏  举报