常见的接口实现
atoi
将字符串转换为一个32位有符号整数
- 读入字符串并丢弃无用的前导空格
- 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正
- 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略
- 将前面步骤读入的这些数字转换为整数,如果没有读入数字,则整数为
0
。必要时更改符号(从步骤 2 开始) - 如果整数数超过 32 位有符号整数范围
[−231, 231 − 1]
,需要截断这个整数,使其保持在这个范围内。具体来说,小于−231
的整数应该被固定为−231
,大于231 − 1
的整数应该被固定为231 − 1
int myAtoi(string s) {
//case 1:"42" -> 42
//case 2:" -42" -> -42
//case 3:"4128 wirh sfa" -> 4128
//case 4:"songd wfa 1234" -> 0
//case 5:"-98765342039483" -> -2147483648
int res = 0;
int sign = 1; // 正号
int i = 0;
int n = s.length();
while (i < n && s[i] == ' ') {
++i;
}
if (s[i] == '-') {
sign = -1;
++i;
} else if (s[i] == '+') {
++i;
}
while (i < n && isdigit(s[i])) {
int num = s[i] - '0';
// 下一步更新res之前先判断是否溢出
if (res > INT_MAX / 10 || res == INT_MAX / 10 && num > INT_MAX % 10)
return INT_MAX;
else if (res < INT_MIN / 10 ||
res == INT_MIN / 10 && -num < INT_MIN % 10)
return INT_MIN;
res = res * 10 + sign * num;
++i;
}
return res;
}
to_string
把一个int型的整数转换为字符串
string myToString(int x) {
if(x == 0) return "0";
string res="";
if(x < 0) {
x *= -1;
while(x) {
char tmp = x % 10 + '0';
res += tmp;
x /= 10;
}
res += '-';
}
else {
while(x) {
char tmp = x % 10 + '0';
res += tmp;
x /= 10;
}
}
reverse(res.begin(),res.end());
return res;
}
手写构造函数
class A {
private:
int* m_ptr;
public:
A() : m_ptr(nullptr) {};
A(int* ptr) : m_ptr(new int(*ptr)) {};
//拷贝构造
A(const A& src) : m_ptr(nullptr) {
if(src.m_ptr) {
m_ptr = new int(*src.m_ptr);
}
};
//移动构造
A(A&& src) : m_ptr(src.m_ptr) {
src.m_ptr = nullptr;
}
~A() {
if(m_ptr)
delete m_ptr;
};
}
strcpy()
把从strsrc地址开始的含有’\0’结束符的字符串复制到以strdst开始的地址空间,返回值类型为char*
char* strcpy(char* strdst,const char* strsrc) {
if(strsrc == NULL || strdst == NULL) return NULL;
char* addr = strdst;
while(*strsrc != '\0') {
*strdst = *strsrc;
strdst++;
strsrc++;
}
*strdst = '\0';
return addr;
}
strlen()
计算给定字符串的长度
int strlen(const char* src) {
if(src == NULL) return -1;
int len = 0;
while((*src) != '\0') {
len++;
src++;
}
return len;
}
strcat()
把src所指的字符串添加到dst结尾处
char* strcat(char* dst,const char* src) {
if(src == NULL || dst == NULL) return NULL;
char* res = dst;
while((*dst) != '\0') {
dst++;
}
while((*src) != '\0') {
*dst = *src;
dst++;
src++;
}
*dst = '\0';
return res;
}
strcmp()
比较两个字符串str1和str2,若相等则返回0;若str1>str2返回正数;若str1<str2返回-负数;
int strcmp(const char* str1,const char* str2) {
assert(str1 && str2);
//找到首个不相等的字符
while((*str1) != '\0' && (*str2) != '\0' && (*str1) == (*str2)) {
str1++;
str2++;
}
return *str1 - *str2;
}
memcpy()
将src的前cnt个字节复制到dst
char* memcpy(char* dst,const char* src,int cnt) {
if(dst == NULL || src == NULL || cnt <= 0) return dst;
//内存重叠的情况,从后往前复制,防止数据覆盖
if(dst >= src && dst <= src+cnt-1) {
for(int i = cnt - 1;i >= 0;--i) {
dst[i] = src[i];
}
}
else {
for(int i = 0;i < cnt;++i) {
dst[i] = src[i];
}
}
return dst;
}
实现String类
class String {
public:
String();
String(const char* str = NULL);
String(const String& other);
~String();
String& operator=(const String& other);
private:
//获取字符串长度
inline size_t length() const {
return m_length;
};
//获取C风格的字符串
inline char* c_str() {
return m_data;
};
private:
char* m_data; //指向字符串的字符数组
size_t m_length; //字符串长度
}
//默认构造函数
String::String() m_data(nullptr),m_length(0) {};
//有参构造函数
String::String(const char* str = NULL) {
if(str == NULL) {
String(); //委托构造
}
else {
m_length = strlen(str);
m_data = new char[m_length + 1];
strcpy(m_data,str);
}
}
//拷贝构造函数
String::String(const String& other) {
m_length = other.m_length;
m_data = new char[m_length + 1];
strcpy(m_data,other.m_data);
}
//析构函数
String::~String() {
if(m_data) {
delete[] m_data;
}
}
//赋值运算符重载
String::String& operator=(const String& other) {
//检查自赋值
if(this == &other) return *this;
if(m_data) delete[] m_data; //释放原有资源
m_length = other.m_length;
m_data = new char[m_length + 1];
strcpy(m_data, other.m_data);
return *this;
}
其他补充知识
前缀和数组
a:[1,2,3,4,5]
sum:[0,1,3,6,10,15]
即:sum[0]=0;sum[i]=sum[i-1]+a[i-1](i>0 && i<n+1)
构造前缀和数组:
vector<int> a={0,1,3,4,5,6};
int n=a.size();
vector<int> sum(n+1);
sum[0]=0;
for(int i=1;i<n+1;++i) {
sum[i]=sum[i-1]+a[i-1];
}
利用前缀和数组求原数组任意区间和a[l,r]:
sum[r+1]-sum[l]
差分数组
a:[0,2,5,4,9,7,10,0]
d:[0,2,3,-1,5,-2,3,-10,-10]
即:d[0]=a[0];d[i]=a[i]-a[i-1];d[n]=d[n-1](n为数组a的长度)
对a[1,4]的每个数+3,可以转化为d上的单点操作,即d[1]+3,d[5]-3
由此可得:对a[l,r]范围内的每个数增加x,等价于对d[l]增加x,dr+1]增加x
重要性质:对d累加可以得到原数组
for(int i=0;i<n;++i) {
if(i == 0) a[i]=d[i];
else a[i]=a[i-1]+d[i];
}
树状数组
一种基于二进制思想的数据结构,用于维护序列前缀和,对于给定的序列a,树状数组节点c[x]维护的是序列中[x-lowbit(x)+1,x]的序列和,其中lowbit(x) = x & (-x)
,即x的最低位的1和后面的0组成的数
基本操作:
- 区间查询:查询前缀和,相减得到区间和,利用c[x]维护的是[x-lowbit(x)+1,x]的区间和,然后不断向前寻找即可
int query(int x) {
int res = 0;
for(int i = x;i > 0;i -= lowbit(i) {
res += c[i]; //i的子节点即为lowbit(i)
}
}
2.单点修改:给序列a的一个数a[x]加上t,然后更新树状数组维护的前缀和,只需不断向上维护c[x]的父节点即可
void update(int x,int t) {
for(int i = x; i <= n;i += lowbit(x) {
c[i] += t;
}
}
例题1:单点修改、区间查询
https://www.luogu.com.cn/problem/P3374
题解:
#include<iostream>
#include<vector>
using namespace std;
//求一个数的最低位1和后面的0构成的数
int lowbit(int x) {
return x & (-x);
}
//nums第x个数增加k,同时修改tree
void add(vector<int>& nums, vector<int>& tree, int x, int k) {
for (int i = x; i <= nums.size(); i += lowbit(i)) {
tree[i] += k;
}
nums[x - 1] += k;
}
//计算nums前x个数的和
int query(vector<int>& nums, vector<int>& tree, int x) {
int res = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
res += tree[i];
}
return res;
}
int main() {
int n, m; //数组长度、操作次数
cin >> n >> m;
vector<int> nums;
vector<int> tree;
nums.resize(n, 0);
tree.resize(n + 1, 0);
//tree[x]记录的是nums第x-lowbit(x)到第x个数的和
//nums:[1 5 4 2 3]
//tree:[0 1 6 4 12 3]
for (int i = 1; i <= n; ++i) {
int number;
cin >> number;
add(nums, tree, i, number);
}
while (m--) {
int op, x, y;
cin >> op >> x >> y;
//单点增加
if (op == 1) add(nums, tree, x, y);
//输出nums第x个数到第y个数的序列和
else {
cout << query(nums, tree, y) - query(nums, tree, x - 1) << endl;
}
}
return 0;
}
例题2:区间修改、单点查询
https://www.luogu.com.cn/problem/P3368
题解:
#include<iostream>
#include<vector>
using namespace std;
//求一个数的最低位1和后面的0构成的数
int lowbit(int x) {
return x & (-x);
}
//nums第x个数增加k,同时修改tree
void add(vector<int>& nums, vector<int>& tree, int x, int k) {
for (int i = x; i <= nums.size(); i += lowbit(i)) {
tree[i] += k;
}
nums[x - 1] += k;
}
//计算nums前x个数的和
int query(vector<int>& nums, vector<int>& tree, int x) {
int res = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
res += tree[i];
}
return res;
}
int main() {
int n, m; //数组长度、操作次数
cin >> n >> m;
vector<int> nums;
vector<int> tree;
nums.resize(n, 0);
tree.resize(n + 1, 0);
//tree[x]记录的是nums第x-lowbit(x)到第x个数的和
//nums:[1 5 4 2 3]
//tree:[0 1 6 4 12 3]
for (int i = 1; i <= n; ++i) {
int number;
cin >> number;
add(nums, tree, i, number);
}
while (m--) {
int op, x, y, k;
cin >> op;
//区间增加
if (op == 1) {
cin >> x >> y >> k; //将nums第x到第y个数增加k
for (int i = x; i <= y; ++i) {
add(nums, tree, i, k);
}
}
else {
cin >> x;
cout << nums[x - 1] << endl;
}
}
return 0;
}
归并排序
基本思想是分治。是将一个待排序的序列分解成两个规模大致相等的子序列,如果不易分解,将得到的子序列继续分解,直到子序列中包含的元素个数为1。因为单个元素的序列本身就是有序的,此时便可以进行合并,从而得到一个完整的有序序列。还可以用来求序列中的逆序对个数
#include<iostream>
#include<vector>
using namespace std;
int cnt = 0; //逆序对数量
//归并排序,并求逆序对数量
void merge(vector<int>& arr, int l, int mid, int r) {
vector<int> tmp(r - l + 1, 0); //临时数组,用于存放比较后的结果
int i = l, j = mid + 1, k = 0; //i、j为两个子数组的起始下标,k为临时数组的起始下标
while (i <= mid && j <= r) {
//两两比较两个子数组的值,将较小的值放入tmp
if (arr[i] <= arr[j]) {
tmp[k++] = arr[i++];
}
else {
tmp[k++] = arr[j++];
/*
* 归并排序中左子数组和右子数组一定是已经排列完的序列
* 当前的j和i构成逆序对,而i到mid一定是递增的,所以i到mid也能和j构成逆序对故逆序对个数为mid-i+1
*/
cnt += mid - i + 1;
}
}
//左子数组剩余元素复制到tmp
while (i <= mid) {
tmp[k++] = arr[i++];
}
//右子数组剩余元素复制到tmp
while (j <= r) {
tmp[k++] = arr[j++];
}
//tmp复制回原数组
k = 0;
for (int i = l; i <= r; ++i) {
arr[i] = tmp[k++];
}
}
void mergeSort(vector<int>& arr, int l, int r) {
long long inv_cnt = 0;
if (l < r) {
int mid = (l + r) >> 1;
//递归左右子数组进行归并排序,并统计逆序对数量
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
//合并左右子数组,并统计逆序对数量
merge(arr, l, mid, r);
}
}
int main() {
int n;
cin >> n;
vector<int> arr;
arr.resize(n);
for (int i = 0; i < n; ++i) {
cin >> arr[i];
}
mergeSort(arr, 0, n - 1);
cout << cnt << endl;
return 0;
}
二维前缀和
定义:给定一个二维矩阵 matrix
,其元素为matrix[i][j]
,我们可以构建一个二维前缀和数组 prefix_sum
,使得 prefix_sum[i][j]
表示从矩阵左上角 (0, 0) 到 (i, j) 的所有元素之和
计算方法:
// 二维前缀和数组
vector<vector<int>> prefixSum(n,vector<int>(m,0));
prefixSum[0][0]=nums[0][0];
for(int i=1;i<n;++i) {
prefixSum[i][0]=prefixSum[i-1][0]+nums[i][0];
}
for(int j=1;j<m;++j) {
prefixSum[0][j]=prefixSum[0][j-1]+nums[0][j];
}
for(int i=1;i<n;++i) {
for(int j=1;j<m;++j) {
prefixSum[i][j]=prefixSum[i-1][j]+prefixSum[i][j-1]-prefixSum[i-1][j-1]+nums[i][j];
}
}
并查集模板
class UnionFind {
public:
std::vector<int> father;
// 并查集初始化(n个点)
UnionFind(int n) {
father.resize(n);
for (int i = 0; i < n; ++i) {
father[i] = i;
}
}
// 并查集寻根
int find(int x) { return x == father[x] ? x : find(father[x]); }
// 判断x和y是否找到同一个根
bool isSame(int x, int y) {
x = find(x);
y = find(y);
return x == y;
}
// 将y->x加入并查集
void join(int x, int y) {
x = find(x);
y = find(y);
if (x != y) {
father[y] = x;
}
}
};