从只有简单c语言编程基础开始,准备机试过程全记录。
一.下载和配置vscode
1.原理背景介绍
vscode全名visual studio code是一款编辑器,首先解释一下编辑器、编译器、还有IDE三个概念的区别:
编辑器主要用于编写修改代码,提供了代码高亮、自动补全等功能,是为了提高程序员编码的效率。常见的有微软开发的vscode、sublime text、notepad++、atom、vim等。
而编译器负责将源代码翻译成能在计算机里运行的机器语言。常见的编译器有GCC、Clang、MSVC等等。
也就是说,编辑器写出来的代码并不能直接运行,需要进入编译器编译成可执行的程序。
而IDE(集成开发环境)通常包含了编辑器和编译器,以及其他有用的开发工具,对于真正的开发者来说是一个更好的选择。
为什么选择vscode进行程序调试和运行呢?首先,因为vscode有一个庞大的扩展生态系统,可以通过安装外部编译器(例如我们下面用到的MinGW),直接编译和调试代码。其次,比起功能更强大的IDE,vscode更轻量级,启动速度快,占用系统资源少,更符合我们完成简单编程任务的要求。
2.下载和配置操作
下载vscode的过程非常简单,就是直接去官网找到合适的版本,注意下载路径自己选择。
这里记录一下配置c/c++环境的过程,也就是安装这个外部编译器MinGW:
1.下载网址:MinGW-w64 - for 32 and 64 bit Windows download | SourceForge.net
2.在Files里找到x86_64-posix-seh双击下载到合适的路径并解压。
3.打开解压好的文件夹,复制bin文件的路径,在Windows的搜索栏搜索找到编辑系统环境变量打开,然后按照以下图片红框标出的步骤执行。
将路径复制上之后,点击三个确定退出去,配置就完成啦。
二.习题练习
习题我将主要按照解题的主题算法(比如枚举、模拟、排序、查找等)进行整理分类,其中涉及到的数据结构不予分类。都是在网上整理的大佬分享,解题思路如有雷同,纯属我copy。解题我都是应用了C++,本科期间我只是系统地学习了C语言的语法,但上手C++还是比较快的,关于C++比C多出来的语法我在下面都会尽量注解一下。
为什么用c++而不是c?主要原因是c++拥有一个广泛的标准库,提供了大量的预定义函数和数据结构,如字符串、列表、映射等,还有现成的排序算法,都可以直接使用,编写题目时候就不用任何算法都从头写起了。
P.S.题目描述我就不贴出来了,需要的家人自行搜索一下,应该都是网上有的题目。
1. 暴力求解
1.1 枚举
1.1.1 用连续自然数之和来表达整数
数据结构:一维向量
最粗暴的枚举法作答,注意输出格式要符合题意就行。
#include <iostream>
#include <vector>
using namespace std;
bool isCon(vector<int> v)
{
for (int i = v.size() - 1; i > 0; --i)
{
if (v[i] != v[i - 1] - 1)
return false;
}
return true;
}//判断向量里的元素是否连续
int sum(vector<int> v)
{
int sum = 0;
for (int i : v)
sum += i;
return sum;
}//求和函数
void print(vector<int> v, int n)
{
cout << n << "=";
if (v.size() == 1) {
cout << v[0] << endl;
return;
}
int i;
for (i = v.size() - 1; i > 0; --i)
cout << v[i] << "+";
cout << v[i] << endl;
}
int main()
{
int n;
int sumcount = 0;
vector<int> v;
cin >> n;
for (int i = n; i >= 1; --i)
{
for (int j = i; j >= 1; --j) {
v.push_back(j);
if (sum(v) == n && isCon(v)) {
print(v, n);//自制print函数
++sumcount;
v.clear();//清空向量
break;
}
if (sum(v) > n) {
v.clear();
break;
}
}
}
cout << "Result:" << sumcount;
}
1.1.2 素数之积
题目本身没有难度,三个判断,是否能整除,除数是否是素数,商是否是素数,三个条件缺一不可。但判断是否是素数这个条件本身在涉及数学问题时考察比较多,要记清楚细节。
平方函数sqrt()调用之前注意声明cmath库。
#include <iostream>
#include <cmath>
using namespace std;
bool is_su(int x){
for(int i=2;i<=sqrt(x);i++){//注意这里的i范围一定要小于等于!!平方
if(x%i == 0){
return false;
}
}
return true;
}
int main(){
int k;
cin >> k;
bool res = false;
for(int i=2;i<=sqrt(k);i++){
if(k%i == 0 && is_su(i)){//整除素数i
if(is_su(k/i)){
//如果商也是素数,那么符合要求直接输出
cout << i << " " << k/i << endl;
res = true;
break;
}
}
}
if(!res){
cout << -1 << " " << -1 << endl;
}//如果没有找到符合要求的输出,输出-1 -1
return 0;
}
1.2 模拟
1.2.1 围棋的气
数据结构:向量、字符串、无序集合
这个题目是一个二维数组的处理,为了得出气的数量,我们要对输入进行按行读取,分组坐标等操作,为了更好地进行数据处理,题解使用了string类型的向量来处理。
从main函数看起:
1.整行读入一个string里,再用空格分隔坐标,存入一个vector<string>
2.将vector放入transform处理,{x1,y1,x2,y2}变成{x1_y1,x2_y2}
3.将黑子和白子的坐标一齐放入counting函数处理,将每个棋子上下左右的位置(不超出棋盘范围)不重复地放入集合,再扣除有棋子的坐标得到气数
题解在我看来还是比较复杂,用到了很多c++ string类型变量独特的操作,以下代码几乎是逐行讲解了,可以再看看注解:
#include <iostream>
#include <string>
#include <vector>
#include <unordered_set>
using namespace std;
int maxside = 18;
int counting(vector<string>& alias,vector<string>& enemy){
unordered_set<string> count;//一种根据数值而非位置访问的数据结构,类似于集合
for(string a:alias){
count.insert(a);//遍历访问alias并插入count集合,注意不会存入相同值的元素
size_t pos = a.find("_");//a是字符串,这里查找字符串中下划线的位置,赋给pos
int x = stoi(a.substr(0,pos));//使用stoi将下划线之前的子串转换为整数并赋值给x
int y = stoi(a.substr(pos+1));//下划线之后的子串转换成整数赋给y
if(x>0){
count.insert(to_string(x-1)+"_"+a.substr(pos+1));//如果x大于0,生成一对下划线相连的坐标插入count集合,这个坐标代表当前位置xy上方的位置
}
if(x<maxside){
count.insert(to_string(x+1)+"_"+a.substr(pos+1));//如果x小于maxside,生成一对下划线相连的坐标插入count集合,这个坐标代表当前位置xy下方的位置
}
if(y>0){
count.insert(a.substr(0,pos)+"_"+to_string(y-1));//如果y大于0,生成一对下划线相连的坐标插入count集合,这个坐标代表当前位置xy左侧的位置
}
if(y<maxside){
count.insert(a.substr(0,pos)+"_"+to_string(y+1));//如果y小于maxside,生成一对下划线相连的坐标插入count集合,这个坐标代表当前位置xy右侧的位置
}
}
int res = count.size()-alias.size();//res是count元素个数减去alias元素个数
for(string e:enemy){
if(count.count(e)){
res--;//如果count中存在enemy中的一个元素,res减一
}
}
return res;//返回res值
}
//整个counting函数就是去数气的过程,输入的是两方棋子的坐标,利用了集合元素不重复的特点,将白子上下左右的空位放入集合(不重复),然后从中去掉有棋子的坐标,最终得到气的数量
vector<string> transform(vector<string>& locs){
vector<string> chess(locs.size()/2);
for(int i=0;i<locs.size();){
chess[i/2] = locs[i] + "_" + locs[i+1];
i += 2;
}
return chess;
}//transform函数将原本单独存放横竖坐标的字符串向量转化为存放一由下划线连接的x_y坐标字符串向量
int main(){
vector<string> locblacks;
vector<string> locwhites;
string input;
getline(cin,input);//读到换行就结束的整行读取
size_t pos = 0;//声明一个变量pos,用于查找空格
while((pos = input.find(" "))!=string::npos){
locblacks.push_back(input.substr(0,pos);
input.erase(0,pos+1);
}//循环在字符串中找空格,把空格前的字符串送进向量里,然后把原字符串这部分连同空格一起删除,直到找不到空格
locblacks.push_back(input);
//下面同样的操作提取白棋坐标
getline(cin,input);//读到换行就结束的整行读取
size_t pos = 0;//声明一个变量pos,用于查找空格
while((pos = input.find(" "))!=string::npos){
locwhites.push_back(input.substr(0,pos);
input.erase(0,pos+1);
}//循环在字符串中找空格,把空格前的字符串送进向量里,然后把原字符串这部分连同空格一起删除,直到找不到空格
locwhites.push_back(input);
vector<string> blacks = transform(locblacks);
bector<string> whites = transform(locwhites);
cout << counting(blacks,whites) << " " << counting(whites,blacks) <<endl;
//分别得出黑子的气和白子的气输出
return 0;
}
总结一下这个题目用到的语法:
首先是unordered_set类型变量的声明、插入insert。
还有string类型的整行读取getline(cin,str)、按内容查找位置find、截取子串substr、删除子串erase。
string转int函数 stoi(),其他类型转string to_string()。
2. 排序与查找
2.1 排序
2.1.1 寻找身高相近的小朋友
数据结构:一维向量
这道题目是比较简单的数组应用题,以下题解采用了向量表示,其实用普通数组也是可以的。首先是一个输入然后用sort函数快排,注意比较函数的规则要符合题目要求。
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;//c++格式开头和库函数调用
//比较函数用于排序
bool compare(int h1, int h2, int mingHeight) {
int different1 = abs(h1 - mingHeight);
int differert2 = abs(h2 - mingHeight);
if (different1 == differert2) {
return h1 < h2;
}//更小的排前面
return different1 < differert2;
}
int main() {
int mingHeight, numFriends;
cin >> mingHeight >> numFriends;//c++的输入
vector<int> frinedHeights(numFriends);//向量,类似于数组用法,长度更灵活
for (int i = 0; i < numFriends; i++) {
cin >> frinedHeights[i];
}//数据存于向量之中
sort(frinedHeights.begin(), frinedHeights.end(), [&](int h1, int h2) {
return compare(h1, h2, mingHeight);
});//sort算法调用按照比较函数的规律快速排序
for (int height : frinedHeights) {
cout << height << " ";
}//c++输出
return 0;
}
2.1.2 开源项目热搜榜
这个题目的思路是:
1.构建包含项目名称,项目热度的自定义类。
2.通过输入的权值和统计数据计算项目热度,把项目名称和热度赋值给自定义类的向量。
3.按照热度和字典序排序。
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
class Project{
public:
string name;
int hot;
Project(string name,int hot):name(name),hot(hot){}//构造函数,用来初始化
};
bool compare(Project& a,Project& b){
if(a.hot == b.hot){
return a.name < b.name;
}
return a.hot > b.hot;
}//比较函数,考虑了按热度降序和字典序排序两种情况
int main(){
int n;
vector<int> weights(5);//权值数组
vector<Project> projects;//自定义类向量
cin >> n;
for(int i=0;i<5;i++){
cin >> weights[i];
}
for(int i=0;i<n;i++){
string name;
cin >> name;
vector<int> data(5);
for(int j=0;j<5;j++){
cin >> data[j];
}
int hot = 0;
for(int j=0;j<5;j++){
hot += weights[j]*data[j];
}//热度计算
Project p(name,hot);
projects.push_back(p);
}
sort(projects.begin(),projects.end(),compare);
for(Project a:projects){
cout << a.name << endl;
}
return 0;
}
2.1.3 整数对最小和
算最小/最大的几个数之和类的问题,可以先用快排整理大小,然后再进行求和输出。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
int n1,n2;
vector<int> arr1,arr2;
int t;
cin >> n1;
for(int i=0;i<n1;i++){
cin >> t;
arr1.push_back(t);
}//把第一组数据读取
cin >> n2;
for(int i=0;i<n2;i++){
cin >> t;
arr2.push_back(t);
}//读取第二组数据
int n;
cin >> n;//读取需要的整数对个数
vector<int> sum;
for(int a:arr1){
for(int b:arr2){
sum.push_back(a+b);
}
}//把所有可能的整数对放入sum向量中
sort(sum.begin(),sum.end());//快排sum
int res = 0;
for(int i=0;i<n;i++){
res = res + sum[i];
}
cout << res << endl;
return 0;
}
2.2 查找
2.2.1 API集群负载统计
数据结构:向量、字符串
思路:
1.按行读取每个完整路径放入向量q,读取目标层次和字符
2.每个路径拆分层次放入向量s
3.循环对比查找目标
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main(){
int n;
vector<string> q;
cin >> n;
string t;
getline(cin,t);
for(int i=0;i<n;i++){
getline(cin,t);
q.push_back(t);
}//注意这里按行循环输入字符向量的格式,需要一个中间变量t为载体
int ceng;
string name;
cin >> ceng >>name;
int res = 0;
for(int i=0;i<n;i++){
vector<string> s;
size_t pos = 0;
while((pos = q[i].find("/"))!=string::npos){
s.push_back(q[i].substr(0,pos));
q[i].erase(0,pos+1);
}//s[0]为空
s.push_back(q[i]);//注意不要忘记尾部字符
if(s[ceng] == name){
res++;
}
}
cout << res << endl;
}
3. 贪心策略
贪心策略常用于求解最优化问题,其核心思想是,总是选择当前状态下最优的策略。也就是说,它并不以整体最优进行考虑,而只考虑当前这一步。因此,这种方法能够得到局部最优解。
注意,只有对于特定的,局部最优解能收敛到全局最优解的问题,才可以采取贪心策略求解。否则应该考虑使用动态规划解决。
3.1.1 鸡兔同笼
计算最多动物数的时候,优先考虑脚少的鸡;计算最少动物数的时候,优先考虑脚多的兔子,直到脚的数目无法构成兔子时再考虑脚少的鸡。典型的贪心策略。
#include <iostream>
#include <cstdio>
using namespace std;
int main(){
int max = 0;
int min = 0;//最多和最少的数量初始都为0
int n;
cin >> n;
if(n%2 == 0){
//能被二整除才有数值
max = n/2;//最多就是全是坤的情况
min = n/4 + (n-4*(n/4))/2;
}
cout << max << " " << min << endl;
return 0;
}
3.1.2 小朋友来自多少小区
#include <iostream>
#include <vector>
#include <unordered_map>
#include <sstream>
#include <cmath>
using namespace std;
int getres(vector<int>& nums) {
unordered_map<int, int> cnts;//统计学生数量和小区数量(同等数量学生)的映射
for (int num : nums) {
cnts[num] = cnts.find(num) == cnts.end() ? 1 : cnts[num] + 1;
}//记录学生数量相同的小区
int ans = 0;
for (auto& pair : cnts) {
int key = pair.first;
int total = key + 1;
ans += ceil(static_cast<double>(pair.second) / total) * total;//贪心策略核心,优先认为相同学生数量的小区是同一个小区
}
return ans;
}
int main(){
string input;
getline(cin,input);
istringstream iss(input);
vector<int> nums;
int num;
while(iss >> num){
nums.push_back(num);
}//字符串转换数据流
cout << getres(nums) << endl;
return 0;
}
4. 递归策略
4.1.1 传递悄悄话
数据结构:二叉树(队列、向量)
二叉树的递归遍历思路:队列不为空的循环:取队列头、去队列头、进入递归主体操作:操作结点、放入新节点进队列。
#include <iostream>
#include <queue>
#include <string>
#include <vector>
#include <sstream>
using namespace std;
queue<int> q;//用来遍历二叉树的队列,存脚标
int result = 0;
int check(int index,vector<int>& nums,int father){
if(index < nums.size() && nums[index]!=0){//如果结点存在
nums[index] += nums[father];//累加结点和父节点的值,更新结点值
q.push(index);//存在的结点处理后放入队列成为新的父节点
if(nums[index]>result){
result = nums[index];
}//最大的节点值替代result
}
return result;
}
int main(){
string input;
int num;
vector<int> nums;
getline(cin,input);
istringstream iss(input);
while(iss >> num){
nums.push_back(num);
}//把二叉树的数据读进去
//进入递归循环操作二叉树
q.push(0);
while(!q.empty()){
int father = q.front();
q.pop();
//层次遍历二叉树
check(father*2+1,nums,father);
check(father*2+2,nums,father);
}
cout << result << endl;
}