数据结构与算法Python版——第四周作业

1有序队列(10分)
题目内容:

一开始给出了一个由小写字母组成的字符串 S。我们规定每次移动中,选择最左侧的字母,将其从原位置移除,并加到字符串的末尾。这样的移动可以执行任意多次

返回我们移动之后可以拥有的最小字符串(注:在Python3中,字符串的大小可用不等号比较)。

输入格式:
S。S为仅含有小写字母的字符串,长度不超过100000。
输出格式:
一个与S等长的字符串。
输入样例:
“cba”
输出样例:
acb

分析:题目规定 “每次移动中,选择最左侧的字母,将其从原位置移除,并加到字符串的末尾”,这样的操作可以让人联想到 队列的属性:FIFO。

解题思路:
1.先定义队列的类(注意根据题目要求定义:列表左侧为队首(出队),列表右侧为队尾(进队))
2.将目前的字符串设置为最小字符串
3.利用队列对字符串进行变形操作,将每次变形后的字符串,与当前最小字符串比较,如果比当前最小字符串小,则替换最小字符串。
4.利用count计数,总共迭代len(S)-1次,将所有元素都排进队尾一次,形成的字符串全部比较一次。

class Queue(list):
	def isEmpty(self):
        return self==[]
    def enqueue(self,item):
        self.append(item)
    def dequeue(self):
        return self.pop(0)
    def peek(self):
        return self(-1)
    def size(self):
        return len(self)
def func(S):
    q=Queue()
    count=0
    min_str=S   #将当前的字符串设置为最小字符串
    
    for c in S:  #字符串中的所有元素进队
        q.enqueue(c)
        
    while count< q.size()-1: #总共迭代变换len(S)-1次
        q.enqueue(q.dequeue()) #将队列最左侧的字符 从原位置移动到队列最右(末尾)
        new_str="".join(q)
        
        if new_str < min_str:  #与当前最小字符串比较,如果比当前最小字符串小,则替换之
            min_str = new_str
        
        count+=1   #迭代一次,计数器+1
    return  min_str
    
S = eval(input())
print(func(S))

上面这种做法用到了队列的类,但是测试用例4因为运行时间超出,没有通过。
我在CSDN里面查了别人的作业,人家用的字符串切片的方法,优化了运行时间,还是同样的思路,只是把字符串直接当成队列来操作。

def func(S):
    min_str=S
    for i in range(len(S)):
        S=S[1:]+S[0]
        if S < min_str:  #与当前最小字符串比较,如果比当前最小字符串小,则替换之
            min_str = S
            
    return min_str
     
S = eval(input())
print(func(S))

测试样例全部通过。

2最近的请求次数(10分)
题目内容:

计算每个事件发生之时,往前算10000毫秒内有多少个事件发生,包含当事件;也即对于列表中的每个元素k,算出整个列表中有多少个元素介于k-10000和k(两端均含)之间。

输入格式:

一个已排序列表mylist,所有元素为非负整数,记录各个请求的发生时间,单位为毫秒。

输出格式:

一个与mylist等长的列表。

输入样例:

[0,10,100,1000,10000,20000,100000]

输出样例:

[1,2,3,4,5,2,1]

解题方法:
解题方法1:一开始我是想到遍历列表的方法来做的,对列表中的每一个元素都进行以下操作:
把列表中的元素与当前元素一一比较,符合条件的话计数器+1,最后用列表的append方法,把计数器加进去。

A=[]  #创建计数列表
def func(mylist):
    for k in mylist:  #对列表中的每一个元素都操作一遍
        count=0
        for j in mylist: #把列表中的每一个元素都比一遍
            if k-10000<= j <= k:
                count+=1  #列表中出现一个满足条件的元素,计数器+1
        A.append(count)
                
    return A
     
mylist = eval(eval(input()))
print(func(mylist))

但我忽略了这是一个已排序列表,我的这种解法时间复杂度太大O(n**2),并且没用到任何队列的知识。

解题方法2:
分析:这是一个已排序列表,右边的值都比左边的大。那么在单次循环中,右边的值都不会比当前循环的元素小,不满足i<=k的条件,我们可以省去这部分操作,不去判断当前元素右侧的大值。

我们还需要判断,i>=k-10000的条件,如果列表中的第一个元素不满足,那我们把第一个元素去掉,接着继续判断第二个元素,依次类推,直到列表左侧的元素满足条件。这样,列表中剩下的元素的个数,就是对当前循环元素来说,符合条件的元素个数。

默认不用判断当前元素的右侧元素。

当前循环元素:0,列表是[0],满足条件0>=0-10000,结果为[1]。

当前循环元素:10,列表是[0,10],满足条件0>=10-10000,结果为[1,2]

当前循环元素:100,列表是[0,10,100],满足条件0>=100-10000,结果为[1,2,3]

当前循环元素:1000,列表是[0,10,100,1000],满足条件0>=1000-10000,结果为[1,2,3,4]

当前循环元素:10000,列表是[0,10,100,1000,10000],满足条件0>=10000-10000,结果为[1,2,3,4,5]

