数据结构PTA第二次练习题

对于栈和队列,我们在做题时,并不需要从零到一地去实现它,C++的库里面其实已经帮我们写好了,我先简单讲一下怎么用

#include<stack> //栈的头文件

stack<int> sta; //生成了一个栈对象,存储的元素类型为int
stack<double> sta; //生成了一个栈对象,存储的元素类型为double
stack<char> sta; //生成了一个栈对象,存储的元素类型为char
...

sta.push(x); //入栈,元素x的数据类型要和生成栈对象时的数据类型保持一致
sta.pop(); //出栈,弹出栈顶元素
int x = sta.top(); //访问栈顶元素,调用这个函数会返回栈顶元素
int Size = sta.size(); //返回栈内元素的个数
bool flag = sta.empty(); //判断栈是否为空,为空返回true, 不为空返回false
...
#include<queue> //队列的头文件

queue<int> q; //生成了一个队列对象,存储的元素类型为int
queue<double> q; //生成了一个队列对象,存储的元素类型为double
queue<char> q; //生成了一个队列对象,存储的元素类型为char
...

q.push(x); //入队,元素x的数据类型要和生成队列对象时的数据类型保持一致
q.pop(); //出队,弹出队头元素
int x = q.front(); //访问队头元素,调用该函数会返回队头的元素
int x = q.back(); //访问队尾元素,调用该函数会返回队尾的元素
int Size = q.size(); //返回队列中元素的个数
bool flag = q.empty(); //判断队列是否为空,为空返回true,不为空返回false
...

第一题 回文数

回文数是一种很有趣的数,正反读起来都一样,比如123321或者123454321,单个的数字还有0,都是回文数。但是我们熟悉的回文数都是十进制下的,现在我们加大难度,对一个给定的数,想知道它在其他进制下是不是回文数。

输入格式:

在一行中给出2的整数N和R,其中N是不超过10的9次方的正整数,是需要判断的数,R是基,R=10表示十进制,R=2表示是二进制,R是不小于2,不超过10的9次方的正整数。

输出格式:

对每一组输入,如果N在R进制下式回文数,那么就就第一行输出“Yes”,否则,输出“No”,在第二行依次给出N在R进制下每一位的数字,数字中间间隔一个空格。

样例1:
123321 10
Yes
1 2 3 3 2 1
样例2:
123321 16
No
1 14 1 11 9
题解:

首先得知道如何将十进制数转换成其他进制,以123321转16进制为例,如下图,让123321除以16,得到整数部分7707,余数部分9,再用7707除以16,整数部分为481,余数部分为11,重复这个步骤,直到整数部分为0。在计算期间,把所有的余数记录下来,就是123321的16进制数(每个余数对应16进制数的一位)。

在这里插入图片描述

同理, 在转换其他进制的时候也是一样的步骤,N要转换成R进制就用N一直除以R,记录余数就行了。下面是代码的实现,ans数组是用来记录余数的,N是十进制数,R代表要转的进制,len代表ans数组中余数的个数。

int ans[100005], len=0;
int N, R;
while(N != 0) {
    ans[len] = N%R;
    len++;
    N = N/R;
}

PS:注意计算出来的R进制数在ans数组里是反过来的, ans[0]对应最低位,所以要输出的话要反过来输出。

回到题目,要判断转为R进制后的数是不是回文数,我们只需要遍历一遍ans数组,看头和尾的数字是不是相同的就行了,代码如下。

bool ok = true;
for(int i=0; i < len/2; i++) {
	if(ans[i] != ans[len-1-i]) { //发现对应位置的数字不对称,该数不是回文数
		ok = false;
		break;
	}
}
if(ok) cout << "Yes" << endl;
else cout << "No" << endl;

以上做法已经可以通过了,但因为这道题是一道栈的题目,所以下面也讲一下如何用栈来判断它是不是回文数。回文数的特点是左右对称,所以我们可以先将ans数组里的前一半数字都压入栈中,再用后一半的数字查看是否和栈顶相同,如果相同则弹出,如果不同则代表该数不是回文数。如果一时没听懂就看一下下面这个例子,如下图,对于数串123321,先将它的前一半压入栈内,现在栈内的元素从栈顶到栈底分别是3、2、1。然后将后一半3个数字一个个来比对,先是比对3,栈顶元素也为3,相等,则弹出栈顶元素,再比对2,栈顶元素也为2,弹出栈顶元素,再比对1,栈顶元素也为1,弹出栈顶元素。这时候已经比对完所有的数,没有发现不对称的数字,则该数就是回文数。

在这里插入图片描述

我再举一个不是回文数的例子, 比如这个数是45677634,那么我们初始化把前一半4567压如栈内,再用后一半一一比对,在比对7和6的时候还没有问题,到比对3时,发现栈顶元素为5,不相同,则可判断这个数不是回文数,如下图。

在这里插入图片描述

