数据结构荣誉课-第一次结题报告
一.重复计数
题目
7-1 重复计数 (100 分)
在一个有限的正整数序列中,有些数会多次重复出现。请你统计每个数的出现次数,然后按数字在序列中第一次出现的位置顺序输出数及其次数。
输入格式:
第1行,1个整数N,表示整数的个数,(1≤N≤50000)。
第2行,N个正整数,每个整数x 都满足 1 ≤ x ≤2000000000。
输出格式:
若干行,每行两个用一个空格隔开的数,第一个是数列中出现的数,第二个是该数在序列中出现的次数。
输入样例:
在这里给出一组输入。例如:
12
8 2 8 2 2 11 1 1 8 1 13 13
输出样例:
在这里给出相应的输出。例如:
8 3
2 3
11 1
1 3
13 2
思路1
- 先读入题目中要求的N个整数,存到一个数组中。
- 引入cout计数,从第i个数开始(0<=i<=N-1),遍历数组从i+1到N-1的数据,如果有数字与第i个数相同则cout+1,直到每一个数字都遍历完成,再按照题目要求的顺序进行输出,则可以完成题目要求。
参考代码
#include<stdio.h>
#include<stdlib.h>
int main(){
int x, n;
scanf("%d", &n);
int a[50000];
for (int i = 0; i < n; i++){
scanf("%d", &a[i]);
}
int cont = 0, i, j;
for (int i = 0; i < n; i++){
cont = 1;
while(a[i]==-1){i++;if(i==n) exit(0);}
for (j = i+1; j < n; j++){
if (a[i] == a[j])
{
cont++;
a[j] = -1;
}
}
printf("%d %d\n", a[i], cont);
}
return 0;
}
思路2
使用二分查找来完成该题,提高时间效率。
使用队列来保存相对顺序。
- 读入所有数据,同时用队列记录元素值出现的相对顺序,之后对数据进行排序,最后输出该值的数量。
- sort()排序:STL中自带的一种高效的标准排序算法,可以排序任意线性存储的容器。
- lower_bound():在[begin,end)进行二分查找,返回大于或等于key的第一个元素位置的指针。如果所有元素都小于key,则返回end的位置指针。
- upper_bound():在[begin,end)进行二分查找,查找的关键字的上界,返回大于key的第一个元素位置的指针。
参考代码
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int a[50000];
int n;
queue<int> q;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(find(a,a+i,a[i])==a+i)
q.push(a[i]);
}
sort(a+1,a+n+1);
int val;
while(q.size()!=1)
{
val=q.front();
q.pop();
cout<<val<<" "<<upper_bound(a+1,a+n+1,val)-lower_bound(a+1,a+n+1,val)<<endl;
}
val=q.front();
q.pop();
cout<<val<<" "<<upper_bound(a+1,a+n+1,val)-lower_bound(a+1,a+n+1,val);
}
二.报数游戏 (100 分)
题目
输入格式:
一行,两个整数n和m。n表示游戏的人数,m表示报数出圈的数字,1≤n≤50000,1≤m≤100.
输出格式:
一行,n个用空格分隔的整数,表示所有人出圈的顺序
输入样例:
在这里给出一组输入。例如:
5 2
输出样例:
在这里给出相应的输出。例如:
2 4 1 5 3
思路
这是一道关于约瑟夫环的问题,关于这道题我使用了标记数组(当犯人活着时置为1,死亡时置为0),没有什么亮点,使用了计数变量num,当经过活着的人时num会+1,直到所有人都被枪决,最后按照顺序输出死亡者即可。
参考代码
#include<iostream>
const int maxn = 50001;
using namespace std;
int main(){
int n,m;
int s[maxn];
int count = 0;
int num = 0;
cin>>n>>m;
for(int i=1;i<=n;i++)
s[i] = 1;
while(count<n){
for(int i=1;i<=n;i++){
if(s[i] == 1)
num++;
if(num == m){
if(count!=n-1)
cout<<i<<" ";
else
cout<<i;
s[i] = 0;
count++;
num = 0;
}
if(count == n)
break;
}
}
cout<<endl;
return 0;
}
三.算术表达式计算 (100 分)
题目
任务: 计算算术表达式的值。
算术表达式按中缀给出,以=号结束,包括+,-,/四种运算和(、)分隔符。运算数的范围是非负整数,没有正负符号,小于等于109 。
计算过程中,如果出现除数为0的情况,表达式的结果为”NaN” ; 如果中间结果超出32位有符号整型范围,仍按整型计算,不必特殊处理。 输入保证表达式正确。
输入格式:
一行,包括1个算术表达式。算术表达式的长度小于等于1000。
输出格式:
一行,算术表达式的值 。
输入样例:
在这里给出一组输入。例如:
(1+30)/3=
输出样例:
在这里给出相应的输出。例如:
10
思路
- 有关该题的题解,我使用了两个栈,一个栈存放数字,另一个栈存放运算符号。
- 与一位数字计算不同,本题中的数字可能不为一位数字。当读到数字字符时,可以循环读入数字字符,并根据字符串转数字函数从而存储数字。
- 需要注意的是,在符号栈进栈之前,我们需要知道该符号的优先级,从而得知需要进栈/出栈。
- 遇到结束符号(本题为等号),不断处理栈顶符号即可(因为之前已经将能计算的全部计算,最后符号栈中符号的优先级关系一定符合正常运算)。
参考代码
#include<iostream>
#include<stack>
#include<string.h>
using namespace std;
char a[1001];
int quan(char ch){
if (ch=='(') return 1;
if (ch=='+'||ch=='-') return 2;
if (ch=='*'||ch=='/') return 3;
}
stack<char>s;
stack<long long>num;
long long caozuo(long long a,long long b,char x){
long long temp;
if(x=='+')
temp=a+b;
if(x=='-')
temp=a-b;
if(x=='*')
temp=a*b;
if(x=='/'&&b!=0)
temp=a/b;
if(x=='/'&&b==0){
printf("NaN");
exit(0);
}
return temp;
}
int shuzi(char k){
if(k=='0'||k=='1'||k=='2'||k=='3'||k=='4'||k=='5'||k=='6'||k=='7'||k=='8'||k=='9')
return 1;
return 0;
}
void start(char *a){
long long x,y,z;
int i;
for(i=0;a[i]!='=';i++){
if (a[i] == '(' || a[i] == ')' || a[i] == '+' || a[i] == '-' || a[i] == '*' || a[i] == '/') {
if (a[i] == '(') s.push(a[i]);
if (a[i] == ')') {
while (s.top() != '(') {
x=num.top();
num.pop();
y=num.top();
num.pop();
z=caozuo(y,x,s.top());
num.push(z);
s.pop();
}
s.pop();
}
if(a[i] == '+' || a[i] == '-' || a[i] == '*' || a[i] == '/'){
if(s.empty()) s.push(a[i]);
else{
int q1 = quan(a[i]);
int q2 = quan(s.top());
if (q1>q2) s.push(a[i]);
else{
do {
x=num.top();
num.pop();
y=num.top();
num.pop();
z=caozuo(y,x,s.top());
num.push(z);
s.pop();
if(s.empty()) break;
q2=quan(s.top());
} while (q1<=q2);
s.push(a[i]);
}
}
}
}
else{
long long op=0;
while(shuzi(a[i])){
op=op*10+(a[i]-'0');
i++;
}
num.push(op);
i--;
}
}
while(!s.empty()){
x=num.top();
num.pop();
y=num.top();
num.pop();
z=caozuo(y,x,s.top());
num.push(z);
s.pop();
}
}
int main(){
scanf("%s",a);
if(strlen(a)==0)
printf(" ");
start(a);
printf("%lld",num.top());
return 0;
}
}
四.最喜爱的序列 (100 分)
题目
小唐这段时间在研究序列。拿来N个整数的序列,他给序列中的每个整数都赋予一个喜爱值。喜爱值也是整数,有正有负,越大表明越喜欢。他想知道,如何从序列中连续取最多m个数,他获得喜爱值最大。1≤N≤500000,1≤m≤N。
输入格式:
第一行是两个整数N,m。分别代表序列中数的个数以及能取的最多个数。
第二行用空格隔开的N个整数,第i个整数Li代表他对第i个数的喜爱值。│Li│≤1000
输出格式:
一行,三个数,表示获得最大喜爱值,及第一个取最大喜爱值的区间。
输入样例:
在这里给出一组输入。例如:
5 2
1 4 5 2 3
输出样例:
在这里给出相应的输出。例如:
9 2 3
思路
使用单调队列解题
- 单调队列:
单调队列,顾名思义,是一种具有单调性的队列。众所周知,单调性有单调递增和单调递减两种,相应的单调队列也分为单调递增队列和单调递减队列两种。
单调递增队列:保证队列头元素一定是当前队列的最小值,用于维护区间的最小值。
单调递减队列:保证队列头元素一定是当前队列的最大值,用于维护区间的最大值。
对于本题:
队列有长度限制。当每进入一个数,都去掉没有希望的数。此时,新进的数的左边比它小的数都出队后,左边的数会比它大。这样,队列就会保持从大到小,满足单调性,这就是单调队列。
参考代码
#include<iostream>
#include<stack>
using namespace std;
int s[500005];
int main(){
deque<int>qu;
int ll,rr;
int n,m,i,j;
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m;
s[0]=0;
for(i=1;i<=n;i++){
cin>>s[i];
s[i]+=s[i-1];
}
int max1=0;
for(i=1;i<=n;i++){
while(!qu.empty()&&s[qu.front()]>s[i]){
qu.pop_front();
}
qu.push_front(i);
int temp=0;
while(!qu.empty()&&i-qu.back()>m){
qu.pop_back();
}
if (qu.back() == i) {
temp = s[i] - s[i - 1];
if (max1 < temp)
{
max1 = temp;
ll = i;
rr = i;
}
if(max1<s[i]-s[qu.back()]){
max1=s[i]-s[qu.back()];
rr=i;
ll=qu.back()+1;
}
}
cout<<max1<<" "<<ll<<" "<<rr;
}