Leetcode101 排序算法

109 阅读4分钟

虽然在 C++ 里可以通过 std::sort() 快速排序,而且刷题时很少需要自己手写排序算法,但是熟习各种排序算法可以加深自己对算法的基本理解,以及解出由这些排序算法引申出来的题目。

在排序算法中,常见的有:冒泡、插入、选择、归并和快速排序算法。

虽然在 C++ 里可以通过 std::sort() 快速排序,而且刷题时很少需要自己手写排序算法,但是熟习各种排序算法可以加深自己对算法的基本理解,以及解出由这些排序算法引申出来的题目。

在排序算法中,常见的有:冒泡、插入、选择、归并和快速排序算法。

image.png

(1)Kth Largest Element in an Array

题目描述

在一个未排序的数组中,找到第 k 大的数字。

本人思路

在做这道题的时候,最开始我思考了一下能不能用快排解决,但是当时对时间的估算有点问题,计算出来的时候是O(nlogn),导致转向了更复杂的冒泡算法。显然时间没法达到O(n)要求,超时了。看了题解之后,照着题解撸了一下快速排序,但由于没打乱,导致还是超时了。个人觉得堆排序来解决这种k-th Element比较靠谱(其实和冒泡的思路有一点像)。

快速选择

这是题解所给出的思路。快速选择一般用于求解 k-th Element 问题,可以在 O(n) 时间复杂度,O(1) 空间复杂度完成求解工作。快速选择的实现和快速排序相似,不过只需要找到第 k 大的枢(pivot)即可,不需要对其左右再进行排序。与快速排序一样,快速选择一般需要先打乱数组,否则最坏情况下时间复杂度为 O()。

// 辅函数 - 快速选择
  int quickSelection(vector<int>& nums, int l, int r) {
      int i = l + 1, j = r;
      while (true) {
          while (i < r && nums[i] <= nums[l]) {
              ++i;
          }
          while (l < j && nums[j] >= nums[l]) {
              --j;
          }
          if (i >= j) {
              break;
          }
          swap(nums[i], nums[j]);
      }
      swap(nums[l], nums[j]);
      return j;
  }

  // 主函数
  int findKthLargest(vector<int>& nums, int k) {
      int l = 0, r = nums.size() - 1, target = nums.size() - k;
      while (l < r) {
          int mid = quickSelection(nums, l, r);
          if (mid == target) {
              return nums[mid];
          }
          if (mid < target) {
              l = mid + 1;
          } else {
              r = mid - 1;
          }
      }
      return nums[l];
  }

堆排序

int findKthLargest(vector<int>& nums, int k) {
    priority_queue<int, vector<int>, greater<int>> minHeap;

    for (int num : nums) {
        minHeap.push(num);
        if (minHeap.size() > k) {
            minHeap.pop();
        }
    }

    int result=minHeap.top();
    return result;
}

(2)347. Top K Frequent Elements (Medium)

题目描述

给定一个数组,求前 k 个最频繁的数字。

个人思路

这个题目也可以用快排解决,但我现在个人更倾向于堆排序。所以这里给出堆排序的题解。

代码展示

vector<int> topKFrequent(vector<int>& nums, int k) {
    unordered_map<int, int> freqMap;
    for (int num : nums) {
        freqMap[num]++;
    }

    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> minHeap;

    for (auto entry : freqMap) {
        minHeap.push({entry.second, entry.first});
        if (minHeap.size() > k) {
            minHeap.pop();
        }
    }

    vector<int> result;
    while (!minHeap.empty()) {
        result.push_back(minHeap.top().second);
        minHeap.pop();
    }
    
    reverse(result.begin(), result.end()); // 将结果逆序,得到频率前 k 高的元素
    return result;
}

总结

今天的主题虽然说的是排序,但其实都是同一类问题:k-th Element问题。对于这类问题,快排固然可以解决,但要考虑到最差情况和打乱排序的问题,个人还是更偏向于堆排序一点。

(1)Kth Largest Element in an Array

题目描述

在一个未排序的数组中,找到第 k 大的数字。

本人思路

在做这道题的时候,最开始我思考了一下能不能用快排解决,但是当时对时间的估算有点问题,计算出来的时候是O(nlogn),导致转向了更复杂的冒泡算法。显然时间没法达到O(n)要求,超时了。看了题解之后,照着题解撸了一下快速排序,但由于没打乱,导致还是超时了。个人觉得堆排序来解决这种k-th Element比较靠谱(其实和冒泡的思路有一点像)。

快速选择

这是题解所给出的思路。快速选择一般用于求解 k-th Element 问题,可以在 O(n) 时间复杂度,O(1) 空间复杂度完成求解工作。快速选择的实现和快速排序相似,不过只需要找到第 k 大的枢(pivot)即可,不需要对其左右再进行排序。与快速排序一样,快速选择一般需要先打乱数组,否则最坏情况下时间复杂度为 O()。

// 辅函数 - 快速选择
  int quickSelection(vector<int>& nums, int l, int r) {
      int i = l + 1, j = r;
      while (true) {
          while (i < r && nums[i] <= nums[l]) {
              ++i;
          }
          while (l < j && nums[j] >= nums[l]) {
              --j;
          }
          if (i >= j) {
              break;
          }
          swap(nums[i], nums[j]);
      }
      swap(nums[l], nums[j]);
      return j;
  }

  // 主函数
  int findKthLargest(vector<int>& nums, int k) {
      int l = 0, r = nums.size() - 1, target = nums.size() - k;
      while (l < r) {
          int mid = quickSelection(nums, l, r);
          if (mid == target) {
              return nums[mid];
          }
          if (mid < target) {
              l = mid + 1;
          } else {
              r = mid - 1;
          }
      }
      return nums[l];
  }

堆排序

int findKthLargest(vector<int>& nums, int k) {
    priority_queue<int, vector<int>, greater<int>> minHeap;

    for (int num : nums) {
        minHeap.push(num);
        if (minHeap.size() > k) {
            minHeap.pop();
        }
    }

    int result=minHeap.top();
    return result;
}

(2)347. Top K Frequent Elements (Medium)

题目描述

给定一个数组,求前 k 个最频繁的数字。

个人思路

这个题目也可以用快排解决,但我现在个人更倾向于堆排序。所以这里给出堆排序的题解。

代码展示

vector<int> topKFrequent(vector<int>& nums, int k) {
    unordered_map<int, int> freqMap;
    for (int num : nums) {
        freqMap[num]++;
    }

    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> minHeap;

    for (auto entry : freqMap) {
        minHeap.push({entry.second, entry.first});
        if (minHeap.size() > k) {
            minHeap.pop();
        }
    }

    vector<int> result;
    while (!minHeap.empty()) {
        result.push_back(minHeap.top().second);
        minHeap.pop();
    }
    
    reverse(result.begin(), result.end()); // 将结果逆序,得到频率前 k 高的元素
    return result;
}

总结

今天的主题虽然说的是排序,但其实都是同一类问题:k-th Element问题。对于这类问题,快排固然可以解决,但要考虑到最差情况和打乱排序的问题,个人还是更偏向于堆排序一点。