如果待检查的数有偶数位,就像上面这样前一半压入,后一半比对。如果是奇数位的话,如12321,则压入前一半12,比对后一半21,中间的那位数字是和自己比对的,一定相同,不需要去判断它,代码如下。

stack<int> sta; //申请一个栈
for(int i=0; i < len/2; i++) { //将前一半的数字压入栈
	sta.push(ans[i]);
}
bool ok = true;
int s;
if(len%2 == 0) s = len/2; //待检查的数有偶数位,判断后一半,从len/2开始
else s = len/2+1; //待检查的数有奇数位,无需判断中间那个数字,从len/2+1开始判断
for(int i=s; i < len; i++) { //一个个比对是否相同
	if(sta.top() != ans[i]) { //比对发现不相同,记录ok = false,并退出循环
        ok = false;
        break;
    }else { //比对相同,弹出栈顶元素
        sta.pop(); 
    }
}
if(ok) cout << "Yes" << endl;
else cout << "No" << endl;

该题的完整代码如下,两种判断方法,都可以使用,推荐用一下栈的方法,毕竟是栈的练习题嘛。

PS:本题还有个特殊情况是输入的N为0时,代码虽然可以输出Yes,但是因为ans数组里面没有东西,所以不会输出转换后的R进制数。

#include<iostream>
using namespace std;

int N, R, ans[100005], len=0;

int main() {
    cin >> N >> R;
    if(N == 0) { //特殊情况
        cout << "Yes" << endl;
        cout << "0";
        return 0;
    }
    while(N != 0) { //将N转换成R进制数
        ans[len] = N%R;
        len++;
        N = N/R;
    }
    bool ok = true;
    for(int i=0; i < len/2; i++) {
        if(ans[i] != ans[len-1-i]) { //发现对应位置的数字不对称,该数不是回文数
            ok = false;
            break;
        }
    }
    if(ok) cout << "Yes" << endl;
    else cout << "No" << endl;
    for(int i=len-1; i >= 0; i--) { //注意要反序输出
        cout << ans[i];
        if(i != 0) cout << ' '; //行末不能有多余空格
    }
    return 0;
}
#include<iostream>
#include<stack> //注意要写栈的头文件
using namespace std;

int N, R, ans[100005], len=0;
stack<int> sta; //申请一个栈

int main() {
    cin >> N >> R;
    if(N == 0) { //特殊情况
        cout << "Yes" << endl;
        cout << "0";
        return 0;
    }
    while(N != 0) { //将N转换成R进制数
        ans[len] = N%R;
        len++;
        N = N/R;
    }
    for(int i=0; i < len/2; i++) { //将前一半的数字压入栈
        sta.push(ans[i]);
    }
    bool ok = true;
    int s;
    if(len%2 == 0) s = len/2; //待检查的数有偶数位,判断后一半,从len/2开始
    else s = len/2+1; //待检查的数有奇数位,无需判断中间那个数字,从len/2+1开始判断
    for(int i=s; i < len; i++) { //一个个比对是否相同
        if(sta.top() != ans[i]) { //比对发现不相同,记录ok = false,并退出循环
            ok = false;
            break;
        }else { //比对相同,弹出栈顶元素
            sta.pop(); 
        }
    }
    if(ok) cout << "Yes" << endl;
    else cout << "No" << endl;
    for(int i=len-1; i >= 0; i--) { //注意要反序输出
        cout << ans[i];
        if(i != 0) cout << ' '; //行末不能有多余空格
    }
    return 0;
}

第二题 列车车厢重排

火车站的列车调度铁轨的结构如下所示: (Exit) 9 8 7 6 5 4 3 2 1 <==== ………………………………… <==== 8 4 2 5 3 9 1 6 7 (Entrance) 两端分别是一条入口(Entrance)轨道和一条出口(Exit)轨道,它们之间可能有N条平行的轨道。每趟列车从入口可以选择任意一条轨道进入排队,以方便最后有序从出口离开。在前例中有9趟列车,在入口处按照{8,4,2,5,3,9,1,6,7}的顺序排队等待进入。如果要求它们必须按序号递减的顺序从出口离开,则至少需要多少条平行铁轨用于调度?调度入队后,各个队列里车厢情况如何?

输入格式:

输入第一行给出一个整数N (2 ≤ N ≤99 ),下一行给出从1到N的整数序号的一个重排列。数字间以空格分隔。

输出格式:

第一行输出1号车厢所在的队列中的元素(车厢编号间以空格分隔),注意,调度时,车厢只进入队列等待,并不出队。 在第二行中输出可以将输入的列车按序号递减的顺序重排所需要最少的辅助铁轨(队列)条数。

样例:
9
8 4 2 5 3 9 1 6 7
8 4 2 1
4
题解:

可能有的同学对题意还不是很清楚,这里重新讲一遍。如下图,有很多车厢乱序排在一个列车库里面,一次出来一节车厢,例如第一辆8出来,如果直接塞到最右边,后面来的9就进不去了,所以要把8暂时停在调度轨道上,最后希望用最少的调度轨道,把所有车厢移到右边,并且要按降序的顺序依次排到右边。

