算法基础_基础算法【高精度 + 前缀和 + 差分 + 双指针】
- ---------------高精度---------------
- 791.高精度加法
- 题目介绍
- 方法一:
- 代码片段解释
- 片段一:
- 解题思路分析
- 792. 高精度减法
- 题目介绍
- 方法一:
- 代码片段解释
- 片段一:
- 解题思路分析
- 793.高精度乘法
- 题目介绍
- 方法一:
- 794.高精度除法
- 题目介绍
- 方法一:
- 解题思路分析
- ---------------前缀和---------------
- 795.前缀和
- 题目介绍
- 方法一:
- 解题思路分析
- 796.子矩阵的和
- 题目介绍
- 方法一:
- 解题思路分析
- ---------------差分---------------
- 797.差分
- 题目介绍
- 方法一:
- 代码片段解释
- 片段一:
- 片段二:
- 解题思路分析
- 798.差分矩阵
- 题目介绍
- 方法一:
- 解题思路分析
- ---------------双指针---------------
- 799.最长连续不重复子序列
- 题目介绍
- 方法一:
- 解题思路分析
- 800.数组元素的目标和
- 题目介绍
- 方法一:
- 2816.判断子序列
- 题目介绍
- 方法一:
往期《算法基础》回顾:
算法基础_基础算法【快速排序 + 归并排序 + 二分查找】
---------------高精度---------------
791.高精度加法
题目介绍
方法一:
#include <iostream>
#include <vector>
using namespace std;
string a, b;
vector<int> A, B;
vector<int> ret;
vector<int> add(vector<int>& A, vector<int>& B)
{
int t = 0;
for (int i = 0; i < A.size() || i < B.size(); i++)//(从前往后遍历)
{
if (i < A.size()) t += A[i];
if (i < B.size()) t += B[i];
ret.push_back(t % 10); //逢十进一
t /= 10; // 总共可以进几位
}
if (t) ret.push_back(1);
return ret;
}
int main()
{
cin >> a >> b;
for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0'); //(从后往前存储)
for (int i = b.size() - 1; i >= 0; i--) B.push_back(b[i] - '0'); //(从后往前存储)
ret = add(A, B);
//注意:从后往前输出数组中的元素时i的初始值为size() - 1
for (int i = ret.size() - 1; i >= 0; i--) printf("%d", ret[i]); //(从后往前输出)
return 0;
}
代码片段解释
片段一:
if (t) ret.push_back(1);
在代码中,
if (t) ret.push_back(1);
这行代码的作用是处理加法运算中最高位的进位。示例:假设
A = [9, 9]
(表示数字99
),B = [1]
(表示数字1
)
- 在循环中,逐位相加:
- 第一位:
9 + 1 = 10
,ret
添加0
,t = 1
(进位)- 第二位:
9 + 0 = 9
,加上进位1
,9 + 1 = 10
,ret
添加0
,t = 1
(进位)- 循环结束后,
t = 1
,表示还有进位,因此ret
添加1
最终
ret = [0, 0, 1]
,表示数字100
疑问:为什么要写成这样if (t) ret.push_back(1);写成if (t) ret.push_back(t);这样看上去不是更合理吗?
在循环结束后,如果
t
不为0
,那么它一定是1
(因为加法最多只会产生一个进位
)从逻辑上讲,
if (t) ret.push_back(t);
也是可行的,因为此时t
的值只可能是0
或1
解题思路分析
高精度加法的思路步骤:
第一步:定义一个变量t用于存储当前位的累加和以及处理进位
第二步:使用for循环同时遍历存储加数的这两个数组(从前往后遍历)
第三步:使用if条件语句判断是否遍历完加数1的所有位,若没将该位的值加到变量t中
第四步:使用if条件语句判断是否遍历完加数2的所有位,若没将该位的值加到变量t中
第五步:将变量t除以10的余数(t % 10)添加到结果容器中
第六步:将变量t除以10
第七步:使用if条件语句判断变量t是否为0,若不为0则将其值添加到结果容器中(处理最高位)
第八步:返回结果数组
792. 高精度减法
题目介绍
方法一:
#include <iostream>
#include <vector>
using namespace std;
string a, b;
vector<int> A, B;
vector<int> ret;
// 判断是否有 A >= B
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]; //A[i] != B[i]时退出函数
}
return true;
}
//高精度减法
vector<int> sub(vector<int>& A, vector<int>& B)
{
int t = 0;
for (int i = 0; i < A.size(); i++)//(从前往后遍历)
{
t = A[i] - t;
if (i < B.size()) t -= B[i];
ret.push_back((t + 10) % 10);
//t为负数时需要向前借一位当10所以+10,其后的%10对其不会产生影响
//t为正数时可不需要做任何操作将其添加到数组,但是+10对其有影响,所以%10消去影响
if (t < 0) t = 1; //当前位产生了借位操作
else t = 0;
}
while (ret.size() > 1 && ret.back() == 0) ret.pop_back(); // 去掉前导0
return ret;
}
int main()
{
cin >> a >> b;
for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');//(从后往前存储)
for (int i = b.size() - 1; i >= 0; i--) B.push_back(b[i] - '0');
if (cmp(A, B))
{
ret = sub(A, B);
for (int i = ret.size() - 1; i >= 0; i--) printf("%d", ret[i]);//(从后往前输出)
}
else
{
ret = sub(B, A);
printf("-");
for (int i = ret.size() - 1; i >= 0; i--) printf("%d", ret[i]);
}
return 0;
}
代码片段解释
片段一:
while (ret.size() > 1 && ret.back() == 0) ret.pop_back(); // 去掉前导0
这段代码的作用是去掉结果中的前导零
在高精度计算中,前导零是指结果中最高位之前的无效零。
- 例如:计算结果为
00123
,实际有效值是123
,前导零00
是无意义的,需要去掉。
ret.size() > 1
这个条件确保结果至少保留一位数字。
如果结果只有一个数字(例如:
[0]
),即使它是0
,也不能去掉,因为0
是一个有效的数字。
ret.back() == 0
ret.back()
获取结果数组的最后一个元素,即当前最高位的数字。如果最高位是
0
,说明这是一个前导零,需要去掉。
ret.pop_back()
这个操作将结果数组的最后一个元素(即:当前最高位)移除。
通过循环不断移除前导零,直到最高位不是
0
或者结果只剩一位。
解题思路分析
高精度减法的思路步骤:
第一步:定义一个变量t用于处理借位以及临时存储当前位的计算结果
第二步:使用for循环同时遍历存储加数的这两个数组(从前往后遍历)
第三步:使用减数1的值减去存储借位的变量t,同时将结果赋给t
第四步:使用if条件语句判断是否遍历完减数2的所有位,若没有则使用临时存储当前位的计算结果的变量t减去该位的值
第五步:将变量t+10后的值除以10的余数((t + 10) % 10)添加到结果容器中
第六步:使用if分支语句判断t是否为负值 ,负值将其置为1否则为0
第七步:使用while循环不断的将结果数组中末尾(高位)的0移除
第八步:返回结果数组
高精度加法与高精度减法的不同之处有以下几点:
- 使用for循环遍历数组的结束条件不同:
- 高精度加法:
for (int i = 0; i < A.size() || i < B.size(); i++)
- 高精度减法:
for (int i = 0; i < A.size(); i++)
- 变量t的使用机制不同:
- 高精度加法:
if (i < A.size()) t += A[i]; if (i < B.size()) t += B[i];
- 高精度减法:
t = A[i] - t; if (i < B.size()) t -= B[i];
- 向结果容器中添加的元素的处理不同:
- 高精度加法:
ret.push_back(t % 10); //逢十进一
- 高精度减法:
ret.push_back((t + 10) % 10); //借一当十
- 变量t的更新操作不同
- 高精度加法:
t /= 10;
- 高精度减法:
if (t < 0) t = 1; else t = 0;
- 最后遇到的问题不同
- 高精度加法:
if (t) ret.push_back(1); //处理最高位的进位
- 高精度减法:
while (ret.size() > 1 && ret.back() == 0) ret.pop_back(); // 去掉前导零
793.高精度乘法
题目介绍
方法一:
#include <iostream>
#include <vector>
using namespace std;
string a;
long long b; //相对于乘数1,乘数2的值相对小一点,long long 类型足以容纳
vector<int> A;
vector<int> B;
vector<int> ret;
vector<int> mul(vector<int>& A, vector<int>& B)
{
int t = 0;
for (int i = 0; i < A.size(); i++)//(从前往后遍历)
{
t = (A[i] * b) + t;
ret.push_back(t % 10);
t = t / 10;// 进位的大小t
}
if (t) ret.push_back(t);
while (ret.size() > 1 && ret.back() == 0) ret.pop_back();
return ret;
}
int main()
{
cin >> a >> b;
//用数组存储乘数的每位数字
for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0'); //(从后往前存储)
//处理数据:
ret = mul(A, B);
//输出数据:
for (int i = ret.size() - 1; i >= 0; i--) cout << ret[i];//(从后往前输出)
return 0;
}
794.高精度除法
题目介绍
方法一:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
string a;
int b;
vector<int> A;
vector<int> ret;
// A / b,商是C,余数是r
vector<int> div(vector<int>& A, int b, int& t)
{
for (int i = A.size() - 1; i >= 0; i--)//(从后往前遍历)
{
t = t * 10 + A[i];
ret.push_back(t / b);
t %= b;
}
reverse(ret.begin(), ret.end()); //数组前面存储的高位,被反转为存储的低位(方便消去前导零)
while (ret.size() > 1 && ret.back() == 0) ret.pop_back();
return ret;
}
int main()
{
cin >> a >> b;
for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');//(从后往前存储)
int t;
ret = div(A, b, t);
for (int i = ret.size() - 1; i >= 0; i--) printf("%d", ret[i]);//(从后往前输出)
cout << endl << t << endl;
return 0;
}
解题思路分析
高精度除法的思路步骤:
第一步:定义一个变量t用于存储当前的余数
第二步:使用for循环同时遍历存储加数的这两个数组(从后往前遍历:先处理高位)
第三步:变量t乘10再加上当前除数1的值,同时将结果赋给t
第四步:将变量t的值除以除数2的商添加到结果数组中
第五步:用变量t的值除以除数2的余数更新变量t
第六步:对结果数组中的所有元素进行反转
第七步:使用while循环不断的将结果数组中末尾(高位)的0移除
第八步:返回结果数组
高精度乘法与高精度除法的不同之处有以下几点:
- 使用for循环遍历数组的方向不同:
- 高精度乘法:
for (int i = 0; i < A.size(); i++)//(从前往后遍历)
- 高精度除法:
for (int i = A.size() - 1; i >= 0; i--)//(从后往前遍历)
- 变量t的使用机制不同:
- 高精度乘法:
t = (A[i] * b) + t;
- 高精度除法:
t = t * 10 + A[i];
- 向结果容器中添加的元素的处理不同:
- 高精度乘法:
ret.push_back(t % 10);
- 高精度除法:
ret.push_back(t / b);
- 变量t的更新操作不同:
- 高精度乘法:
t = t / 10;
- 高精度除法:
t %= b;
- 处理最高位的进位不同:
- 高精度乘法:
if (t) ret.push_back(t);
- 高精度除法:
不需要处理
- 消去前导零的方式不同:
- 高精度乘法:
while (ret.size() > 1 && ret.back() == 0) ret.pop_back();
- 高精度除法:
reverse(ret.begin(), ret.end()); while (ret.size() > 1 && ret.back() == 0) ret.pop_back()
---------------前缀和---------------
795.前缀和
题目介绍
方法一:
#include <iostream>
using namespace std;
const int N = 1e5+10;
int n, m;
int a[N], s[N];
int main()
{
scanf("%d%d", &n, &m);
//构造前缀和数组s (原数组和前缀和数组都从下标1处开始存放元素,应为我们要预留s0为0)
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];
while (m--)
{
int l, r;
scanf("%d%d", &l, &r);
printf("%d\n", s[r] - s[l - 1]);
}
return 0;
}
解题思路分析
如何
去求
一个一维矩阵的前缀和?(假如说数组s是某一维数组的前缀和数组)
s[i] = s[i - 1] + a[i];
如何
使用
一个一维矩阵的前缀和?(假如说我们要求得某一维数组中区间为[l,r]之间的元素之和)
结果 = s[r] - s[l - 1]
796.子矩阵的和
题目介绍
方法一:
#include <iostream>
using namespace std;
const int N = 1010;
int n, m, q;
int a[N][N], s[N][N];
int main()
{
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &a[i][j]);
// 初始化前缀和数组
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];
// 询问
while (q--)
{
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
printf("%d\n", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);//使用前缀和数组
}
return 0;
}
解题思路分析
如何
去求
一个二维矩阵的前缀和?(假如说数组s是某二维数组的前缀和数组)
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]
---------------差分---------------
797.差分
题目介绍
方法一:
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int a[N], b[N];
void insert(int l, int r, int c)
{
b[l] += c;
b[r + 1] -= c;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]); // 原数组a的赋值
for (int i = 1; i <= n; i++) insert(i, i, a[i]); // 通过原数组a为差分数组b进行赋值
while (m--)
{
int l, r, c;
scanf("%d%d%d", &l, &r, &c);
insert(l, r, c);
}
for (int i = 1; i <= n; i++)
{
a[i] = a[i - 1] + b[i];
printf("%d ", a[i]);
}
return 0;
}
代码片段解释
片段一:
for (int i = 1; i <= n; i++) insert(i, i, a[i]);
这段代码中的
for (int i = 1; i <= n; i++) insert(i, i, a[i]);
的作用是初始化差分数组b
(差分数组b
是原数组a
的差分表示)
差分数组的特点是:
b[i] = a[i] - a[i - 1]
(对于i > 1
)b[1] = a[1]
(因为a[0]
不存在,默认为0
)通过差分数组,我们可以
高效地对原数组的某个区间进行批量加减操作
- 如果要对原数组
a
的区间[l, r]
中的每个元素加上c
,只需要:
b[l] += c
b[r + 1] -= c
- 最后通过前缀和运算(
b[i] += b[i - 1]
)还原出更新后的原数组
for (int i = 1; i <= n; i++) insert(i, i, a[i]);
的作用:将原数组a
转换为差分数组b
具体过程:
- 初始时,差分数组
b
的所有元素都是0
- 对于每个
i
(从1
到n
),执行insert(i, i, a[i])
:
b[i] += a[i]
:将a[i]
的值赋给b[i]
b[i + 1] -= a[i]
:将a[i]
的值从b[i + 1]
中减去- 最终,差分数组
b
的值满足:
b[1] = a[1]
b[i] = a[i] - a[i - 1]
(对于i > 1
)
示例:假设原数组
a
为:a = [0, 1, 2, 3, 4]
(注意:a[0]
不存在,默认为0
)执行
for (int i = 1; i <= n; i++) insert(i, i, a[i]);
后,差分数组b
为:b = [0, 1, 1, 1, 1, -4]
解释:
b[1] = a[1] = 1
b[2] = a[2] - a[1] = 2 - 1 = 1
b[3] = a[3] - a[2] = 3 - 2 = 1
b[4] = a[4] - a[3] = 4 - 3 = 1
b[5] = -a[4] = -4
(因为b[5] -= a[4]
)
片段二:
insert(l, r, c);
在这段代码中,
insert(l, r, c);
的作用是对差分数组b
进行区间更新,从而高效地对原数组a
的某个区间[l, r]
中的所有元素加上一个常数c
insert(l, r, c)
的作用:对差分数组b
进行区间更新,表示对原数组a
的区间[l, r]
中的每个元素加上c
//insert 函数的定义是: void insert(int l, int r, int c) { b[l] += c; b[r + 1] -= c; }
b[l] += c
:
- 表示从位置
l
开始,每个元素都加上c
- 这是因为差分数组
b
的前缀和会累加b[l]
,从而影响从l
开始的所有元素。
b[r + 1] -= c
:
- 表示从位置
r + 1
开始,每个元素都减去c
- 这是为了抵消
b[l] += c
的影响,确保只有区间[l, r]
中的元素被更新。
示例:区间更新
假设原数组
a
为:a = [1, 2, 3, 4, 5]
对应的差分数组
b
为:b = [1, 1, 1, 1, 1]
(因为
b[i] = a[i] - a[i - 1]
)现在,我们希望对区间
[2, 4]
中的每个元素加上2
。调用insert(2, 4, 2)
后:
b[2] += 2
,b
变为[1, 3, 1, 1, 1]
。b[5] -= 2
,b
变为[1, 3, 1, 1, -1]
。最后,通过前缀和运算还原出更新后的原数组:
a[1] = b[1] = 1 a[2] = b[1] + b[2] = 1 + 3 = 4 a[3] = b[1] + b[2] + b[3] = 1 + 3 + 1 = 5 a[4] = b[1] + b[2] + b[3] + b[4] = 1 + 3 + 1 + 1 = 6 a[5] = b[1] + b[2] + b[3] + b[4] + b[5] = 1 + 3 + 1 + 1 - 1 = 5
更新后的原数组
a
为:a = [1, 4, 5, 6, 5]
可以看到,区间
[2, 4]
中的每个元素都加上了2
解题思路分析
构造一维差分数组的核心:
void insert(int l, int r, int c) { b[l] += c; b[r + 1] -= c; }
第一步:使用for循环通过一维原数组为一维差分数组赋值
第二步:使用while循环将某个子区间中的元素都加上c
第三步:使用for循环通过一维差分数组还原出一维原数组
798.差分矩阵
题目介绍
方法一:
#include <iostream>
using namespace std;
const int N = 1010;
int n, m, q;
int a[N][N], b[N][N];
void insert(int x1, int y1, int x2, int y2, int c)
{
b[x1][y1] += c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y1] -= c;
b[x2 + 1][y2 + 1] += c;
}
int main()
{
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &a[i][j]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
insert(i, j, i, j, a[i][j]);
while (q--)
{
int x1, y1, x2, y2, c;
scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
insert(x1, y1, x2, y2, c);
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j];
printf("%d ", a[i][j]);
}
puts("");
}
return 0;
}
解题思路分析
构造二维差分数组的核心:
void insert(int x1, int y1, int x2, int y2, int c) { b[x1][y1] += c; b[x1][y2 + 1] -= c; b[x2 + 1][y1] -= c; b[x2 + 1][y2 + 1] += c; }
第一步:使用双重for循环通过二维原数组为二维差分数组赋值
第二步:使用while循环将某个子矩阵中的元素都加上c
第三步:使用双重for循环通过二维差分数组还原出二维原数组
---------------双指针---------------
799.最长连续不重复子序列
题目介绍
方法一:
#include <iostream>
using namespace std;
const int N = 1e5+10;
int n;
int a[N], s[N];
int res = 0;
int main()
{
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0, j = 0; i < n; i++)
{
s[a[i]]++;
while (s[a[i]] > 1)
{
s[a[j]]--;
j++;
}
res = max(res, i - j + 1);
}
cout << res << endl;
return 0;
}
解题思路分析
双指针的使用思路和步骤:
第一步:使用for循环经典的开头
for (int i = 0, j = 0; i < n; i++)
第二步:根据题目的要求进行不同的操作
第三步:使用while循环书写满足条件时的重复行为
第四步:统计结果
800.数组元素的目标和
题目介绍
方法一:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n, m, x;
int a[N], b[N];
int main()
{
scanf("%d%d%d", &n, &m, &x);
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
for (int i = 0; i < m; i++) scanf("%d", &b[i]);
for (int i = 0, j = m - 1; i < n; i++)
{
while (j >= 0 && a[i] + b[j] > x) j--;
if (a[i] + b[j] == x)
{
printf("%d %d\n", i, j);
break;
}
}
return 0;
}
2816.判断子序列
题目介绍
方法一:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010;
int n, m;
int a[N], b[N];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
for (int i = 0; i < m; i++) scanf("%d", &b[i]);
int i = 0, j = 0;
while (i < n && j < m)
{
if (a[i] == b[j]) i++;
j++;
}
if (i == n) puts("Yes");
else puts("No");
return 0;
}