JLU数据结构荣誉课———第一次实验
一.7-1 重复计数 (100 分)
在一个有限的正整数序列中,有些数会多次重复出现。请你统计每个数的出现次数,然后按数字在序列中第一次出现的位置顺序输出数及其次数。
作者 谷方明
单位 吉林大学
代码长度限制 16 KB
时间限制 1000 ms
内存限制 64 MB
输入格式:
第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
题目分析:
统计所给出序列中每种数字的出现次数并按照数字的出现顺序输出。
解题思路:
思路一:
暴力法:
开辟两个数组来分别储存输入序列和数字出现的次数,首先遍历输入序列,每次遇到未出现过的数字,向后遍历计数,并把相同的数字赋值为0,仅仅保留第一次出现的数字,在赋值的过程中进行计数,
并输出计数。此方法时间复杂度为O(n2),尝试后无法通过全部样例。
思路二:
用 STL中的map把第一次出现的元素和出现的次序联系起来,如果数字第一次出现,kind++,kind初值为0,就是记录他是第几种出现的不同元素,并为mp[i]赋值为kind,开一个结构体数组toys与kind对应,用toys[kind]记录kind对应元素的值和数量,如果数字不是第一次出现,就是mp[i]!=0,toys[kind].num++;更新数字出现的次数,最后从1到kind输出每个不同元素的个数。
代码:
#include <map>
#include <iostream>
using namespace std;
map<int,int>mp;
struct {
int num;
int sum;
}toy[500000];
int main(){
int n,k;
int kind=1;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>k;
if(mp[k]==0){
mp[k]=kind;
toy[kind].num=k;
toy[kind].sum++;
kind++;
}else{
toy[mp[k]].sum++;
}
}
for(int i=1;i<kind;i++)
cout<<toy[i].num<<" "<<toy[i].sum<<endl;
}
思路三:
先用集合set来储存当前已经输入的元素,如果set集合中不存在输入的元素,把该元素插入队列,目的为了建立一个输入顺序一致,但每种元素只有一个的队列queue,也向集合中插入元素,然后对num数组进行排序,从头弹出队列中的元素,利用upper_bounr和lower_bound计算出元素的个数.
lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
完整代码:
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #include<bits/stdc++.h> using namespace std; set<int> s; queue<int> q; int num[50005]; int main() { int n; cin >> n; for(int i=1;i<=n;++i) { scanf("%d", &num[i]); if (!s.count(num[i])) { q.push(num[i]); s.insert(num[i]); } } sort(num + 1, num + n + 1); while (!q.empty()) { int tmp = q.front(); printf("%d %d\n", tmp, upper_bound(num + 1, num + n + 1, tmp) - lower_bound(num+ 1, num+ n + 1, tmp)); q.pop(); } } |
7-2 报数游戏 (100 分)
n个人围成一圈,从1开始依次编号,做报数游戏。 现指定从第1个人开始报数,报数到第m个人时,该人出圈,然后从其下一个人重新开始报数,仍是报数到第m个人出圈,如此重复下去,直到所有人都出圈。总人数不足m时将循环报数。请输出所有人出圈的顺序。
输入格式:
一行,两个整数n和m。n表示游戏的人数,m表示报数出圈的数字,1≤n≤50000,1≤m≤100.
输出格式:
一行,n个用空格分隔的整数,表示所有人出圈的顺序
输入样例:
在这里给出一组输入。例如:
5 2
输出样例:
在这里给出相应的输出。例如:
2 4 1 5 3
思路一:用数组储存n个人各自的序号,从第一个人开始报数,如果数组元素不为0,count++,如果达到要求(count==m),输出数组元素,此人赋值为0,人的总数减一,直到人的总数为0才退出循环。
此方法的时间复杂度为O(mn),效率并不高。
完整代码:
#include <stdlib.h>
#include <stdio.h>
int main(){
int num[50000];
int n,m,count=0,k;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
num[i]=i;
k=n;
while(k!=0){
for(int i=1;i<=n;i++){
if(num[i]!=0)
count++;
if(count==m){
k--;
count=0;
if(k==n-1)
printf("%d",num[i]);
else
printf(" %d",num[i]);
num[i]=0;
}
}
}
return 0;
}
思路二:用循环链表来实现思路一的过程
完整代码:
typedef struct toys{
int num;
struct toys*next;
}PUR;
#include <stdio.h>
#include <malloc.h>
int main(){
int n,j,count=1;
PUR*head=NULL,*tail=NULL,*q,*q0,*p,*y;
scanf("%d%d",&n,&j);
for(int i=1;i<=n;i++){
PUR* p=(PUR*)malloc(sizeof(PUR));
p->num=i;
p->next=NULL;
if(head==NULL){
head=tail=p;
}else{
tail->next=p;
tail=p;
}
}
tail->next=head;
q0=tail;
q=head;
while(q->next!=q){
q0=q;
q=q->next;
count++;
if(count==j){
printf("%d ",q->num);
count=1;
q0->next=q->next;
y=q;
q=q->next;
free(y);
}
}
printf("%d",q->num);
}
思路三:
思路二的链表删除操作效率比较低,可用静态链表即跳舞链实现,每次删除只需要修改前驱和后继的元素下标,效率很高。
#include <iostream>
using namespace std;
int l[50001],r[50001];
int main()
{
int n,k;
cin>>n>>k;
int i,m=0;
for(i=1;i<=n;i++)
{
l[i]=i-1;
r[i]=i+1;
}
l[1]=n;
r[n]=1;
r[0]=1;
for(int j=1;j<=n;j++)
{
for(int x=0;x<k;x++)
{
m=r[m];
}
cout<<m;
if(j!=n) cout<<" ";
r[l[m]]=r[m];
l[r[m]]=l[m];
}
return 0;
}
7-3 算术表达式计算 (100 分)
任务: 计算算术表达式的值。
算术表达式按中缀给出,以=号结束,包括+,-,,/四种运算和(、)分隔符。运算数的范围是非负整数,没有正负符号,小于等于109 。
计算过程中,如果出现除数为0的情况,表达式的结果为”NaN” ; 如果中间结果超出32位有符号整型范围,仍按整型计算,不必特殊处理。 输入保证表达式正确。
输入格式:
一行,包括1个算术表达式。算术表达式的长度小于等于1000。
输出格式:
一行,算术表达式的值 。
输入样例:
在这里给出一组输入。例如:
(1+30)/3=
输出样例:
在这里给出相应的输出。例如:
10
解题思路:
(1) 首先构造一个运算符栈,运算符(以括号为分界点)在栈内遵循越往栈顶优先级不降低的原则进行排列。
(2)从左向右扫描算术表达式,从左边第一个字符开始判断:
a.如果当前字符是字母,则直接输出。
b.如果是运算符(不包括括号),则比较优先级。
①如果是空栈,直接入栈;
②如果当前运算符的优先级大于栈顶运算符的优先级,则将运算符直接入栈;
注:对于优先级相同的运算符来说,先出现的先运算,所以优先级相等的情况是要先将栈顶元素出栈再将当前元素入栈。
③否则将栈顶运算符出栈并输出,直到当前运算符的优先级大于栈顶运算符的优先级or栈顶是左括号时,再将当前运算符入栈。
注:括号比任何在括号前面入栈的元素优先级都大,比任何在左括号之后的,右括号之前的优先级都小。因为括号之中的运算符优先级最大
c.如果是括号,则根据括号的方向进行处理。
①如果是左括号,则直接入栈;
②否则,遇右括号前将所有的运算符全部出栈并输出,遇左括号后将左右的两括号一起删除。
(3) 重复上述操作(2)直至扫描结束,将栈内剩余运算符全部出栈并输出。
细节:对于运算符优先级的比较,我把运算符建造成一个对象,通过重载>运算符函数,来实现优先级的比较。
class calculation{
public:
calculation(char ch){
cal=ch;
}
calculation(){
cal='#';
}
int operator>(char ch){
switch(cal){
case '+':if(ch=='-'||ch=='+')return 0;if(ch=='*'||ch=='/'||ch=='(') return -1;break;
case '-':if(ch=='+'||ch=='-')return 0;if(ch=='*'||ch=='/'||ch=='(') return -1;break;
case'*':if(ch=='/'||ch=='*')return 0;if(ch=='+'||ch=='-') return 1;break;if(ch=='(')return -1;break;
case '/':if(ch=='*'||ch=='/')return 0;if(ch=='+'||ch=='-') return 1;break;if(ch=='(')return -1;break;
case')':return 2;break;
}
}
private:
char cal;
};
完整代码:
#include <iostream>
#include <stack>
#include <string.h>
#include <math.h>
using namespace std;
class calculation{
public:
calculation(char ch){
cal=ch;
}
calculation(){
cal='#';
}
int operator>(char ch){
switch(cal){
case '+':if(ch=='-'||ch=='+')return 0;if(ch=='*'||ch=='/'||ch=='(') return -1;break;
case '-':if(ch=='+'||ch=='-')return 0;if(ch=='*'||ch=='/'||ch=='(') return -1;break;
case'*':if(ch=='/'||ch=='*')return 0;if(ch=='+'||ch=='-') return 1;break;if(ch=='(')return -1;break;
case '/':if(ch=='*'||ch=='/')return 0;if(ch=='+'||ch=='-') return 1;break;if(ch=='(')return -1;break;
case')':return 2;break;
}
}
private:
char cal;
};
char input[1000];
int trans(char ,int ,int);
int toys(int ,int);
int main(){
stack<int>s1;//操作数;
stack<char>s2;//运算符;
calculation a;
char m,ch,s='#';
int x,y,n,k,j;
scanf("%s",input);
n=strlen(input);
for(int i=0;i<n-1;i++){
ch=input[i];
if(ch>='0'&&ch<='9'){
j=i;
while(input[j]>='0'&&input[j]<='9')
j++;
k=toys(i,j-1);
i=j-1;
s1.push(k);
}else{
a=calculation(ch);
if(s2.empty()||s2.top()=='('){
s2.push(ch);
}else{
if(ch=='('||a>s2.top()==1){
s2.push(ch);}
else{
if(ch==')'){
while((m=s2.top())!='('){
m=s2.top(); s2.pop();y=s1.top();s1.pop();x=s1.top();s1.pop();
if(m=='/'&&y==0){
cout<<"NaN";return 0;
}else{
s1.push(trans(m,x,y));
}
}
s2.pop();
}else{
while(!s2.empty()&&(a>s2.top()==0||a>s2.top()==-1)&&s2.top()!='('){
m=s2.top();s2.pop();y=s1.top();s1.pop();x=s1.top();s1.pop();
if(m=='/'&&y==0){
cout<<"NaN";return 0;
}else{
s1.push(trans(m,x,y));
}
}s2.push(ch);
}
}
}
}
}
while(!s2.empty()){
m=s2.top();s2.pop();y=s1.top();s1.pop();x=s1.top();s1.pop();
if(m=='/'&&y==0){
cout<<"NaN";return 0;
}else{
s1.push(trans(m,x,y));}
}
cout<<s1.top();
}
int trans(char m,int x,int y){
switch(m){
case '+':return (x+y);break;
case '-':return (x-y);break;
case '*':return (x*y);break;
case '/':return (x/y);break;
}
}
int toys(int x,int y){
int num=0;
for(int i=x;i<=y;i++){
num+=(input[i]-'0')*pow(10,y-i);
}
return num;
}
7-4 最喜爱的序列 (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
解题思路:
将数组转化为前n项和的形式,用于维持单调队列时区间和的计算。
利用单调队列,保持一个单调递增的队列,当前的操作数如果小于队尾元素,弹出队尾元素直到队尾元素不大于当前元素,当前操作数入队尾、当区间长度不满足要求,弹出队头元素。
假设当前区间右下标为x,区间和为sun(x)-sum(左端下标-1),所以只需要求出sum(左端下标-1)的最小值即可,应用单调队列可以实现。
完整代码:
#include <iostream>
#include <deque>
using namespace std;
long long sum[500000];
deque<int> q;
int head, tail;
int main(void)
{
int n, m, i, j;
scanf("%d%d", &n, &m);
for (i = 1; i <= n; i++)
{
scanf("%lld", &sum[i]);
sum[i] += sum[i - 1];
}
q.push_front(0);
long long max=0;
for (i = 1; i <= n; i++)
{
while (!q.empty() && sum[q.front()] > sum[i])
{
q.pop_front();
}
q.push_front(i);
while (!q.empty() && i - q.back() > m)
{
q.pop_back();
}
if(max< sum[i] - sum[q.back()])
{
max = sum[i] - sum[q.back()];
head = q.back()+1;
tail = i;
}
}
printf("%lld %d %d\n", max, head, tail);
return 0;
}