当前循环元素:20000,列表是[0,10,100,1000,10000,20000],0>=20000-10000——False,移除列表中的元素:0,当前列表[10,100,1000,10000,20000];
继续判断:10>=20000-10000——False,移除列表中的元素:10,
当前列表[100,1000,10000,20000];
继续判断:100>=20000-10000——False,移除列表中的元素:100,
当前列表[1000,10000,20000];
继续判断:1000>=20000-10000——False,移除列表中的元素:1000,
当前列表[10000,20000];
继续判断:10000>=20000-10000——True,不再移除,当前列表[10000,20000];
结果为[1,2,3,4,5,2]

当前循环元素:100000,列表是[0,10,100,1000,10000,20000,100000],0>=100000-10000——False,移除列表中的元素:0,当前列表[10,100,1000,10000,20000,100000];
继续判断:10>=100000-10000——False,移除列表中的元素:10,
当前列表[100,1000,10000,20000,100000];
继续判断:100>=100000-10000——False,移除列表中的元素:100,
当前列表[1000,10000,20000,100000];
继续判断:1000>=100000-10000——False,移除列表中的元素:1000,
当前列表[10000,20000,100000];
继续判断:10000>=100000-10000——False,移除列表中的元素:10000,
当前列表[20000,100000];
继续判断:20000>=100000-10000——False,移除列表中的元素:20000,
当前列表[100000];
继续判断:100000>=100000-10000——True,不再移除,当前列表[100000];
结果为[1,2,3,4,5,2,1]

我们可以从上述过程中发现,列表添加元素是在列表的右端,列表移除元素是在列表的左端。
这符合队列的规律(新数据项的添加总发生在一端(通常称为“尾 rear”端) 而现存数据项的移除总发生在另一端(通常称为 “首front”端) )

1.创建一个队列用来模拟过程,创建一个列表用来记录各个元素中满足条件的元素个数(i>=k-10000)

2.从左往右依次使列表的元素从队列的队首(右端)进入,判断队列的尾端(左端)元素是否满足i>=i-10000,如不满足,删除元素,继续判断左端元素,直到满足条件时停止,统计队列中的元素个数。

3.将统计的个数添加到记录列表中。

#作业2:最近的请求次数————优化运行时间
class Queue(list):
    def enqueue(self,item):
        self.append(item)
        
    def dequeue(self):
        return self.pop(0)
    
    def size(self):
        return len(self)
    
    def isEmpty(self):
        return self==[]
    
def func(mylist):
    q=Queue()  #创建一个队列类的实例对象
    ls=[]    #创建一个列表用来保存计数
    
    for k in mylist:
        q.enqueue(k)  #元素进队
        while q[0] < k-10000:  #当左侧元素不满足条件时,一直进行删除操作
            q.dequeue()
        ls.append(q.size())    #注意:新生成的列表要求与原列表等长,所以放在原列表的循环下
    return ls
     
mylist = eval(input())
print(func(mylist))

运行后,测试样例没有完全通过,发现,我少考虑了一种情况,就是在原列表中,当前循环元素的右侧元素可能与它相等,如果我们直接不判断它的相等右侧元素,那么统计次数就会比真实的少,所以我们不能忽略,那么我们更改一下列表当前循环元素入队的条件:

#作业2:最近的请求次数————优化运行时间
class Queue(list):
    def enqueue(self,item):
        self.append(item)
        
    def dequeue(self):
        return self.pop(0)
    
    def size(self):
        return len(self)
    
    def isEmpty(self):
        return self==[]
    
def func(mylist):
    q=Queue()  #创建一个队列类的实例对象
    ls=[]    #创建一个列表用来保存计数
    
    for i in range(len(mylist)):
        q.enqueue(mylist[i])  #元素进队
        
        count = 0  #引入计数器,对列表中与当前循环元素相同的元素个数进行统计
        
        for j in range(i+1,len(mylist)):
            if mylist[j] == mylist[i]:
                count+=1
        
        
        while q[0] < mylist[i]-10000:  #当左侧元素不满足条件时,一直进行删除操作
            q.dequeue()
        ls.append(q.size()+count)    #注意:新生成的列表要求与原列表等长,所以放在原列表的循环下
    return ls
     
mylist = eval(input())
print(func(mylist))

啊西,处理了元素重复问题,运行时间又超了…

搜了一下别人的解法,测试用例全部通过了。

人家没有通过定义队列的类来实现队列,可能减少了不少时间复杂度吧,下面是代码:

def func(mylist):
    output = []
    new_list = [] # 用列表来模拟队列
    for i in range(len(mylist)):
        new_list.append(mylist[i])
        while new_list[-1] - new_list[0] > 10000:
            new_list.pop(0)
        count = 0
        for j in range(i+1, len(mylist)):
            if mylist[j] == mylist[i]:
                count += 1
            else:
                break
        output.append(len(new_list)+count)
    return output


mylist = eval(input())
print(func(mylist))

原文链接:https://blog.csdn.net/Divine0/article/details/104971270

