第一周算法题

第一周(10.23-10.29)

第一题:

题目来源:活动 - AcWing

题目描述:给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。

逆序对的定义如下:对于数列的第 i 个和第 j个元素,如果满足 i<j 且 a[i]>a[j],则其为一个逆序对;否则不是。

输入格式

第一行包含整数 n,表示数列的长度。

第二行包含 n 个整数,表示整个数列。

输出格式

输出一个整数,表示逆序对的个数。

数据范围

1≤n≤100000,

数列中的元素的取值范围 [[1,10e9]。

输入样例:
6
2 3 4 5 6 1
输出结果:
5
解题思路:
本题数据范围过大,很容易运行超时,因此主要运用分治思想,利用到归并排序的算法,分成若干段,递归处理。而选择归并排序而不是其他排序,主要是利用归并排序时不断二分,且会分割成两个有序数列,右边数列中的数字一定排在左序列数字右边,因此每一次从左数列切换右数列,都可视为找到了一组逆序,并可以确定在左下标后的每一个数字都与该右标为逆序,从而直接相减大大节省遍历时间,以实现十万个数据的处理。
代码如下:
归并排序:
#include <stdio.h>
int temp[1000000];
long long sort(int *arr, int l, int r) {
if (l >= r) {
    return 0;
}
int mid = (l + r) >> 1;
long long nums=sort(arr, l, mid)+ sort(arr, mid + 1, r);
int i = l, j = mid + 1;
int k = 0;
while (i <= mid && j <= r) {
    if (arr[i] <= arr[j]) {
        temp[k++] = arr[i++];
        
    } else {        
        temp[k++] = arr[j++];
        nums += (mid - i) + 1;  
    }
}
while (i <= mid) {
    temp[k++] = arr[i++];
}
while (j <= r) {
    temp[k++] = arr[j++];
}
for (i = l, k = 0; i <= r; ++i, ++k) {  
    arr[i] = temp[k];
}
return nums;
}
int main(){
    int n;
    scanf("%d",&n);
    int i=0;
    long long k=0;
    int arr[n];
    for(i=0;i<n;i++){
        scanf("%d",&arr[i]);
    }
    k=sort(arr,0,n-1); 
    printf("%lld",k);
    return 0;
}
当然,不考虑10w的数据量和归并O(nlogn)的复杂度,冒泡无疑是最简单的,只是O(n^2)在处理大数据时会超时,代码如下:
冒泡排序:
#include <stdio.h>
int main(){
    int n;
    scanf("%d",&n);
    int arr[n];
    int i=0;
    for(i=0;i<n;i++){
        scanf("%d",&arr[i]);
    }
    int nums=0;
    while(n){
        for(i=0;i<n-1;i++){
            if(arr[i]>arr[i+1]){
                int k=arr[i];
                arr[i]=arr[i+1];
                arr[i+1]=k;
                nums++;
            }
        }
        n--;
    }
    printf("%d",nums);  
    return 0;
} 

解题中的问题与收获:
1.忽略了数据过大,整型会溢出。
​
2.错误的认为本题不需要排序,可删除
​
`for (i = l, k = 0; i <= r; ++i, ++k) {  
    arr[i] = temp[k];`
`}`
​
这一步,结果是会导致产生无序数组。

第二题:

题目来源:

乐程招新模拟题。

题目描述:

JOJO 比较笨不会使用头文件。但是他想处理一批数字来得到他们指定的幂。所以答案中不能用math的头文件或者pow等函数哦~ 题目给定 n 组数据 a, b​ , p​, 对于每组数据求出 a的b次方​​对p取模​的值。

输入格式:

第一行包含整数 n。 接下来 n 行,每行包含三个整数 a, b , p。

输出格式:

对于每组数据,输出一个结果,表示 a的b次方对p的值。 每个结果占一行。

数据范围
1≤n≤100000,
1≤a, b, p≤2×10e9;

输入样例:

2
3 2 5
4 3 9

输出样例:

4
1
解题思路:

本题数据范围过大,很容易发生溢出和运行超时,为了避免溢出,因此,可以对每一步进行取模并使用long long,但是这只能解决溢出的问题,不能解决超时。所以将用到快速幂算法,将时间复杂度控制在o(log n),不断的将指数除以2,从而大大节省时间。

代码如下:
#include <stdio.h>
long long pm(long long a, long long b, int p) {
    long long result = 1;
    a %= p;    
    while (b > 0) {
        if (b % 2 == 1) {
            result = (result * a) % p;
        }       
        a = (a * a) % p;
        b /= 2;
    }   
    return result;
}

int main() {
    int n;
    scanf("%d", &n);    
    int arr[n][3];
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < 3; j++) {
            scanf("%d", &arr[i][j]);
        }
    }
for (int i = 0; i < n; i++) {
    long long a = arr[i] [0];
    long long b = arr[i] [1];
    int p = arr[i] [2];
    long long result = pm(a, b, p);
    printf("%lld\n", result);
}

return 0;
}

解题中的问题与收获:

1.学习了一种新的数学方法。

第三题:

