目录
- 经典算法
- 快速排序
- 核心思想
- cpp代码
- 二分查找
- 核心思想
- cpp代码
- 具体题目
- 荷兰旗问题-颜色分类 (leetcode75)
- 思路
- cpp代码
- 数组中的第K个最大元素 (leetcode215)
- 思路:快速选择
- cpp代码
经典算法
快速排序
经典面试手撕题,刚好明天又要面试百度了,先复习一下~
核心思想
快速排序算法的核心其实就是“分而治之”。
给定一个数组int a = {2, 6, 3, 8, 1, 5, 4}
,我们首先选择一个基准元素,将问题分割成两个子问题:将比基准元素小的值放左边,将比基准元素大的值放右边,那么我们需要排序的整体数组就变成了两个更短的数组,进而我们分别对左右两边的子数组继续干上面相同的事情,直到分割到最后的子数组中只有一个元素,这样的子数组必定是有序的,进而我们的整个数组也就排序好了。实际上我们分析的就是一个递归过程。
那么“分”的思路我们想好了,该如何“治”呢?
从上面的分析我们可以得出,要写一个快速排序算法,我们需要知道当前处理子数组的左右边界/index,也就是int left
和int right
。
假定我们每一次对子数组进行处理的时候,基准元素都是左边界第一个数,我们将它拿出来key = a[left]
,将第一个位置看做成一个“坑”来存放之后需要调整到前面来的数。
那么我们首先从后往前去找比key
小的数,将它放在a[left]
里,这样这个坑就完成了它的使命,但是由于我们将后面的a[right]
中的数拿出来,这里又变成了一个坑,那么我们下面就要反过来从前往后去找比key
大的数,将它填在a[right]
里。依此类推,这就是一个填坑游戏,哈哈~
cpp代码
具体cpp代码如下:
#include <iostream>
#include <vector>
using namespace std;
void quickSort(int left, int right, vector<int>& a){
// 递归终止条件
if(left >= right){
return;
}
int l = left, r = right;
int key = a[l];
while(l < r){
// 从后往前找比key小的数
while(l < r && a[r] >= key){
r--;
}
if(l < r){
a[l++] = a[r];
}
// 从前往后找比key大的数
while(l < r && a[l] <= key){
l++;
}
if(l < r){
a[r--] = a[l];
}
}
// 放置key的位置
a[l] = key;
// 继续递归处理左右两边子数组
quickSort(left, l-1, a);
quickSort(l+1, right, a);
}
int main() {
vector<int> a = {10, 7, 2, 6, 5, 11, 8};
quickSort(0, a.size()-1, a);
for(int i = 0; i < a.size(); ++i){
cout << a[i] << " ";
}
cout << endl;
return 0;
}
二分查找
哈哈哈昨天刚写完快速排序,今天面试面试官反倒让我写了二分查找,这就来更新!
题目🔗
核心思想
对于一个有序数组,其索引范围是[left, right]
,我们想查询一个元素target
是否存在:
首先查询当前中间元素nums[mid]
的大小:
- 如果nums[mid] < target
,则说明target
在nums[mid]
右边,进而去(mid, right]
中找
- 如果nums[mid] > target
,则说明target
在nums[mid]
左边,进而去[left, mid)
中找。
依据上面的分析,实际上就是一个循环的过程,直到我们找到这个target
。
cpp代码
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while(left <= right){
// 找到中间元素索引
int mid = (left + right) / 2;
if(nums[mid] < target){
// 当中间元素小于target
left = mid + 1;
}
else if(nums[mid] > target){
// 当中间元素大于target
right = mid - 1;
}
// 当中间元素等于target,直接返回索引
else return mid;
}
return -1;
}
};
具体题目
荷兰旗问题-颜色分类 (leetcode75)
题目🔗
荷兰国旗是由红白蓝三种颜色条纹拼接而成的,如下图所示。
如果我们给定若干条这样的条纹并且随机拼接(下图情况),请你把他们按照荷兰国旗颜色进行排序。
具体题目描述如下:
给定一个包含红色、白色和蓝色、共 n
个元素的数组 nums
,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0
、 1
和 2
分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:
输入:nums = [2,0,1]
输出:[0,1,2]
提示:
n == nums.length
1 <= n <= 300
nums[i]
为 0
、1
或 2
思路
这题是不是和快排很像?我们如果把小于基准元素的值放在左边,把等于基准元素的值放在中间,把大于基准元素的值放在右边,就完美解决了。
但是如何实现“把等于基准元素的值放在中间”呢?
因为这题只是进行一趟排序,所以没有递归的过程,那么我们可以用一个cur
指针来遍历数组中的元素,同样,我们也有left
和right
,分别记录中间和左边、中间和右边两个区间的边界。
那么当我们的cur
碰到比key
大的元素,就把它和right
前一个数进行交换,并且right--
。同理,当cur
碰到比key
小的元素,就把它和left
后一个数进行交换,并且left++, cur++
。
重点是,当cur
碰到和key
相等的元素时,就跳过,将它留在中间区域,这样就实现了“把等于基准元素的值放在中间”。
可能有细心的小伙伴就发现了,欸这里的right--
怎么没有cur++
呢?这是因为我们在交换的时候可能会把后面的另一个大数给交换回来,如果我们进行cur++
,就跳过了这次比较,有漏网之鱼了哦。
cpp代码
class Solution {
public:
void sortColors(vector<int>& nums) {
int left = -1, right = nums.size();
int key = 1;
int cur = 0;
while(cur < right){
if(nums[cur] < key){
swap(nums[cur++], nums[++left]);
}
else if(nums[cur] > key){
swap(nums[cur], nums[--right]);
}
else{
cur++;
}
}
}
};
数组中的第K个最大元素 (leetcode215)
题目🔗
给定整数数组 nums
和整数 k
,请返回数组中第 k
个最大的元素。
请注意,你需要找的是数组排序后的第 k
个最大的元素,而不是第 k
个不同的元素。
你必须设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例 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 <= 105
-104 <= nums[i] <= 104
思路:快速选择
其实回看每一轮快速排序,就已经确定基准元素的位置,那么我们就可以把快排简化成,去确定第k-1
个位置的数值(这里我们是从大到小进行排列)。
这里我们要考虑基准元素的选取问题,为了避免当选择边界作为基准,对于已经有序的区间而出现时间复杂度退化的情况,我们选取中间元素作为基准,也即int mid = left + (right - left) / 2;
,同时将它和边界最右侧的元素进行互换swap(nums[mid], nums[right])
,保证它在边界处不会被覆盖,这样我们的基准元素就变为了nums[right]
。
我们用int l
和int r
分别从左右两侧进行判断,l
从左侧找比nums[right]
小的数,然后r
从右侧找比nums[right]
大的数,最后将两数位置交换,变成有序的状态。
那么最后的基准元素nums[right]
应该放哪呢?
我们结束循环的条件是l == r
,且最后一定是以l
的while循环结果结束,那么这个时候r
一定是指向比nums[right]
小的数,因为在[r, right)
区间中,所有数都是满足比nums[right]
小的。现在l == r
,说明l
和r
都指向比nums[right]
小的数,那我们直接把nums[right]
和num[l]
交换就可以了。
然后我们通过判断这一轮确定下来的基准元素位置和k
的大小,再去选择性的进行下一次排序确定,最后当某一轮确定的基准元素位置和k
相等时,我们就可以返回结果了。
cpp代码
class Solution {
private:
int quickSortKthElement(vector<int>& nums, int k, int left, int right){
// 选择中间元素作为基准元素,避免有序情况下的退化
int mid = left + (right - left) / 2;
swap(nums[mid], nums[right]); // 将基准元素放在最右边,防止被覆盖
int l = left, r = right;
while(l < r){
while(l < r && nums[l] >= nums[right]) l++; // 循环直到找到左边部分比基准元素小的
while(l < r && nums[r] <= nums[right]) r--; // 循环直到找到右边部分比基准元素大的
if(l < r){
swap(nums[l], nums[r]); // 交换大小元素
}
}
swap(nums[l], nums[right]); // 最后将基准元素放在l的位置
if(l == k - 1) return nums[l];
else if(l > k -1) return quickSortKthElement(nums, k, left, l - 1);
else return quickSortKthElement(nums, k, l + 1, right);
};
public:
int findKthLargest(vector<int>& nums, int k) {
return quickSortKthElement(nums, k, 0, nums.size() - 1);
}
};