一些学习笔记

本文深入探讨了接口实现中的atoi函数,将字符串转换为整数的方法,以及常见算法如to_string、手写构造函数、strcpy、strlen、strcat和strcmp。同时介绍了字符串类的实现和前缀和、差分数组的概念与应用,以及树状数组的使用。还提供了归并排序求逆序对数量的算法和二维前缀和的计算方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

常见的接口实现

atoi

将字符串转换为一个32位有符号整数

  1. 读入字符串并丢弃无用的前导空格
  2. 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正
  3. 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略
  4. 将前面步骤读入的这些数字转换为整数,如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)
  5. 如果整数数超过 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组成的数
基本操作:

  1. 区间查询:查询前缀和,相减得到区间和,利用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;
        }
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值