第一周(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; }
解题中的问题与收获:
以前我误以为创建结构体变量都要分配内存,现在知道了:
-
struct ListNode* bl = p;
:这行代码创建了一个新的指针bl
,并让它指向p
所指向的内存地址。在这种情况下,你并没有创建新的ListNode
,只是创建了一个新的指针,并让它指向已经存在的ListNode
。所以,你不需要为bl
分配内存。 -
struct ListNode* new = (struct ListNode*)malloc(sizeof(struct ListNode));
:这行代码创建了一个新的ListNode
,并为其分配了内存。在这种情况下,创建了一个新的ListNode
,并且需要为其分配内存。所以,必须使用malloc
或其他内存分配函数为其分配内存。总结一下,当你只是想要创建一个新的指针并让它指向已经存在的结构体时,你可以使用第一种方式。但是,当你想要创建一个新的结 构体时,你必须使用第二种方式,并为其分配内存。而且两个结构体指针指向的是同一个内存单元,而不是指向变量本身,因此,其中一个改变并不会影响到另外一个。
第五题:
题目来源:活动 - AcWing
题目描述:
实现一个单链表,链表初始为空,支持三种操作:
-
向链表头插入一个数;
-
删除第 k个插入的数后面的数;
-
在第 k个插入的数后插入一个数。
现在要对该链表进行 M次操作,进行完所有操作后,从头到尾输出整个链表。
注意:题目中第 k个插入的数并不是指当前链表的第 k个数。例如操作过程中一共插入了 n个数,则按照插入的时间顺序,这 n个数依次为:第 11 个插入的数,第 22 个插入的数,…第 n个插入的数。
输入格式
第一行包含整数 M,表示操作次数。
接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:
-
H x
,表示向链表头插入一个数 x。 -
D k
,表示删除第 k个插入的数后面的数(当 k为 0时,表示删除头结点)。 -
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.在处理庞大的数据的时候,还是用静态链表,不过与动态链表相比的话,还是动态便于理解。