题目来源:活动 - AcWing

题目描述:给定两个正整数(不含前导 0),计算它们的和。

输入格式

共两行,每行包含一个整数。

输出格式

共一行,包含所求的和。

数据范围

1≤整数长度≤100000

输入样例:
12
23
输出样例:
35
解题思路:

很简单的高精度加法,留个模板题在这,为了避免溢出,直接用字符串存储。

代码如下:

#include <stdio.h>
#include <string.h> 
int main(){
	char str1[100001];
	char str2[100001];
	gets(str1);
	gets(str2);
	int a[100000]={0};
	int b[100000]={0};
	int c[100000]={0};
	int la,lb,lc;
	la=strlen(str1);
	lb=strlen(str2);
	 int i=0;
	 for(i=0;i<la;i++){
	 	a[la-i]=str1[i]-'0'; 
	 }
	for(i=0;i<lb;i++){
	   b[lb-i]=str2[i]-'0';	
	}
	int max=la;
	if(lb>max){
		max=lb;
	}
	for(i=1;i<=max;i++){
	c[i]+=a[i]+b[i];
		c[i+1]=c[i]/10;		
		c[i]%=10;
	}
	if(c[max+1]!=0){
		for(i=max+1;i>=1;i--){
			printf("%d",c[i]);
		}
	}
	else
	for(i=max;i>=1;i--){
		printf("%d",c[i]); 
	}
	return 0;
} 

第四题:

题目来源:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
题目描述:

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解题思路:

没什么可说的,直接暴力求解,三次遍历,一次用来判断是否两个链表同时存在数字,第二次用来处理其中一个链表还没到底,而另一个链表已经走完的情况,第三次用来消除前置零,纯暴力,无美感。

代码如下:
我的代码:
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) {

  struct ListNode *head=(struct ListNode*)malloc(sizeof(struct ListNode));

   struct ListNode *p=head;

  head->next=NULL;

head->val=0;

while(l1&&l2){

  struct ListNode*new=(struct ListNode*)malloc(sizeof(struct ListNode));

  head->next=new;

  new->next=NULL;

  new->val=0;

  int k=(l1->val+l2->val);

  new->val+=(k/10);

  head->val+=k%10;

  if(head->val>=10){

​    new->val+=1;

​    head->val-=10;

  }

  head=head->next;

  l1=l1->next;

  l2=l2->next;

}

while(l1){

struct ListNode*new=(struct ListNode*)malloc(sizeof(struct ListNode));

  head->next=new;

  new->next=NULL;

  new->val=0;

  int k=l1->val;

  new->val+=(k/10);

  head->val+=k%10;

  if(head->val>=10){

​    new->val+=1;

​    head->val-=10;

  }

  head=head->next;

  l1=l1->next;

}

while(l2){

struct ListNode*new=(struct ListNode*)malloc(sizeof(struct ListNode));

  head->next=new;

  new->next=NULL;

  new->val=0;

  int k=l2->val;

  new->val+=(k/10);

  head->val+=k%10;

  if(head->val>=10){

​    new->val+=1;

​    head->val-=10;

  }

  head=head->next;

  l2=l2->next;

}

struct ListNode*bl=p;

for(bl;bl->next;bl=bl->next){

  if(bl->next->next==NULL&&bl->next->val==0){

​    bl->next=NULL;

​    break;

  }

}

return p;

}
或者AI的这种模式,至少有美感多了:
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) {

  struct ListNode *head=(struct ListNode*)malloc(sizeof(struct ListNode));

  head->next=NULL;

  head->val=0;

  struct ListNode *cur = head;

  int carry = 0;

  while(l1 || l2 || carry){

​    int sum = (l1 ? l1->val : 0) + (l2 ? l2->val : 0) + carry;

​    struct ListNode*new=(struct ListNode*)malloc(sizeof(struct ListNode));

​    new->next=NULL;

​    new->val=sum%10;

​    cur->next=new;

​    carry=sum/10;

​    cur=cur->next;

​    if (l1) l1=l1->next;

​    if (l2) l2=l2->next;

  }

  return head->next;

}

解题中的问题与收获:

以前我误以为创建结构体变量都要分配内存,现在知道了:

  1. struct ListNode* bl = p;:这行代码创建了一个新的指针 bl,并让它指向 p 所指向的内存地址。在这种情况下,你并没有创建新的 ListNode,只是创建了一个新的指针,并让它指向已经存在的 ListNode。所以,你不需要为 bl 分配内存。

  2. struct ListNode* new = (struct ListNode*)malloc(sizeof(struct ListNode));:这行代码创建了一个新的 ListNode,并为其分配了内存。在这种情况下,创建了一个新的 ListNode,并且需要为其分配内存。所以,必须使用 malloc 或其他内存分配函数为其分配内存。

    总结一下,当你只是想要创建一个新的指针并让它指向已经存在的结构体时,你可以使用第一种方式。但是,当你想要创建一个新的结 构体时,你必须使用第二种方式,并为其分配内存。而且两个结构体指针指向的是同一个内存单元,而不是指向变量本身,因此,其中一个改变并不会影响到另外一个。