在这里插入图片描述

这道题,每一条调度轨道符合先进先出,所以我们用队列来实现,题目给出N最大为99,所以我们可以申请100个队列来,因为最坏情况就是一条调度轨道上停一节车厢。

接下来就是调度的策略:规定一节车厢出来,我们就在已经存有元素的队列中找,找到第一个队列的尾部车厢号大于当前待插入车厢号的队列,将当前车厢插入该队列,若没有找到符合条件的队列,则申请一条新的队列,再把这节车厢号存进去。

以题目样例为例:(可以拿张纸跟着画一遍,更容易理解)

第一个8出来,当前没有队列里有元素,申请一条新的队列,8放进去;

第二个4出来,当前有元素的队列,只有一条,队尾元素为8,大于4,所以把4压入队列;

第三个2出来,当前有元素的队列有一条,队尾元素为4,大于2,把2压入该队列;

第四个5出来,当前有元素的队列有一条,队尾元素为2,小于5,没有找到可插入的队列,新申请一条队列存储5;

第五个3出来,当前有两条队列,从第一条开始判断,队尾元素为2,小于3,判断第二条,队尾元素为5,大于3,把3压入该队列;

第六个9出来,当前有两条队列,从第一条开始判断,队尾为2,小于9, 第二条队尾为3,小于9, 那么就新申请一条队列存储9;

第七个1出来,当前有三条队列,从第一条开始判断,队尾为2,大于1,那就把1压入该队列;

第八个6出来,当前有三条队列,从第一条到第三条,队尾元素分别为1、3、9,依次判断下来就被压入第三条队列里了;

第九个7出来,当前有三条队列,从第一条到第三条,队尾元素分别为1、3、6,都小于7,所以新申请一条队列压入7

所有车厢都出来了,现在的情形是,有四条队列,分别是

第一条队列:8 4 2 1

第二条队列:5 3

第三条队列:9 6

第四条队列:7

所以这道题的答案就是包含了车厢号为1的那节调度导轨和最少调度导轨4条

8 4 2 1
4

可能有人已经有疑问了,为什么出来的车厢,一定要从第一条队列开始找队尾比它大的,像我们在插入1号车厢的时候,一共有三条队列:842、53、9,说不定插到第二条队列或第三条队列上才是最少的正确答案呢,回答是不可能。因为我们为了让队列尽可能的少条,就需要在插入新的车厢号的时候,保持每条队列的队尾车厢号都尽可能的大。只有队尾车厢号都尽可能的大,后续插入新的车厢时,才最大限度的保证不需要去申请一条新的队列。而每条队列的队尾元素一定是升序的,用反证法来证明就是,如果将一个车厢号插入到队尾升序的队列中的其中一条里面,打破了队尾升序的局面,那么按照调度规则,该车厢号能被移动到当前队列,就说明前面的所有队列队尾都是小于它的,而当前队列队尾的元素都小于往后的所有队列的队尾元素,并且该车厢号小于当前队列队尾元素,所以所有队列的队尾仍然是升序的,这就与上述的队尾升序的局面被打破的说法互相矛盾。既然知道了队列的队尾元素一定是升序,那么我们只要从第一条队列开始找,找到第一个队尾元素大于当前车厢号的队列就行了,如果在队尾元素都是尽可能最大的情况下都没找到,那就一定得再申请一条新队列用于调度了,这就使得按这种调度规则求出的答案一定是最少。我已经很尽力解释为什么了,能不能理解就看造化了,要还不懂就来宿舍问我:)

下面是具体实现的代码:

#include<iostream>
#include<queue>
using namespace std;

int main() {
    queue<int> q[100]; //事先申请100个队列
    int ans=0; //记录当前已经用了几个队列
    int n;
    cin >> n;
    for(int i=0; i < n; i++) {
        int x;
        cin >> x; //当前出来的车厢号
        bool flag = false;
        for(int j=0; j < ans; j++) { //从第一条队列开始找
            if(q[j].back() > x) { //队尾元素大于当前车厢号,把当前车厢号插入该队列,并break退出查找
                q[j].push(x);
                flag = true;
                break;
            }
        }
        if(!flag) { //如果没有找到可插入的队列,就申请一条新的队列插入当前车厢号
            q[ans].push(x);
            ans++;
        }
    }
    for(int i=0; i < ans; i++) { //找到队尾元素为1的调度轨道
        if(q[i].back() == 1) {
            while(!q[i].empty()) { //输出该调度轨道上的车厢号
                cout << q[i].front();
                q[i].pop();
                if(!q[i].empty()) cout << ' '; //行末不能有多余空格
                else cout << endl;
            }
            break;
        }
    }
    cout << ans << endl; //输出最少需要的调度轨道数
    return 0;
}
  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值