排序
- 冒泡排序
基本思想:第一次循环将最大的元素放在arr[n-1],第二次循环将第二大的元素放在arr[n-2],最多需要n-1次循环
void bubble_sort(vector<int> &nums) {
int n = nums.size();
bool swapped;
for (int i = 1; i < n; ++i) {
swapped = false;
for (int j = 0; j < n - i; ++j) {
if (nums[j] > nums[j + 1]) {
nums[j] = nums[j] ^ nums[j + 1];
nums[j + 1] = nums[j] ^ nums[j + 1];
nums[j] = nums[j] ^ nums[j + 1];
swapped = true;
}
}
if (!swapped) break; // 说明此时数组已有序,无需执行后续的循环
}
}
- 选择排序
基本思想:第一次循环后将最小的元素放在arr[0],第二次循环后将第二小的元素放在arr[1],总共需要n-1次循环
void selection_sort(vector<int> &nums) {
int n = nums.size(), min;
for (int i = 0; i < n - 1; ++i) {
min = i;
for (int j = i + 1; j < n; ++j) {
if (a[j] < a[min]) {
min = j;
}
}
if (min != i) {
nums[i] = nums[i] ^ nums[min];
nums[min] = nums[i] ^ nums[min];
nums[i] = nums[i] ^ nums[min];
}
}
}
- 快速排序
基本思想:每次遍历时将比base小的元素放在左边,比base大的元素放在右边,然后递归遍历两个子数组。
void quick_sort(vector<int> &nums, int l, int r) {
if (l >= r)return;
int base = nums[l], start = l, end = r;
while (l < r) {
while (r > l && nums[r] >= base) {
r--;
}
nums[l] = nums[r];
while (l < r && nums[l] <= base) {
l++;
}
nums[r] = nums[l];
}
nums[l] = base; // 此时nums[l]左边的数都小于base, 右边的数都大于base
quick_sort(nums, start, l - 1);
quick_sort(nums, l + 1, end);
}
- 归并排序
基本思想:将一个数组分割成两个有序的子数组,然后将其合并
void merge_sort(vector<int> &nums, int l, int r) {
if (l >= r) return;
int mid = (l + r) / 2;
merge_sort(nums, l, mid); //对左半子序列排序
merge_sort(nums, mid + 1, r); //对右半子序列排序
mergeTwoSortedArr(nums, l, r, mid); //合并两个有序的子序列
}
void mergeTwoSortedArr(vector<int> &nums, int l, int r, int mid) {
vector<int> tmp(r - l + 1);
int i = l, j = mid + 1, index = 0;
while (i <= mid || j <= r) {
if (j > r || i <= mid && nums[i] < nums[j]) {
tmp[index++] = nums[i++];
} else {
tmp[index++] = nums[j++];
}
}
for (int i = 0; i < tmp.size(); ++i) {
nums[l + i] = tmp[i];
}
}
- 插入排序
基本思想:每次都将一个元素插入前面已经排好序的序列中
void insertion_sort(vector<int> &nums) {
int tmp, j;
for (int i = 1; i < nums.size(); ++i) {
if (nums[i] >= nums[i - 1])continue;
tmp = nums[i];
for (j = i - 1; j >= 0 && nums[j] > tmp; --j) {
nums[j + 1] = nums[j];
}
nums[j + 1] = tmp;
}
}
- 堆排序
基本思想:将数组构造成大顶堆,每次取出根节点最为最大元素放在数组的最后
void heap_sort(vector<int> &nums) {
int size = nums.size();
//将数组转化成一颗完全二叉树,每个父节点和左子节点、右子节点的索引关系为 left=2*a+1;right=2*a+2
//最后一个非叶子节点的索引是size/2 - 1
for (int i = size / 2 - 1; i >= 0; --i) {
maxHeapify(nums, i, size - 1); // 构造大顶堆,从最后一个非叶子节点开始往前遍历,直到将整个数组构造成一个大顶堆
}
for (int i = 1; i < size; ++i) {
//此时nums[0]即为整个数组中的最大值,将其放在数组结尾
swap(nums[0], nums[size - i]);
maxHeapify(nums, 0, size - 1 - i); // 根节点变了,重新构造大顶堆,只需调整一次即可
}
}
// 构造大顶堆, rootIndex为根节点下标, lastIndex为最后一个节点下标
void maxHeapify(vector<int> &nums, int rootIndex, int lastIndex) {
int left = 2 * rootIndex + 1, right = 2 * rootIndex + 2, largestIndex = rootIndex;
if (left <= lastIndex && nums[left] > nums[largestIndex]) {
largestIndex = left;
}
if (right <= lastIndex && nums[right] > nums[largestIndex]) {
largestIndex = right;
}
if (largestIndex != rootIndex) {
// 交换nums[largestIndex]和nums[rootIndex]
swap(nums[largestIndex], nums[rootIndex]);
//交换后可能会破坏原有的大顶堆,因此需要继续调整
maxHeapify(nums, largestIndex, lastIndex);
}
}
- 数组中的第K个最大元素
解法1:优先队列
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int> pq;
for (auto& num : nums) {
pq.push(num);
}
for (int i = 0; i < k - 1; ++i) {
pq.pop();
}
return pq.top();
}
解法2:参考堆排序
int findKthLargest(vector<int> &nums, int k) {
int n = nums.size();
for (int i = n / 2 - 1; i >= 0; --i) {
maxHeapify(nums, i, n - 1);
}
for (int i = 0; i < k; ++i) { // 无需遍历n-1次,只需遍历k次
swap(nums[0], nums[n - i - 1]);
maxHeapify(nums, 0, n - i - 2);
}
return nums[n - k];
}
void maxHeapify(vector<int> &nums, int rootIndex, int lastIndex) {
int left = 2 * rootIndex + 1, right = 2 * rootIndex + 2, largestIndex = rootIndex;
if (left <= lastIndex && nums[left] > nums[largestIndex]) {
largestIndex = left;
}
if (right <= lastIndex && nums[right] > nums[largestIndex]) {
largestIndex = right;
}
if (largestIndex != rootIndex) {
swap(nums[rootIndex], nums[largestIndex]);
maxHeapify(nums, largestIndex, lastIndex);
}
}
解法3:参考快速排序
int findKthLargest(vector<int> &nums, int k) {
return quickSelection(nums, 0, nums.size() - 1, nums.size() - k);
}
// 寻找将nums[start]-nums[end]排好序后下标为target的元素
int quickSelection(vector<int> &nums, int start, int end, int target) {
int idx = start + rand() % (end - start + 1);
swap(nums[idx], nums[start]);
int left = start, right = end, base = nums[start]; // base是每次进行快速排序时所使用的基准数,通过随机的方式来提高性能(也可以删除前面两行)
while (left < right) {
while (left < right && nums[right] >= base) {
--right;
}
nums[left] = nums[right];
while (left < right && nums[left] <= base) {
++left;
}
nums[right] = nums[left];
}
nums[left] = base;
if (left == target) {
return base;
} else if (left > target) {
return quickSelection(nums, start, left - 1, target);
} else {
return quickSelection(nums, left + 1, end, target);
}
}
解法4:将数组分为大于base/小于base/等于base的3个集合,然后根据集合的元素个数来判断第k大的数处在哪个集合中
int findKthLargest(vector<int>& nums, int k) {
int base= nums[0]; // base可以随机取,base = nums[rand()%nums.size()]
vector<int> bigger, smaller, equal;
for (auto& num : nums) {
if (num > base) {
bigger.push_back(num);
} else if (num < base) {
smaller.push_back(num);
} else {
equal.push_back(num);
}
}
if (bigger.size() >= k) {
return findKthLargest(bigger, k);
} else if (k > nums.size() - smaller.size()) {
return findKthLargest(smaller, k - nums.size() + smaller.size());
}
return base;
}
- 前 K 个高频元素
解法1:暴力求解,构造元素及其出现次数的映射集合,然后转成vector,按照出现次数从大到小排序,然后取出前k个
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> m;
for (auto& num : nums) {
m[num]++;
}
vector<pair<int, int>> v(m.begin(), m.end());
sort(v.begin(), v.end(),
[&](const pair<int, int>& a, const pair<int, int>& b) {
return a.second > b.second;
});
vector<int> res;
for (int i = 0; i < k; ++i) {
res.push_back(v[i].first);
}
return res;
}
解法2:桶排序,用buckets[i]保存nums中出现了i次的元素。从后往前遍历bucket,取出前k个元素
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> m; // 保存nums中元素及其出现次数
int maxCount = 0; // 记录出现频率最高是多少次, 以便确定bucket数组的长度
for (auto& num : nums) {
maxCount = max(maxCount, ++m[num]);
}
vector<vector<int>> bucket(maxCount +
1); // bucket[i]保存所有出现了i次的元素
for (auto& p : m) {
bucket[p.second].push_back(p.first);
}
vector<int> ans;
for (int i = maxCount; i >= 0; --i) {
for (auto& x : bucket[i]) {
ans.push_back(x);
if (ans.size() == k)
return ans;
}
}
return ans;
}
- 根据字符出现频率排序
参考347. 前 K 个高频元素的解法2:桶排序
string frequencySort(string s) {
unordered_map<char, int> count;
int maxCount = 0;
for (auto& c : s) {
maxCount = max(maxCount, ++count[c]);
}
vector<vector<char>> buckets(maxCount + 1);
for (auto& p : count) {
buckets[p.second].push_back(p.first);
}
string res = "";
for (int i = maxCount; i >= 0; --i) {
for (auto& c : buckets[i]) {
res.append(i, c);
}
}
return res;
}
- 颜色分类
解法1:单指针
void sortColors(vector<int>& nums) {
int ptr = 0;
//第一次遍历后,[0,ptr-1]全部为0
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] == 0) {
nums[i] = nums[ptr];
nums[ptr++] = 0;
}
}
//第二次从ptr开始遍历,把1全部放到前面
for (int i = ptr; i < nums.size(); ++i) {
if (nums[i] == 1) {
nums[i] = nums[ptr];
nums[ptr++] = 1;
}
}
}
解法2:双指针,一次遍历
void sortColors(vector<int> &nums) {
int p0 = 0, p1 = 0; // p0指向所有0的下一个元素, p1指向所有1的下一个元素
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] == 0) {
nums[i] = nums[p0];
nums[p0] = 0;
if (p0 < p1) { // 说明之前p0处的元素是1, 并且被换到了i处, 所以要将这个1换回来
nums[i] = nums[p1];
nums[p1] = 1;
}
++p0;
++p1;
} else if (nums[i] == 1) {
nums[i] = nums[p1];
nums[p1] = 1;
++p1;
}
}
}
解法3:双指针
void sortColors(vector<int>& nums) {
int p0 = 0, p2 = nums.size() - 1;
for (int i = 0; i <= p2; ++i) {
while (i <= p2 && nums[i] == 2) {
nums[i] = nums[p2];
nums[p2] = 2;
--p2;
}
if (nums[i] == 0) {
nums[i] = nums[p0];
nums[p0] = 0;
++p0;
}
}
}