算法基础课笔记 (持续更新中 ...)

算法基础

第 一 讲

快速排序

知识点

1.确定分界点 (x) 数组 q 的 中间值

x = a[(l + r) / 2]  //一般以中位数为分界点,很少 x = a[l] / a[r] / 随机   

2.调整范围:<重点>

快速排序

3.递归 处理 左边 和 右边

模板
void quick_sort(int q[], int l, int r)//快速排序
{
    if (l >= r) 
        return;

    int x = q[(l + r) / 2]; // 分界点
    int i = l - 1 ;         // 因为下面的是最开始就将i,j移动一位,再判断,所以要在边界的两端前一位开始
    int j = r + 1 ;         
    while (i < j)
    {
        while(q[ ++ i ] < x);
        while(q[ -- j ] > x);
        if (i < j) 
            swap(q[i], q[j]);
    }
    
    quick_sort(q, l, j);      //递归遍历左边
    quick_sort(q, j + 1, r);  //递归遍历右边
}

quick_sort(q, 0, n - 1);	  // mian函数调用快排方法

归并排序

1.主要思想
// 自底向上
1. 确定分界点 mid = (l + r) / 2 ;  //中间 下标 位置
2. 递归处理左,右两段
3. 归并(双指针算法,指针表示剩余部分中最小元素的位置)  
2.模板
void merge_sort(int q[], int l, int r)  // 归并排序
{
    if (l >= r) 
        return;
        
    int mid = (l + r) / 2;
    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);
    
    // 归并过程 ! 
    int k = 0 ;  			// 临时数组 tmp 的下标
    int i = l, j = mid + 1; 
    while(i <= mid && j <= r)
    {
        if(q[i] <= q[j])
            tem[k++] = q[i++];
        else
            tem[k++] = q[j++];
    }
    while (i <= mid)			// 扫尾
        tem[k++] = q[i++];
    while (j <= r)
        tem[k++] = q[j++];
    
    for (i = l ,j = 0; i <= r; i ++ ,j++ ) // 物归原主
        q[i] = tem[j];
}

merge_sort(q, 0, n - 1);        // 调用方法

二分

知识点
	整数二分二分模板
1.循环必须是l < r
2.if判断条件看是不是不满足条件, 然后修改上下界
3.if else后是r = mid - 1,则前面mid 语句要加1
4.出循环一定是l == r,所以l和r用哪个都可以

	二分只有下面两种情况
1:找 [ >= X ] 的第一个位置 (满足某个条件的第一个数)
2:找 [ <= X ]的最后一个数  (满足某个条件的最后一个数)

	二分的流程:
1.确定二分的边界
2.编写二分的代码框架
3.设计一个check(性质)
4.判断一下区间如何更新
5.如果更新方式写的是l = mid, r = mid - 1,那么就在算mid的时候加上1

在这里插入图片描述

模板
// 判断条件很复杂时用check函数,否则if后直接写条件即可
bool check(int mid) {
    ...
    return ...;
}

/* 
	能二分的题一定是满足某种性质,分成左右两部分
	if的判断条件是让mid落在满足你想要结果的区间内
*/

// 模板1: 找满足某个条件的第一个数  即右半段的第一个值
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) 
            r = mid;  
        else 
            l = mid + 1;
    }
    return l;
}

// 模板2: 找满足某个条件的最后一个数  即左半段的最后一个值
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) 
            l = mid;
        else 
            r = mid - 1;
    }
    return l;
}


//浮点数二分
bool check(double x)  // 检查x是否满足某种性质

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) 
            r = mid;
        else 
            l = mid;
    }
    return l;
}
//说明:
 浮点数二分不需要考虑mid是否 + 1,else后是否 + 1。
 没有固定的浮点数序列,因此要考虑精度eps,一般比题目要求多1位小数就行
 需要自己确定l和r的值,即查找范围
例题
/*
	leetcode 69:x的平方根
实现 int sqrt(int x) 函数。计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
说明: 8 的平方根是 2.82842…, 由于返回类型是整数,小数部分将被舍去。
*/

class Solution {
public:
    int mySqrt(int x) {
        int l = 0, r = x;
        while (l < r) {
            int mid = l + r + 1ll >> 1; 
            if (mid  <= x / mid) 
                l = mid;  
            else 
                r = mid - 1;
        }
        return l;
    }
};

