1. 使用递归求数组的最大值
int process(const vector<int>& arr, int L, int R){
if(L == R) //base case
return arr[L];
int mid = L + ((R - L) >> 1); // 防止溢出
int leftMax = process(arr, L, mid);
int rightMax = process(arr, mid+1, R);
return max(leftMax, rightMax);
}
2. Master公式
子问题的规模相同
- 笔记
将一个大的无序数组有序,可以把大的数组分成两个,然后对这两个数组分别进行排序,之后在把这两个数组合并成一个有序的数组
✅tested
vector<int> mergeSort(vector<int>& nums) {
process(nums, 0, nums.size() - 1);
return nums;
}
void process(vector<int>& nums, int L, int R){
if(L == R){
return;
}
int mid = L + ((R - L) >> 1);
process(nums, L, mid);
process(nums, mid + 1, R);
merge(nums, L, mid, R);
}
void merge(vector<int>& nums, int L, int M, int R){
vector<int> help; //!额外空间复杂度
int p1 = L;
int p2 = M + 1;
while(p1 <= M && p2 <= R){
help.push_back(nums[p1] <= nums[p2] ? nums[p1++] : nums[p2++]);
}
while(p1 <= M){
help.push_back(nums[p1++]);
}
while(p2 <= R){
help.push_back(nums[p2++]);
}
for(int i = 0; i < help.size(); i++){
nums[L + i] = help[i];
}
}
小和问题
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。
例:
[1, 3, 4, 2, 5]
1: 0
3: 1
4: 1 + 3
2: 1
5: 1 + 3 + 4 + 2
all = 0 + 1 + 4 + 1 + 10 = 16
int merge(vector<int>& arr, int L, int M, int R){
vector<int> help;
int p1 = L;
int p2 = M + 1;
int res = 0;
while (p1 <= M && p2 <= R){
res += arr[p1] < arr[p2] ? (R - p2 + 1) * arr[p1] : 0;
// 相等时先拷贝右组
help.push_back(arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]);
}
while(p1 <= M){
help.push_back(p1++);
}
while (p2 <= R){
help.push_back(p2++);
}
for(int i = 0; i < help.size(); i++){
arr[L + i] = help[i];
}
return res;
}
int process(vector<int>& arr, int L, int R){
if(L == R){
return 0;
}
int mid = L + ((R - L) >> 1);
return process(arr, L, mid) + process(arr, mid + 1, R) + merge(arr, L, mid, R);
}
int smallSum(vector<int>& arr){
if(arr.size() < 2){
return 0;
}
return process(arr, 0, arr.size() - 1);
}
类似问题:逆序对
int mod = (int) 1e9 + 7;
int InversePairs(vector<int> data) {
if(data.size() < 2){
return 0;
}
return process(data, 0, data.size() - 1) % mod;
}
int process(vector<int>& data, int L, int R){
if(L == R){
return 0;
}
int mid = L + ((R - L) >> 1);
return process(data, L, mid) % mod +
process(data, mid + 1, R) % mod + merge(data, L, mid, R) % mod;
}
int merge(vector<int>& data, int L, int M, int R){
vector<int> help;
int p1 = L;
int p2 = M + 1;
int res = 0;
while(p1 <= M && p2 <= R){
res = (res + (data[p1] < data[p2] ? 0 : (R - p2 + 1))) % mod;
help.push_back(data[p1] < data[p2] ? data[p2++] : data[p1++]);
}
while(p1 <= M){
help.push_back(data[p1++]);
}
while(p2 <= R){
help.push_back(data[p2++]);
}
for(int i = 0; i < help.size(); i++){
data[L + i] = help[i];
}
return res % mod;
}
问题1
给定一个数组arr和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。要求时间复杂度为O(N),空间复杂度为O(1)。
分两种情况:
(1) arr[i] <= num,arr[i]和 <=区的下一个数交换,<=区右扩,i++
(2) arr[i] > num,i++
vector<int> partition1(vector<int>& arr, int num){
int less = -1; // <区右边界
int more = arr.size(); // >区左边界
int i = 0;
while (i < more){
if(arr[i] <= num){ //当前值 < 划分值
swap(arr, ++less, i++);
}else if(arr[i] > num){ //当前值 > 划分值
i++;
}
}
return {less + 1, more};
}
荷兰国旗问题:
给定一个数组arr和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。要求时间复杂度为O(N),空间复杂度为O(1)。
分三种情况:
(1) arr[i] < num,arr[i]和 <区下一个交换,<区右扩,i++
(2) arr[i] == num,i++
(3) arr[i] > num,arr[i]和 >区前一个交换,>区左扩,i不变
vector<int> partition2(vector<int> & arr, int num){
int less = -1; // <区右边界
int more = arr.size(); // >区左边界
int i = 0;
while (i < more){
if(arr[i] < num){ //当前值 < 划分值
swap(arr, ++less, i++);
}else if(arr[i] > num){ //当前值 > 划分值
swap(arr, --more, i);
}else{
i++;
}
}
return {less + 1, more};
}
快速排序:
1.0版本:把最后一个数作为划分值num,<=num的放在左边,>num的放在右边,将num与>num区域的第一个数做交换,左右分别递归
2.0版本:把最后一个数作为划分值num,<num的放在左边,=num的放中间,>num的放在右边,将num与>num区域的第一个数做交换,左右分别递归
时间复杂度:O(N^2)
3.0版本:在数组中随机选择一个数,与最后一个数交换作为划分值num
时间复杂度:O(NlogN)
✅tested
void quickSort(vector<int>& nums, int L, int R){
if(L < R){
srand((unsigned)time(NULL));
swap(nums, L + rand()%(R - L + 1), R); //在数组中随机选择一个数,与最后一个数交换作为划分值num
vector<int> p = partition(nums, L, R); //根据实际的数据状况决定
quickSort(nums, L, p[0] - 1);
quickSort(nums, p[1] + 1, R);
}
}
//返回值:等于区域的左边界和右边界
vector<int> partition(vector<int>& nums, int L, int R){
int less = L - 1; // <区右边界
int more = R; // >区左边界
while(L < more){
if(nums[L] < nums[R]){ //当前值 < 划分值
swap(nums, ++less, L++);
}
else if(nums[L] > nums[R]){ //当前值 > 划分值
swap(nums, --more, L);
}
else{
L++;
}
}
swap(nums, more, R);
return {less + 1, more};
}
void swap(vector<int>& nums, int i, int j){
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
逻辑上完全二叉树
-
定义:对一棵具有 n 个结点的二叉树按层序编号,编号为 i (1<=i<=n)的结点与同样深度的满二叉树中编号为 i 的结点在二叉树中位置完全相同
-
把从0出发连续的一段,想象成二叉树
-
i
- 左:2*i+1
- 右:2*i+2
- 父:(i - 1)/2
大根堆
- 堆结构(重要)
- 优先级队列结构 就是 堆结构
✅tested
// 某个数现在处在index位置,往上继续移动
void heapInsert(vector<int>& arr, int index){
while (arr[index] > arr[(index - 1) / 2]){ //当前的数大于父位置的数
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
//某个数在index位置,能否往下移动
void heapify(vector<int>& arr, int index, int heapSize){
int left = index * 2 + 1; //左孩子的下标
while (left < heapSize){
//两个孩子中,谁的值大,把下标给largest
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
//父和较大的孩子之间,谁的值大,把下标给largest
largest = arr[largest] > arr[index] ? largest : index;
if(largest == index){
break;
}
swap(arr, largest, index);
index = largest;
left = index * 2 + 1;
}
}
堆排序
先将整个数组变成大根堆,把最大值和堆的最后一个位置做交换,heapsize--,剩下的进行heapify,继续把最大值和堆的最后一个位置做交换,heapsize--
-
时间复杂度: O(NlogN)
-
额外空间复杂度: O(1)
✅tested
void heapSort(vector<int>& arr){
if(arr.size() < 2){
return;
}
// 用户一次只给一个数: O(NlogN)
for (int i = 0; i < arr.size(); i++){ //O(N)
heapInsert(arr, i); //O(logN)
}
// 用户一次给出所有的数: O(N)
// for (int i = arr.size() - 1; i >= 0; i--){
// heapify(arr, i, arr.size());
// }
int heapSize = arr.size();
swap(arr, 0, --heapSize); //0位置的数与堆上最后一个数做交换
while (heapSize > 0){ //O(N)
heapify(arr, 0, heapSize); //O(logN)
swap(arr, 0, --heapSize); //O(1)
}
}
小根堆
已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。
思路:假设k=6,准备一个小根堆,首先遍历前7个数,小根堆的最小值一定在0位置。
复杂度:O(N*logk)
void sortedArrDistanceLessK(vector<int>& arr, int k){
// 默认大根堆,greater -> 小根堆
priority_queue<int, vector<int>, greater<int>> q;
int index = 0;
for(; index <= min((int)arr.size(), k); index++){
q.push(arr[index]);
}
int i = 0;
for(; index < arr.size(); i++, index++){
q.push(arr[index]);
arr[i] = q.top();
q.pop();
}
while (!q.empty()){
arr[i++] = q.top();
q.pop();
}
}