[力扣刷题总结](排序篇)

在这里插入图片描述在这里插入图片描述

  • 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
  • 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序

算法复杂度:
在这里插入图片描述

在这里插入图片描述


归并排序

(1)基本思想
  归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

(2)分而治之
在这里插入图片描述
可以看到这种**结构很像一棵完全二叉树,**本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。

(3) 合并相邻有序子序列
  再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。
在这里插入图片描述在这里插入图片描述
(4)归并排序基于分治算法。最容易想到的实现方式是自顶向下的递归实现,时间复杂度: T(n)
归并算法是一个不断递归的过程,假设数组的元素个数是n。
时间复杂度是T(n)的函数: T(n) = 2*T(n/2) + O(n)

怎么解这个公式呢?
对于规模为n的问题,一共要进行log(n)层的大小切分;
每一层的合并复杂度都是O(n);
所以整体的复杂度就是O(nlogn)。

空间复杂度: O(n)
由于合并n个元素需要分配一个大小为n的额外数组,合并完成之后,这个数组的空间就会被释放。

void merge(vector<int>& nums, vector<int>& tmp, int l, int r) {
    if (l >= r) return;
    int m = l + (r - l) / 2;
    // 不断进行拆分
    merge(nums, tmp, l, m);
    merge(nums, tmp, m + 1, r);
    // 拆分完后开始进行排序
    int i = l, j = m + 1;
    for (int k = l; k <= r; k++) tmp[k] = nums[k];

    for (int k = l; k <= r; k++) {
        if (i == m + 1) nums[k] = tmp[j++];
        // 下面可以选择是降序还是升序排序
        else if (j == r + 1 || tmp[i] <= tmp[j]) nums[k] = tmp[i++];
        else nums[k] = tmp[j++];
    }
}

void mergerSort(vector<int>& arr){
    int n = arr.size();
    vector<int> tmp(n);
    merge(arr, tmp, 0, n-1);
}

剑指 Offer 51. 数组中的逆序对

力扣链接
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

示例 1:

输入: [7,5,6,4]
输出: 5

限制:

0 <= 数组长度 <= 50000

解法1:归并排序

思路:

这道题能够用归并排序做的原因是,在merge过程中可以计算出左右两个有序子数组的逆序对数。 而且,“排序”虽然修改了原数组的元素顺序,但是修改前已经统计了在排序前所有可能的逆序对数,所以不会产生错误。

本题的解法,只需要在归并排序判断nums[pos1] > nums[pos2]处加上一行统计代码即可(ret += (mid - pos1 + 1);)。为什么是这样统计的呢?假如有两个子数组:

1 6 8 9

1 3 4 5

在pos1指向6,pos2指向3时,按照归并排序思想要将3放入tmp数组。此时,以3结尾的逆序对为(6,3), (8, 3), (9, 3)共三个,计算方法为3 - 1 + 1 = 3(mid = 3, pos1 = 1)。

其余代码部分和归并排序代码并无二致。

代码:

class Solution {
public:
    int cnt = 0;
    int reversePairs(vector<int>& nums) {
        mergeSort(nums);
        return cnt;
    }
    void mergeSort(vector<int>& arr){
        int n = arr.size();
        vector<int> tmp(n);
        merge(arr, tmp, 0, n-1);
    }
    void merge(vector<int>& arr, vector<int>& tmp, int l, int r){
        if(l >= r) return;
        int mid = l + (r-l)/2;
        //分
        merge(arr,tmp,l,mid);
        merge(arr,tmp,mid+1,r);
        //合并
        int i = l, j = mid+1;
        for(int k = l;k<=r;k++) tmp[k] = arr[k];

        for(int k = l;k<=r;k++){
            if(i == mid+1) arr[k] = tmp[j++];
            //下面可以选择是降序还是升序
            else if(j == r+1 || tmp[i] <= tmp[j]) arr[k] = tmp[i++];
            else{
                arr[k] = tmp[j++];
                cnt += mid - i + 1;
            } 
        }                           
    }
};

在这里插入图片描述

解法2:桶排序

class Solution {
public:
    string frequencySort(string s) {
        unordered_map<char,int> umap;
        int maxFre = 0;
        int n = s.size();
        for(char& c:s){
            umap[c]++;
            maxFre = max(maxFre,umap[c]);
        }
        vector<string> buckets(maxFre+1);
        for(auto& [ch,cnt]:umap){
            buckets[cnt].push_back(ch);
        }
        string res;
        for(int i = maxFre;i>0;i--){
            string bucket = buckets[i];
            for(auto& ch:bucket){
                for(int k = 0;k<i;k++){
                    res.push_back(ch);
                }
            }
        }
        return res;
    }
};

在这里插入图片描述

969. 煎饼排序

力扣链接
给你一个整数数组 arr ,请使用 煎饼翻转 完成对数组的排序。

一次煎饼翻转的执行过程如下:

选择一个整数 k ,1 <= k <= arr.length
反转子数组 arr[0…k-1](下标从 0 开始)
例如,arr = [3,2,1,4] ,选择 k = 3 进行一次煎饼翻转,反转子数组 [3,2,1] ,得到 arr = [1,2,3,4] 。

以数组形式返回能使 arr 有序的煎饼翻转操作所对应的 k 值序列。任何将数组排序且翻转次数在 10 * arr.length 范围内的有效答案都将被判断为正确。

示例 1:

输入:[3,2,4,1]
输出:[4,2,4,3]
解释:
我们执行 4 次煎饼翻转,k 值分别为 4,2,4,和 3。
初始状态 arr = [3, 2, 4, 1]
第一次翻转后(k = 4):arr = [1, 4, 2, 3]
第二次翻转后(k = 2):arr = [4, 1, 2, 3]
第三次翻转后(k = 4):arr = [3, 2, 1, 4]
第四次翻转后(k = 3):arr = [1, 2, 3, 4],此时已完成排序。
示例 2:

输入:[1,2,3]
输出:[]
解释:
输入已经排序,因此不需要翻转任何内容。
请注意,其他可能的答案,如 [3,3] ,也将被判断为正确。

提示:

1 <= arr.length <= 100
1 <= arr[i] <= arr.length
arr 中的所有整数互不相同(即,arr 是从 1 到 arr.length 整数的一个排列)

解法1:类选择排序

思路:

设一个元素的下标是 index,我们可以通过两次煎饼排序将它放到尾部:

  1. 第一步选择k=index+1,然后反转子数组 arr[0…k−1],此时该元素已经被放到首部。

  2. 第二步选择k=n,其中 n 是数组 arr 的长度,然后反转整个数组,此时该元素已经被放到尾部。

通过以上两步操作,我们可以将当前数组的最大值放到尾部,然后将去掉尾部元素的数组作为新的处理对象,重复以上操作,直到处理对象的长度等于一,此时原数组已经完成排序,且需要的总操作数是2×(n−1),符合题目要求。如果最大值已经在尾部,我们可以省略对应的操作。

代码:

class Solution {
public:
    vector<int> pancakeSort(vector<int>& arr) {
        vector<int> result;
        int n = arr.size();
        for(int i = 0;i<n;i++){
            int index = max_element(arr.begin(),arr.end()-i) - arr.begin();
            if(index == n - i - 1) continue;//最大值已经在和合适的位置
            reverse(arr.begin(),arr.begin()+index+1);
            reverse(arr.begin(),arr.end()-i);
            result.push_back(index+1);
            result.push_back(arr.end()-i-arr.begin());
        }
        return result;
    }
};

在这里插入图片描述

桶排序

在这里插入图片描述

快速排序

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序

在这里插入图片描述

912. 排序数组

力扣链接
给你一个整数数组 nums,请你将该数组升序排列。

示例 1:

输入:nums = [5,2,3,1]
输出:[1,2,3,5]
示例 2:

输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]

提示:

1 <= nums.length <= 5 * 104
-5 * 104 <= nums[i] <= 5 * 104

解法1:快速排序(超时)

class Solution {
public:
    void qSort(vector<int>& nums, int l, int r){
                int p;
                if(l < r){
                    p = partition(nums,l,r);
                    qSort(nums,l,p-1);
                    qSort(nums,p+1,r);
                }
            }
    int partition(vector<int>& nums, int l, int r){
        int p = nums[l];
        while(l<r){
            while(l < r && nums[r] >= p) r--;
            swap(nums[l],nums[r]);
            while(l < r && nums[l] < p) l++;
            swap(nums[l], nums[r]);
        }
        return l;
    }
    vector<int> sortArray(vector<int>& nums) {
        //快速排序
        qSort(nums, 0, nums.size()-1);
        return nums;
    }
};

解法2:快速排序(随机优化)

class Solution {
public:
    void qSort(vector<int>& nums, int l, int r){
        int p;
        if(l < r){
            p = partition(nums,l,r);
            qSort(nums,l,p-1);
            qSort(nums,p+1,r);
        }
    }
    int partition(vector<int>& nums, int l, int r){//交换数组中子表的记录,使枢轴记录到位,并返回其所在位置
        int i = rand()%(r-l+1) + l;
        swap(nums[l],nums[i]);
        int p = nums[l];
        while(l<r){//从表的两端交替向中间扫描
            while(l < r && nums[r] >= p) r--;
            swap(nums[l],nums[r]);//比枢纽小的交换到低端
            while(l < r && nums[l] < p) l++;
            swap(nums[l], nums[r]);
        }
        return l;
    }
    vector<int> sortArray(vector<int>& nums) {
        //快速排序
        qSort(nums, 0, nums.size()-1);
        return nums;
    }
};

在这里插入图片描述

23. 合并K个升序链表

likou
给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:

输入:lists = []
输出:[]
示例 3:

