2021-05-16

                                                 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;
}
 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值