编程题算法总结
求最大公约数 最小公倍数
最大公约数
辗转相除法
大的a除小的b,得到余数如果是0,那么b就是最大公约数,否则就取余数做那个小的,本来的b就成了大的继续操作。
int n,m;
//辗转相除法,ab最大公约数 = ab余数和b的最大公约数
int yu,a,b;
a = n>m?n:m;
b = n>m?m:n;
while(1)
{
yu = a % b;
if(yu == 0) break;
a = b;
b = yu; //已经是余数了,一定比b小
}
//b就是
更相减损法
大的a 小的b,a-b=0就找到了,否则a = a-b循环
int a,b;
int cal = a*b;
//更相减损法 ,a = a-b
while(a!=b)
{
if(a>b)
a = a-b;
else b = b-a;
}
//b就是
最小公倍数
偷懒法
先求最大公约数,由最大公约* 最小公倍数 = a* b
迭乘法
最小公倍数一定是其中某个数的n倍
int a,b;
int cal = a*b;
int i = 1;
//迭乘法求最小公倍数
while(a*i%b!=0)
{
i++;
}
//a*i就是
正序数组插值
int arr[10],x;
//正序数组和要插入的值
int i = 8;
while(i>=0&&arr[i]>x) //注意从后面开始遍历,移动到后面的那个
{
arr[i+1] = arr[i];
i--;
}
arr[i+1] = x;
for (int i = 0; i < 10; i++)
{
printf("%d\n",arr[i]); //打印输出
}
以上其实是插入的算法,如果是删除,需要从前向后遍历,然后把后面的元素往前移
编辑距离
设A和B是两个字符串。我们要用最少的字符操作次数,将字符串A转换为字符串B。这里所说的字符操作共有三种:
- 删除一个字符;
- 插入一个字符;
- 将一个字符改为另一个字符。
对任给的两个字符串A和B,计算出将字符串A变换为字符串B所用的最少字符操作次数
> 三种操作分别是:插入 删除 替换
需要清楚的点:
-
两字符串A和B,给A插入相当于给B删除,反之亦然(例如cat和cate)
-
替换A相当于替换B(例如cat和fat)
-
故本质操作就三种 : ① A插 ② B插 ③ 替换
dp[i][j]表示从 A 的前 i 个 字符转换到 B 的 前 j 个 字 符所需的最短步数
(例如A是qwert B是aserty 那么最终输出结果就是dp[5][6])
-
dp[i-1][j-1]到dp[i][j]需要进行替换操作。若A的第 i 位(其实就是当前操作位)等于 B的第 j 位(当前操作位),那么就不需要替换,因此dp[i][j] = dp[i-1][j-1] ,否则dp[i][j] = dp[i-1][j-1]+1
-
dp[i-1][j]到d[i][j]需要进行删除操作。dp[i][j] = dp[i-1][j] + 1
-
dp[i][j-1]到d[i][j]需要进行添加操作。dp[i][j] = dp[i][j-1] + 1
★当A与B末位相同时:
dp[i][j] = min(dp[i][j−1]+1,dp[i−1][j]+1,dp[i−1][j−1])
★当A与B末位不相同时:
dp[i][j] =min(dp[i][j−1]+1,dp[i−1][j]+1,dp[i−1][j−1]+1)
那么,我们要怎么从dp[0][0] 到 dp[i][j]呢?
很简单,每一步做出判断,替换、删除、添加哪个短就选哪个!
这里需要注意
- dp[a][b]代表着前面所有步数的最短选择!
- 由于不知道AB长度,因此A插和B插不可以混为一谈!
注意事项:
边界情况,A或B为空,直接输出不为空的数组长度即可
#include <stdio.h>
using namespace std;
int main(){
//注意!!数组是从0开始索引的,所以第i位就是i-1的索引,以此类推!!
string a,b;
getline(cin,a);
getline(cin,b);
int len1 = a.length();
int len2 = b.length(); //接受输入记录长度
int dp[len1][len2]; //定义dp数组
for (int i = 0; i < len1; i++) {dp[i][0] = i;}
for (int j = 0; j < len2; j++) {dp[0][j] = j;} //边界情况,同时也是初始化
for (int i = 1; i < len1; i++)
{
for (int j = 1; j < len2; j++) //ij从1开始,因为已经初始化过了,从0开始会溢出
{
if(a[i-1] == b[j-1]) //A的第i位与B的第j位相同的情况
dp[i][j] = min(min(dp[i][j-1]+1, dp[i-1][j]+1),dp[i-1][j-1]);
else
dp[i][j] = min(min(dp[i][j-1]+1, dp[i-1][j]+1),dp[i-1][j-1]+1);
}
}
cout<<dp[len1-1][len2-1]<<endl;
return 0;
}
寻找回文子串
#include<iostream>
using namespace std;
int main()
{
string s;
while(getline(cin,s))
{
int len = s.length();
int dp[len][len]; //dp[i][j]表示i-j段是否为回文,是的话就表示长度 否的话就是0
//从i处,一个一个改变终点直到结尾,是否存在回文?不存在的话,i往前走一个重复上述操作。
//那么,怎么判断某段是否是回文呢?
//ij相等 是回文 例如a
//ij差一且si=sj,比如aa,是。若si≠sj,不是。
//ij差大于1,且si=sj,比如abcba,此时看s[i+1] s[j-1]是否为回文,
//若i从开头开始遍历,会出现abeda这种,虽然a=a,但是bed是否为回文并没有判断!!!!
int maxlen = 1;
for(int i = len-1; i >= 0; i--){
for(int j = i; j < len; j++){
if(i == j)
{
//maxlen = 0;
dp[i][j] = 1;
}
if(s[i]==s[j] && j==i+1){
maxlen=max(maxlen,2);
dp[i][j] = 1; //记录一下i-j是回文串
}
if(s[i]==s[j] && j>i+1 && dp[i+1][j-1]){
maxlen = max(maxlen,j-i+1); //一定要注意写max!!!!
dp[i][j] = 1;
}
}
}cout<<maxlen<<endl;
}
return 0;
}
排序
冒泡
每一趟排好一个数,一共要排n趟,每趟遍历n-i个数,因为前i个已经在前i趟排好了!!
void BubbleSort_V1( int data[], int n )
{
int i, j, k, t;
bool flag;//提前结束排序的标志,1表示提前结束
for( i = 1; i <= n-1; i++ )
{
flag = 1;
for( j = 1; j <= n-i; j++ )
{
if( data[j+1] < data[j] )
{
flag = 0;
t = data[j+1];
data[j+1] = data[j];
data[j] = t;
}
}
if( flag == 1 ) break;
}
}
快排
是对冒泡的改进
- 先从数列中取出一个数作为基准数。
- 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
- 再对左右区间重复第二步,直到各区间只有一个数。
注意!!!swap必须传入指针变量才能当api调用!!!
选择排序
跟冒泡很相近,区别在于选择排序一轮只排一个数,比如找到最小的放在第一个位置
#include<stdio.h>
int main(){
int arr[5] = {9,7,4,8,6};
for(int i = 0; i < 4; i++)
{
for (int j = i + 1; j < 5; j++)
{
if(arr[i]<arr[j])
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
//printf("%d %d ",i,arr[i]);
}
}
}//printf("\n");
for (int i = 0; i < 5; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
很他妈神奇,j从0的话,就是正序排,是因为j从0就相当于固定j
链表
初始化和尾插
初始化其实就是新建个头节点然后分配内存再置空
Node* initLinkedList() {
Node* head = (Node*)malloc(sizeof(Node)); // 为头节点分配内存
if (head == NULL) {
printf("内存分配失败");
exit(1);
}
head->next = NULL; // 只有这句是有用的!!!!!!!!!!
return head;
}
区别
-
Node* head = NULL;:这是将头节点的指针初始化为 NULL,表示链表为空,没有任何节点。
-
Node* head = (Node*)malloc(sizeof(Node));:这是为头节点分配了内存空间,并将指针指向这块内存空间。此时头节点并不为空,而是一个指向已分配内存的节点指针。然而,此时头节点还没有与其他节点链接在一起,所以 head->next 设置为 NULL。
-
总结起来,Node* head = NULL; 表示链表为空,而 Node* head = (Node*)malloc(sizeof(Node)); 表示链表只有一个头节点,但还没有其他节点与其链接。
在实际使用中,通常会将 Node* head = (Node*)malloc(sizeof(Node)); 与后续的插入操作写在一起!!!
#include<stdio.h>
#include<stdlib.h>
typedef struct Student{
double id;
double score;
struct Student* next;
}Node;
//传入指向头指针(头指针指向头节点)的指针(因为要改变指向头节点的头指针)、要插入的值
//如果传入Node*head,只能改变Node head(注意不是Node*head),head只是一个结构体,已经不是一个指向结构体的指针了!!!!
void AddatEnd(Node** head, int id, int score)
{
Node* new_node = (Node*)malloc(sizeof(Node));//固定写法,背下来吧
new_node->id = id;
new_node->score = score;
new_node->next = NULL; //记得给next赋值NULL
Node *cur = *head; //头节点还需要传出,不可以随意更改,因此使用临时节点遍历
if(*head ==NULL)
{
*head = new_node; //头指针为空,意思是没有头节点(头指针指向头节点,头指针存放的地址就是头节点这个结构体的地址)
return;
}
while(cur->next!=NULL)
{
cur = cur->next;
}
cur->next = new_node;
}
void Print(Node *head)
{
Node *cur = head;
while(cur!=NULL)
{
printf("%.0lf,%.0lf\n",cur->id,cur->score);
cur = cur->next;
}
printf("\n");
}
//返回值为void,所以对入参操作时不返回,要想改变头指针,只有通过二级指针改变。
//返回值为Node*,对入参操作后直接返回给原函数,不用通过二级指针修改
int main(){
int n,m;
scanf("%d%d",&n,&m);
Node *head1 = NULL;
double c1,c2;
while(n--)
{
scanf("%lf%lf",&c1,&c2);
AddatEnd(&head1,c1,c2);
}
Node *head2 = NULL;
while(m--)
{
scanf("%lf%lf",&c1,&c2);
AddatEnd(&head2,c1,c2);
}
Print(head1);
Print(head2);
return 0;
}
如果写Node* head = NULL,那么插入操作时就要判断* head是否为NULL,是的话把新建的节点赋给他
如果写Node* head= (Node*)malloc(sizeof(Node))并判空,赋值head->next = NULL,就不用判空,直接插入就行
head->next不是头指针的数据域!!!而是head指向的头节点的数据域!!!!!
cur = cur->next并不会改变链表结构!!!因为本来就是指向next的!!!
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct Node{
int data;
struct Node* next;
}Node;
//初始化插值
void AddAtEnd(Node **head,int data){
Node* new_node = (Node*)malloc(sizeof(Node));
new_node->data = data;
new_node->next = NULL;
if(*head == NULL)
{
*head = new_node;
// printf("%d",new_node->data);
return;
}
Node*cur = *head;
while(cur->next!=NULL)
{
cur = cur->next;
}
cur->next = new_node;
}
//get,获取链表第a个元素
void GetNode(Node *head, int a){
if(a<=0&&head==NULL) //链表为空或者获取第-100个节点,都不合理
{
printf("get fail\n");
return;
}
a--;
Node *cur = head;
while(a&&cur->next!=NULL)//先a到头还是先next到头?先a说明可以获取,先next说明a大了
{
cur = cur->next;
a--;
}
if(a==0) printf("%d\n",cur->data);//a为0了,说明找到了,边界情况a=1也是满足的
else
{
printf("get fail\n");
}
}
//delete删除某节点
void DeleteNode(Node **head, int a){
if(a<=0&&(*head == NULL))
{
printf("delete fail\n");
return;
}
if(a==1)
{
Node *temp = *head;
*head = (*head)->next;
free(temp);
printf("delete OK\n");
return;
}
Node *cur = *head;
Node *prev = NULL;
a--;
while(a&&cur->next!=NULL)//先a到头还是先next到头?先a说明可以获取,先next说明a大了
{
prev = cur;
cur = cur->next;
a--;
}
if(a==0)
{
prev->next = cur->next;//a为0了,说明找到了
printf("delete OK\n");
}
else
{
printf("delete fail\n");
}
}
//Insert指定位置a插入b
void InsertNode(Node **head, int a, int b){
if(a<=0)
{
printf("insert fail\n");
return;
}
Node*temp = (Node*)malloc(sizeof(Node));
temp->data = b;
temp->next = NULL;
if(*head ==NULL&&a==1)
{
*head = temp;
printf("insert OK\n");
return;
}
else if(a ==1) //头插法
{
temp->next = *head;
*head = temp;
printf("insert OK\n");
return;
}
a--;
Node *cur = *head;
Node *prev = NULL;
while(a&&cur->next!=NULL)//先a到头还是先next到头?先a说明可以获取,先next说明a大了
{
prev = cur;
cur = cur->next;
a--;
}
if(a==0)
{
temp->next = cur->next;
prev->next = temp;
printf("insert OK\n");
}
else
{
printf("insert fail\n");
}
}
void showNode(Node *head){
if(head ==NULL)
{
printf("Link list is empty\n");
return;
}
Node*cur = head;
printf("%d",cur->data);
while(cur->next!=NULL)
{
cur = cur->next;
printf("%d ",cur->data);
}
}
int main(){
int n;
scanf("%d",&n);
int arr[n];
for(int i = n-1; i >= 0; i--)
{
scanf("%d",&arr[i]);
// printf("插入成功");
}
Node *head = NULL;
for (int i = 0; i < n; i++)
{
AddAtEnd(&head,arr[i]);
}
char commad[10] = "0000000000";
int a,b;
while(~scanf("%s",commad))
{
if(!strcmp(commad,"get"))
{
scanf("%d",&a);
GetNode(head,a);
}
if(!strcmp(commad,"delete"))
{
scanf("%d",&a);
DeleteNode(&head,a);
}
if(!strcmp(commad,"insert"))
{
scanf("%d%d",&a,&b);
InsertNode(&head,a,b);
}
if(!strcmp(commad,"show"))
{
showNode(head);
}
}
return 0;
}
静态链表
本质上就是个结构体数组,每个结构体存有数据域和一个int 索引,把每个节点(结构体)存在数组的固定位置,next就是数组的索引,不用指针了
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)