3基数排序(10分)
题目内容:

实现一个基数排序算法,用于10进制的正整数从小到大的排序。

思路是保持10个队列(队列0、队列1…队列9、队列main),开始,所有的数都在main队列,没有排序。

第一趟将所有的数根据其10进制个位(09),放入相应的队列09,全放好后,按照FIFO的顺序,将每个队列的数合并排到main队列。

第二趟再从main队列队首取数,根据其十位的数值,放入相应队列0~9,全放好后,仍然按照FIFO的顺序,将每个队列的数合并排到main队列。

第三趟放百位,再合并;第四趟放千位,再合并。

直到最多的位数放完,合并完,这样main队列里就是排好序的数列了。

输入格式:

一个列表mylist,其中mylist包含一些需要排序的正整数,正整数互不相同且均不超过100000,且个数在1至1000之间。

输出格式:
一个与mylist等长的列表。

输入样例:
[8, 91, 34, 22, 65, 30, 4, 55, 18]

输出样例:
[4, 8, 18, 22, 30, 34, 55, 65, 91]

问题分析:
有一主队列:[8, 91, 34, 22, 65, 30, 4, 55, 18] 要进行排序

第一轮
从左往右将它们从主队列依次出队,根据个位数的数值分配至编号0到9的子队列中:

0 :30

1:91

2:22

3:

4:34 , 4

5:65 ,55

6:

7:

8:8, 18

9:

接下来将这些子队列的元素依次出队,进入主队列:

[30,91,22,34,4,65,55,8,18]

可以看出这些数已经按个位数从大到小的顺序排好了

接着再进行一次分配,这次是根据十位数来分配:

0:4,8

1:18

2:22

3:30,34

4:

5:55

6:65

7:

8:

9:91

接下来将这些子队列的元素依次出队,进入主队列:
[4,8,18,22,30,34,55,65,91]

这时候已经排序完成。(在第二轮排序时已经不用管个位数的排序了(第一轮已经排好),所以目前的主队里,在各个数量级上都按从大到小排好了,我们再把各个数量级进行排序就行了)

在别的地方找到的示意图:
在这里插入图片描述

如果最高位是3位数或者更多,则继续进行上面直到高位数排完为止

有几位数就需要几轮的排序
1.先找出列表中的最大值
2.确定操作循环次数

n=1
while max(mylist)>10**n:
	n+=1  #n已经被修改
	for i in range(n):   #总共将以下操作进行n次(出主队,进主队)
	     # your code(出主队,进主队)

那么如何来确定当前提取的数值该进入哪一个子队列呢:

应该是根据该数值在不同数位的基数来进入子队列

        for j in range(len(mylist)):  #从主队出队次数:队列中原有的元素个数,但不能用队列表示,因为下面的操作改变了队列
            num=q_main.dequeue()
            radix=int((num/(10**i))%10) #求出num各个数位的基数(技巧),可以自己推一遍
            q[radix].enqueue(num)  #num根据基数进入相应的子队列

接下来遍历所有子队列,取出子队列中的数值,放进主队列中

        #出子队,进主队
        for i in range(10):
            while not q[i].isEmpty():  #如果当前子队列不空,就一直让元素从子队列出来,然后进入主队列
                q_main.enqueue(q[i].dequeue())

下面是完整代码:

#定义队列的类
class Queue(list):
    def enqueue(self,item):
        self.append(item)
        
    def dequeue(self):
        return self.pop(0)
    
    def size(self):
        return len(self)
    
    def isEmpty(self):
        return self==[]
    
    
def func(mylist):
    #创建11个队列:
    ''' #用列表推导式创建10个队列'''
    q=[Queue() for i in range(10)]  
    '''这个操作会生成一个列表,列表中有十个队列,q[0]~q[9]可以访问。
    在之前的课中,我们还用过列表推导式x={j:None for j in range(100)}
    l=[i for i in range(1000)]来创建字典,列表等方法'''
    
    q_main=Queue()  #创建主队列
    for num in mylist:  #列表中的元素进队
        q_main.enqueue(num)
        
    #计算列表中最大元素的位数——也是我们要进行操作的回合数
    n=1
    while max(mylist)>10**n:
        n+=1  #n已经被修改
        
    for i in range(n):   #总共将以下操作进行n次(出主队,进主队)
        #出主队,进子队
        for j in range(len(mylist)):  #出队len(mylist)次
            num=q_main.dequeue()
            radix=int((num/(10**i))%10) #求出num各个数位的基数(技巧),可以自己推一遍
            q[radix].enqueue(num)  #num根据基数进入相应的子队列
            
        #出子队,进主队
        for i in range(10):
            while not q[i].isEmpty():  #如果当前子队列不空,就一直让元素从子队列出来,然后进入主队列
                q_main.enqueue(q[i].dequeue())
                
    return q_main
 
mylist = eval(input())
print(func(mylist))

但测试样例3未通过,其他测试样例和时间复杂度正常,不知道为什么(可能以后还会回来再看?先就这样吧)

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值