第五题:

题目来源:活动 - AcWing

题目描述:

实现一个单链表,链表初始为空,支持三种操作:

  1. 向链表头插入一个数;

  2. 删除第 k个插入的数后面的数;

  3. 在第 k个插入的数后插入一个数。

现在要对该链表进行 M次操作,进行完所有操作后,从头到尾输出整个链表。

注意:题目中第 k个插入的数并不是指当前链表的第 k个数。例如操作过程中一共插入了 n个数,则按照插入的时间顺序,这 n个数依次为:第 11 个插入的数,第 22 个插入的数,…第 n个插入的数。

输入格式

第一行包含整数 M,表示操作次数。

接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:

  1. H x,表示向链表头插入一个数 x。

  2. D k,表示删除第 k个插入的数后面的数(当 k为 0时,表示删除头结点)。

  3. I k x,表示在第 k个插入的数后面插入一个数 x(此操作中 k均大于 0)。

输出格式

共一行,将整个链表从头到尾输出。

数据范围

1≤M≤100000

输入样例:
10
H 9
I 1 1
D 1
D 0
H 6
I 3 6
I 4 5
I 4 5
I 3 4
D 6
输出样例:
6 4 6 5
解题思路:

通过sscanf处理输入的字符串,针对每种情况写出不同的函数的接口,从而实现它的操作,接下来将展示静态链表和动态链表两种实例。可惜的是,我的动态链表因为要不断分配内存,运行时间较慢,无法通过十万庞大的数据量,静态链表是我看的题解,不然做不出来。

动态链表:
运用一个结构体变量pos来记录它们是当前第几个被插入的变量。
#include <stdio.h>
#include <stdlib.h>
struct node {
	int data;
	struct node* next;
	int pos;
};
void head_insert(struct node* head, int data, int* pos) {
	struct node* new = (struct node*)malloc(sizeof(struct node));
	new->data = data;
	new->pos = *(pos);
	new->next = head->next;
	head->next = new;
}
void add_node(struct node* head, int position, int data, int* pos) {
	struct node* p = head;
	for (p; p; p = p->next) {
		if (p->pos == position) {
			break;
		}
	}
	struct node* new = (struct node*)malloc(sizeof(struct node));
	new->data = data;
	new->next = p->next;
	p->next = new;
	new->pos = *(pos);
}
void del_node(struct node* head, int dposition, int* pos) {
	struct node* p = head;
	if (dposition == 0) {
		struct node *k =head->next;
		head->next = head->next->next;		
		free(k);
		return;
	}
	for (p; p; p = p->next) {
		if (p->pos == dposition) {
			break;
		}
	}
	struct node* k = p->next;
	p->next = k->next;
	free(k);
}
void print(struct node* head) {
	struct node* p = head->next;
	while (p) {
		printf("%d ", p->data);
		p = p->next;
	}
	printf("\n");
}
int main() {
	int n;
	scanf("%d", &n);
	getchar();
	struct node* head = (struct node*)malloc(sizeof(struct node));
	head->next = NULL;
	head->data = -1;
	head->pos = -1;
	int index = 1;
	int* pos = &index;
	while (n--) {
		char str[100000];
		gets(str);
		char c = str[0];
		if (c == 'H') {
			int add;
			sscanf(str + 2, "%d", &add);
			head_insert(head, add, pos);
			index++;
			continue;
		}
		if (c == 'I') {
			int k, j;
			sscanf(str + 2, "%d %d", &k, &j);
			add_node(head, k, j, pos);
			index++;
			continue;
		}
		if (c == 'D') {
			int k;
			sscanf(str + 2, "%d", &k);
			del_node(head, k, pos);
			continue;
		}
	}
	print(head);
	free(head);
	return 0;
}

静态链表:
#include <stdio.h>
int i=0, head, point[100010], data[100010];
void head_insert(int add) {
	data[i] = add;
	point[i] = head;
	head=i;
	i++;
}
void add_node(int k,int j) {
	data[i] = j;
	point[i] = point[k];
	point[k] = i;
	i++;
}
void del_node(int k) {
	point[k] = point[point[k]];
}
int main() {
	int n;
	scanf("%d", &n);
	getchar();
	head = -1;
	i = 0;
	while (n--) {
		char str[100000];
		gets(str);
		char c = str[0];
		if (c == 'H') {
			int add;
			sscanf(str + 2, "%d", &add);
			head_insert(add);
			continue;
		}
		if (c == 'I') {
			int k, j;
			sscanf(str + 2, "%d %d", &k, &j);
			add_node(k-1,j);
			continue;
		}
		if (c == 'D') {
			int k;
			sscanf(str + 2, "%d", &k);
			if (!k)head = point[head];
			del_node(k-1);
			continue;
		}
	}
	for (i = head; i != -1; i = point[i]) {
		printf("%d ", data[i]);
	}
	return 0;
}

解题中的问题与收获:

1.在处理庞大的数据的时候,还是用静态链表,不过与动态链表相比的话,还是动态便于理解。

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Xiao Ling.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值