输入:lists = [[]]
输出:[]

提示:

k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i] 按 升序 排列
lists[i].length 的总和不超过 10^4

解法1:分治

在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.size() == 0) return nullptr;
        return helper(lists,0,lists.size()-1);
    }
    ListNode* helper(vector<ListNode*>& lists, int start, int end){
        if(start == end) return lists[start];
        int mid = start + (end-start) / 2;
        ListNode* left = helper(lists,start,mid);
        ListNode* right = helper(lists,mid+1,end);
        return mergeTwoLists(left, right);
    }
    ListNode* mergeTwoLists(ListNode* a, ListNode* b){//合并
        if(a == nullptr) return b;
        else if (b == nullptr) return a;
        else if (a->val < b->val){
            a->next = mergeTwoLists(a->next, b);
            return a;
        }else{
            b->next = mergeTwoLists(a,b->next);
            return b;
        }
    }
};

在这里插入图片描述

解法2:两两合并

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.size() == 0) return nullptr;
        //两两合并
        ListNode* result = lists[0];
        for(int i = 1;i<lists.size();i++){
            result = mergeTwoLists(result,lists[i]);
        }
        return result;
    }

    ListNode* mergeTwoLists(ListNode* a, ListNode* b){
        if(a == nullptr) return b;
        else if (b == nullptr) return a;
        else if(a->val < b->val){
            a->next = mergeTwoLists(a->next,b);
            return a;
        }else{
            b->next = mergeTwoLists(a,b->next);
            return b;
        }
    }
};

在这里插入图片描述

解法3:最小堆

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    struct Node{
        int val;
        ListNode* ptr;
        bool operator < (const Node& r) const{
            return val > r.val;
        }
    };
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        priority_queue<Node> que;
        ListNode* head = new ListNode;
        ListNode* pre = head;

        //这里跟上一版不一样,不再是一股脑全部放到堆中
        //而是只把k个链表的第一个节点放入到堆中
        for(int i = 0;i<lists.size();i++){
            if (lists[i] != nullptr) que.push({lists[i]->val,lists[i]});
        }

        //之后不断从堆中取出节点,如果这个节点还有下一个节点,  
		//就将下个节点也放入堆中
        while(!que.empty()){
            pre->next = que.top().ptr;
            que.pop();
            pre = pre->next;
            if(pre->next != nullptr) que.push({pre->next->val,pre->next});
        }
        pre->next = nullptr;
        return head->next;
    }
};

在这里插入图片描述

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

力扣链接
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4

提示:

1 <= k <= nums.length <= 104
-104 <= nums[i] <= 104

解法1:快排

class Solution {
public:
    //快排思想
    int partition(vector<int>& nums, int l, int r){
        int i = rand() % (r-l+1) + l;
        swap(nums[i],nums[l]);
        int p = nums[l];
        while(l < r){
            while(l < r && nums[r] >= p ) r--;
            swap(nums[r],nums[l]);
            while(l < r && nums[l] < p ) l++;
            swap(nums[r],nums[l]);
        }
        return l;
    }
    int qSort(vector<int>& nums, int l, int r, int k){
        int p = partition(nums, l, r);
        if(p == nums.size() - k) return nums[p];
        else if(p < nums.size() - k) return qSort(nums, p+1, r, k);
        else return qSort(nums, l, p-1, k);
        
    }
    int findKthLargest(vector<int>& nums, int k) {
        return qSort(nums, 0, nums.size() - 1, k);
    }
};

在这里插入图片描述

解法2:最小堆

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        //小顶堆
        priority_queue<int,vector<int>,greater<int>> pq;
        for(int i = 0;i<k;i++){
            pq.push(nums[i]);
        }
        
        for(int i = k;i<nums.size();i++){
            if(nums[i] > pq.top()){
                pq.pop();
                pq.push(nums[i]);
            }
        }
        return pq.top();
    }
};
class Solution {
public:
    void heapAdjust(vector<int>& arr,int i, int heapSize){
        int l = i*2+1, r = i*2+2,largest = i;
        if(l < heapSize && arr[l] > arr[largest]) largest = l;
        if(r < heapSize && arr[r] > arr[largest]) largest = r;
        if(largest != i){
            swap(arr[i],arr[largest]);
            heapAdjust(arr,largest, heapSize);
        }
    }
    void buildHeap(vector<int>& arr, int heapSize){
        for(int i = heapSize/2;i>=0;i--){
            heapAdjust(arr, i, heapSize);
        }
    }
    int findKthLargest(vector<int>& nums, int k) {
        int heapSize = nums.size();
        buildHeap(nums,heapSize);
        for(int i = nums.size() - 1;i>=nums.size()-k+1;i--){
            swap(nums[0],nums[i]);
            --heapSize;
            heapAdjust(nums, 0, heapSize);
        }
        return nums[0];
    }
};

在这里插入图片描述

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姬霓钛美

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值