// 两个int相加减会溢出 中间加个长整型常量
// 少用乘法,用除法可以防止溢出
/*
	解题思路:
1. 首先我们要思考采用它是属于哪一种类型的模板。
2. 这道题只能采用模板2,即答案 X 在左边的情况。
3. 因为我们这道题要求的时 “由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。” 因此如果结果时 2.333, 2.999, 2.123 之类的最后输出都只能是 2
4. 在确定了属于哪种类型后就直接套模板写代码即可。
*/

第 二 讲

  • 大整数存储
vector<int> A;
string a;
cin >> a;
for (int i = a.size() - 1; i >= 0; i--) 
    A.push_back(a[i] - '0');
  • 大整数比较
// A >= B返回true,否则返回false
bool cmp(vector<int>& A, vector<int>& B) {
    if (A.size() != B.size()) 
        return A.size() > B.size();
    for (int i = A.size() - 1; i >= 0; i--) 
    {
        if (A[i] != B[i])
            return A[i] > B[i];
    }
    return true;
}

高精度运算

说明:
  • 大整数低位存放在数组低地址处,高位存放在数组高地址处
  • 数组地址由低到高(0→n - 1)
  • 整数位数最左边是高位,最右边是低位(高位→低位)
  • 注意处理最高位进位
  • 读取数组时反向(n-1→0)遍历,运算时正向(0→n-1)遍历
数组下标0123
原来的数8765
数组存储5678
// 加法
#include<iostream>
#include<vector>

using namespace std;

vector<int> add(vector<int> &A, vector<int> &B)   // 加引用避免再拷贝整个数组
{
    vector<int> C;
    int t = 0;

    for (int i = 0; i < A.size() || i < B.size(); i++) // 从个位开始遍历
    {
        if(i < A.size())        // 每一次用t表示Ai,Bi与上一个数的进位这三个数的和
            t += A[i];
        if(i < B.size()) 
            t += B[i];
        C.push_back(t % 10);    // 当前这一位输出t除以10的余数
        t /= 10;    			// t是否进位
    }
    if(t)  C.push_back(1);   		// 如果最高位有进位则补1 
    return C;
}

int main()
{
    string a, b;    		// 使用字符串读入
    vector<int> A, B;
    cin >> a >> b;          //a = "123456"
    for (int i = a.size() - 1; i >= 0; i--) // 使用vector逆序读入,变成整数需要减去偏移量0,A = [6,5,4,3,2,1]
        A.push_back(a[i] - '0');
    for (int i = b.size() - 1; i >= 0; i--) 
        B.push_back(b[i] - '0');
   
    auto C = add(A, B);

    for (int i = C.size() - 1; i >= 0; i--) 
        printf("%d", C[i]);		// 倒序输出
    return 0;
}

// 个位放在索引0的位置:出现进位的情况时需要在高位补上1,这样在数组后端补数比较容易


//  乘法
// C = A * b, A >= 0, b > 0
vector<int> mul(vector<int> &A, int b)
{
    vector<int> C;

    int t = 0;
    for (int i = 0; i < A.size() || t; i ++ )
    {
        if (i < A.size()) t += A[i] * b;
        C.push_back(t % 10);
        t /= 10;
    }

    while (C.size() > 1 && C.back() == 0) C.pop_back();

    return C;
}

前缀和

在这里插入图片描述

一维前缀和

在这里插入图片描述

/*
	笔记:
1. 假设S0=a0=0
2. 复杂度由O(n)降为O(1)
3. 数组a和S的第1个元素都不存储(下标为0),而从第2个元素开始存储(下标为1)
4. 注意遍历范围是1 ~ n  < 重点 >
5. 在一些不涉及a[i]的题目中,不必要存储a[i]的值,只需要存储S[i]就足够
*/

int a[N], S[N];

for (int i = 1; i <= n; i++) 
    S[i] = S[i - 1] + a[i];      // 给定数组a,初始化前缀和数组S

for (int i = 1; i <= n; i++)  {
    scanf("%d", &a[i])        
    S[i] = S[i - 1] + a[i];   // 未给定数组a,可合并读入和初始化的过程
}

int l, r;
cout << S[r] - S[l - 1] << endl;                        // 计算a[l] + ... + a[r]

二维前缀和

在这里插入图片描述

