平均时间复杂度:n * log 2 n
(1)确定分界点x:q[l] 或 q[(l + r) / 2] 或 q[r] 或 随机
(2)调整区间:保证q[k]左边的数都小于等于x,q[k]右边的数都大于等于x,q[k]不一定等于x。
(3)在两个区间重复(1)(2)步
不需要开辟额外空间的做法:设两个指针i,j,i指向最左边的数,j指向最右边的数,i向右移,当遇到大于x的数时停下,j向左移,当遇到小于x的数时停下,然后交换i,j所指向的数。i,j以这一规则接着移动,直到i,j相遇为止。然后再分别对i,j分界点的两边进行此算法。
class Solution {
public:
void quick_sort(int i,int j,vector& nums){
int m = j - i;
if(m < 0 || m == 0){
return;
}
int x = nums[(i + j) >> 1];//参照标准
int p = i;
int q = j;
j++;//因为要先j--再判断,所以先加一
i--;//因为要先i++再判断,所以先减一
while(i < j){
do{
i++;
}while(nums[i] < x);
//nums[i] x);
if(i < j){
int a = nums[i];
nums[i] = nums[j];
nums[j] = a;
}
}
quick_sort(p,j,nums);
quick_sort(j + 1,q,nums);
/*
这里不能取
sort(p,i - 1,nums);
sort(i,q,nums);
会陷入死循环,如[0,1],
若想取i,则改变x的值向上取整
*/
return;
}
vector sortArray(vector& nums) {
int n = nums.size();
int i = 0;
int j = n - 1;
quick_sort(i,j,nums);
return nums;
}
};
题目:给定一个长度为n的整数数列,以及一个整数k,请用快速选择算法求出数列的第k小的数是多少(假设这个数一定在区间内)。
进行一次快速排序时,假设分界点左边元素个数为SL,右边元素个数为SR,此时分为两种情况:
(1)k <= SL,递归左边
(2)k > SL,递归右边,k = k - SL
时间复杂度:n + n / 2 + n / 4 + ... = n(1 + 1/2 + 1/4 + 1/8 + ...) <= 2n,所以时间复杂度为O(n)。
#include
using namespace std;
const int N = 100010;
int n, k;
int q[N];
int quick_sort(int l, int r, int k){
if(l == r) return q[l];//因为保证k对应的值始终在区间内,所以区间内至少有一个值,所以可以不写成l >= r
int x = q[l], i = l - 1, j = r + 1;
while(i < j){
while(q[++i] < x);
while(q[--j] > x);
if(i < j) swap(q[i], q[j]);
}
int sl = j - l + 1;
if(k <= sl) return quick_sort(l, j, k);
return quick_sort(j + 1, r, k - sl);
}
int main(){
cin >> n >> k;
for(int i = 0; i < n; i++) cin >> q[i];
cout << quick_sort(0, n - 1, k) << endl;
return 0;
}
时间复杂度:n * log 2 n
(1)确定分界点mid:将整个数组平均分为左右两半。
mid = (l + r) / 2,两个区间[l,mid],[mid + 1,r]。
(2)对这两半分别进行递归排序:每次分两半,直到每对每组大小为1。
n除以2多少次等于1呢?log 2 n次,所以这一操作时间复杂度为O(log 2 n)。
(3)对于每对的两个有序数组 合并为一个有序数组:设i,j两个指针分别指向两个数组的第一个数,比较这两个指针所指向的数的大小,将小的那个取出,相应指针后移一位,再比较这两个指针所指向的数的大小,以此类推,直到一个指针移到数组的末尾,将另一个指针指向的数及其之后的数放到答案数组的最后面即可。
class Solution {
public:
void merge_sort(vector& nums,int l,int r){//归并排序
//数组中没有数据或只有一个数据时,返回
if(l >= r){
return;
}
//先进行对半分
int middle = (l + r) >> 1;
merge_sort(nums, l, middle);
merge_sort(nums, middle + 1, r);
//然后对每对数组进行排序
int temp[r - l + 1];//暂存结果
int n = 0;
int i = l,j = middle + 1;
while(i <= middle && j <= r){
if(nums[i] < nums[j]){
temp[n++] = nums[i++];
}
else{
temp[n++] = nums[j++];
}
}
if(i > middle){
while(j <= r){
temp[n++] = nums[j++];
}
}
else if(j > r){
while(i <= middle){
temp[n++] = nums[i++];
}
}
//从暂存数组中取出数据
int p = l,q = 0;
while(p <= r){
nums[p++] = temp[q++];
}
}
vector sortArray(vector& nums) {
int n = nums.size();
int l = 0;
int r = n - 1;
merge_sort(nums,l,r);
return nums;
}
};
逆序对:序列中的一对数,前面的数比后面的数大。
使用归并排序,共有三种情况:
(1)这一对数都在mid的左边:左半边内部的逆序对数量:merge_sort(L, mid)
(2)这一对数都在mid的右边:右半边内部的逆序对数量:merge_sort(mid + 1, R)
(3)一个数在mid左边,一个数在mid右边:设右边有m个数。对右边第一个点,左边有S1个数比它大,那么有S1个逆序对。以此类推,对右边第m个点,左边有Sm个数比它大,那么有Sm个逆序对。那么这种情况下一共有S1 + S2 + ... + Sm个逆序对。
在合并过程中,设指针i指向左边,指针j指向右边,若指针i指向的数比指针j指向的数小,则i向后移一位。直到指针i指向的数比指针j指向的数大,则S = mid - i + 1,j向后移一位。
设序列中有n个数,则最多有n(n - 1) / 2个逆序对,数值可能会很大,大于int的最大值,要用long long来存。
class Solution {
public:
typedef long long LL;
int merge_sort(int l, int r, vector& nums){
if(l >= r) return 0;
int mid = (l + r) >> 1;
LL res = merge_sort(l, mid, nums) + merge_sort(mid + 1, r, nums);
int temp[r - l + 1];
int i = l, j = mid + 1, k = 0;
while(i <= mid && j <= r){
if(nums[i] <= nums[j]) temp[k++] = nums[i++];
else{
temp[k++] = nums[j++];
res = res + mid - i + 1;
}
}
//扫尾
while(i <= mid) temp[k++] = nums[i++];
while(j <= r) temp[k++] = nums[j++];
//物归原主
for(int p = l, q = 0; p <= r; p++, q++){
nums[p] = temp[q];
}
return res;
}
int reversePairs(vector& nums) {
int n = nums.size();
int result = merge_sort(0, n - 1, nums);
return result;
}
};
(1)首先确定区间的中点位置mid 当l > r时无解 假设范围内有正数也有负数,范围为(l, r)。 如果结果保留n位小数,则r - l > 10^(-n-2) 1.两个大的整数相加A + B(大的整数的位数一般 <= 10^6) 2.两个大的整数相减A - B 3.大的整数乘小的整数A * a(小的整数一般 <= 10^9) 4.大的整数除以小的整数A / a 注:这里假设A,B,a都大于等于0 1.两个大的整数相加A + B 可划分为两个问题 2.两个大的整数相减A - B 可划分为两个问题 3.大的整数乘小的整数A * a 可划分为两个问题 4.大的整数除以小的整数A / a 可划分为两个问题 设有一个数组:a1,a2,...,an 作用:如求数组a中第l项到第r项的和,则答案等于S[r] - S[l - 1] 题目:输入一个长度为n的整数序列。接下来再输入m个询问,每个询问输入一对l,r。对于每个询问,输出原序列中从第i个数到第r个数的和。 S[i,j]表示包含a[i,j]的左上角的子矩阵中数的和:一行一行求:S[i,j] = S[i - 1,j] + S[i,j - 1] - S[i -1,j - 1] + a[i,j]。 作用:输出原矩阵中顶点为(x1,y1),(x2,y2)的子矩阵中数的和:S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]。 题目:输入一个边长为n,m的整数矩阵。接下来再输入q个询问,每个询问输入x1,y1,x2,y2。对于每个询问,输出原矩阵中顶点为(x1,y1),(x2,y2)的子矩阵中数的和。 给定原数组a[n],构造数组b[n],使得a[i] = b[1] + b[2] + ... + b[i]。此时,b为a的差分,a为b的前缀和。 有了b数组,可以在O(n)的时间内得到a数组。 作用:若我们想把a数组内从a[l]到a[r]的项全部加上c,若直接对a数组操作,则需要O(r - l + 1)的时间复杂度,可若有了b数组,可以只对b[l]与b[r + 1]操作:b[l] = b[l] + c,b[r + 1] = b[r + 1] - c,时间复杂度为O(1)。 b初始化:可以将a数组的初值想象成0,然后我们需要给a的每个点加上a[i]。 题目:输入一个长度为n的整数序列。接下来再输入m个操作,每个操作包含三个整数l, r, c,表示将序列中[l, r]之间的每个数加上c。请你输出进行完所有操作后的序列。 给定原矩阵a[i, j],构造差分矩阵b[i, j],使b[i, j]的二维前缀和为a[i, j]。 作用:假设我们需要给a矩阵的一个子矩阵(x1,y1),(x2,y2)中的所有数加上c。 b初始化:可以将a数组的初值想象成0,然后我们需要给a矩阵的每个1*1的子矩阵加上a[i,j]。 题目:输入一个n行m列的整数矩阵,再输入q个操作,每个操作包含五个整数x1, y1, x2, y2, c,其中(x1, y1)和(x2, y2)表示一个子矩阵的左上角坐标和右下角坐标。每个操作都要将选中的子矩阵中的每个元素的值加上c。请将进行完所有操作后的矩阵输出。 核心思想:将算法的时间复杂度从O(n^2)优化到O(n) 例:输入一个英文句子,输出每个单词,假设句子中每个单词之间有一个空格。 题目:请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。 (最右边是第0位) (1)先把第k位移到最后一位:n>>k x = 1010 lowbit(x) = 10 -x = ~x + 1(补码=反码+1) 作用1:求x的二进制表示中1的个数 一(三)01:03:54 七、离散化 八、区间合并
(2)将待查的K值与R[mid]比较:
若相等,则查找成功并返回此位置,
否则,①若R[mid].key>K,将查找区间变为[left,mid-1]
②若R[mid].keyclass Solution {
public:
//假设所有元素不重复
int search(vector
2.数的三次方根
设mid = (l + r) / 2。
if(mid^3 == x) result = mid;
if(mid^3 > x) r = mid;
else l = mid;#include
三、高精度
①大整数存储:将整数的每一位存到数组中
例如:A = 123456789,比较好的处理办法是从个位开始存:9存到nums[0],8存到nums[1],以此类推。从个位开始存的原因是比较好处理进位问题。
②大整数相加:注意进位。#include
①大整数存储:同1
②大整数相减:若A > B,则运算A - B;若A < B,则运算-(B - A)。注意借位。#include
①大整数存储:同1
②大整数相乘:将A的每一位与a相乘,结果加上进位所得值t,t取10的余数为当前位结果,t除以10为进位。#include
①大整数存储:同1
②大整数相除:将A的每一位 加上 上一位求得的余数*10 再除以a,得到的商为当前位的值。#include
四、前缀和与差分
1.一维前缀和
前缀和数组:S0 = 0,...,S[i] = a[1] + ... + a[i],...#include
2.二维前缀和
#include
3.一维差分
即b[1] = a[1];
b[2] = a[2] - a[1];
b[3] = a[3] - a[2];
...
b[n] = a[n] - a[n - 1];#include
4.二维差分
(1)我们若让b[x1,y1] += c,那么a[x1,y1]这一点右下角的所有点的前缀和都会加c。
(2)我们再让b[x2 + 1,y1] -= c,b[x1,y2 + 1] -= c,b[x2 + 1,y2 + 1] += c。
这么做会让时间复杂度变成O(1)。#include
五、双指针算法
for(i = 0, j = 0; i < n; i++){
while(j < i && check(i, j)) j++;
//每道题目的具体逻辑
}
#include
1.最长不重复连续子序列
//朴素做法:O(n^2)
for(int i = 0; i < n; i++){//i为终点
for(int j = 0; j <= i; j++){//j为起点
if(check(j, i)){
res = max(res, i - j + 1);
break;
}
}
}
//改进版:O(n)
for(int i = 0, j = 0; i < n; i++){
while(j <= i && !check(j, i)) j++;
res = max(res, i - j + 1);
}
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n = s.size();
unordered_map
六、位运算
1.n的二进制表示中第k位数字是几
(2)看一下最右边是几:x&1#include
2.lowbit(x)
x = 101000 lowbit(x) = 1000
lowbit(x) = x & (-x) = x & (~x + 1)int lowbit(int x){
return x & -x;
}
//main
int res = 0;
while(x) x -= lowbit(x), res++;//每次减去x的最后一位1
return res;