/* 
	说明:
假设数组a中行下标或列下标为0的项都是0
复杂度由O(m * n)降为O(1)
读入数组a和初始化前缀和数组S的过程可以合并在一起
注意遍历范围是1 ~ n ! < 重点 >
在一些不涉及a[i][j]的题目中,不必要存储a[i][j]的值,只需要存储S[i][j]就足够
*/

int a[N][N], S[N][N];

// 给定数组a
for (int i = 1; i <= n; i++)
    for (int j = 1; j <= m; j++) 
        S[i][j] = S[i - 1][j] + S[i][j - 1] - S[i - 1][j - 1] + a[i][j];

// 没有给定数组a,需要读入并初始化前缀和数组,则可以合并读入和初始化的过程
for (int i = 1; i <= n; i++)
    for (int j = 1; j <= m; j++) {
        cin >> 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 ) 
   res = S[x2][y2] - S[x2][y1 - 1] - S[x1 - 1][y2] + S[x1 - 1][y1 - 1]    // 使用
    

差分

在这里插入图片描述

一维差分

给一维数组 a 的区间 [ l , r ] 中的每个数加上C:

在这里插入图片描述

//原数组就是差分数组的前缀和
原始数组:9  3  6  2  6  8
差分数组:9 -6  3 -4  4  2
void insert(int l, int r, int c)  
{
    b[l] += c;
    b[r + 1] -= c;
}

注意:下标也是从 1 开始

  • 一个例子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ET3gWRTD-1627827057750)(E:\AcWing算法\Md图片\image-20210801212423409.png)]

// 操作 b 数组, 初始化为 0 
void insert(int l, int r, int c)  
{
    b[l] += c;
    b[r + 1] -= c;
}

//a[i] 
insert(l, r, c); // 操作: 将在数组 a 的区间 l - r 的所有数加上 c , 实际上是在 数组 b 上操作,a 数组不变

for (int i = 1; i <= n; i ++ ) //求数组 b 的前缀和
    b[i] += b[i - 1];

for (int i = 1; i <= n; i ++ ) { // a 数组 + b 的前缀和 为答案
    a[i] += b[i];
    cout << a[i] <<' ';
}
     
二维差分

给 [x1, y1] 与 [x2, y2] 构成的矩形范围内的每个数加上c:

在这里插入图片描述

b[ x1 ][ y1 ] += c      蓝色 矩形面积的元素都 加上 c
b[ x1 ][y2 + 1] -= c    绿色 矩形面积的元素再 减去 c, 使其内元素不发生改变
b[x2 + 1][ y1 ] -= c    紫色 矩形面积的元素再 减去 c, 使其内元素不发生改变
b[x2 + 1][y2 + 1] += c  红色 矩形面积的元素再 加上 c, 红色内的相当于被减了两次,再加上一次c,才能使其恢复
void insert(int x1, int y1, int x2, int y2, int c) 
{
    B[x1][y1] += c;
    B[x2 + 1][y1] -= c;
    B[x1][y2 + 1] -= c;
    B[x2 + 1][y2 + 1] += c;
}
int B[N][N];            // 二维差分数组

void insert(int x1, int y1, int x2, int y2, int c) {
    B[x1][y1] += c;
    B[x2 + 1][y1] -= c;
    B[x1][y2 + 1] -= c;
    B[x2 + 1][y2 + 1] += c;
}

// 构造(无需额外的数组a)
int tmp;
for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
        scanf("%d", &tmp);
        insert(i, j, i, j, tmp);
    }
}

// 转换成二维前缀和数组
for (int i = 1; i <= n; i++)
    for (int j = 1; j <= m; j++)
        B[i][j] += B[i - 1][j] + B[i][j - 1] - B[i - 1][j - 1];

int B[N][N];            // 二维差分数组

void insert(int x1, int y1, int x2, int y2, int c) {
    B[x1][y1] += c;
    B[x2 + 1][y1] -= c;
    B[x1][y2 + 1] -= c;
    B[x2 + 1][y2 + 1] += c;
}

// 构造(无需额外的数组a)
int tmp;
for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
        scanf("%d", &tmp);
        insert(i, j, i, j, tmp);
    }
}

// 转换成二维前缀和数组
for (int i = 1; i <= n; i++)
    for (int j = 1; j <= m; j++)
        B[i][j] += B[i - 1][j] + B[i][j - 1] - B[i - 1][j - 1];

第 三 讲

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值