算法竞赛入门经典第四章例题总结:

46 篇文章 0 订阅

计算组合数

def cnm(n,m):
    if(m < n-m): m = n-m
    a = 1;b = 1;ans = 0
    for i in range(m+1,n+1):
        a *= i
        b *= (i-m)
    print(a//b)
cnm(21,1)

这道题本身很简单,但是一个注意一点,利用m<n-m进行优化是有意思的小技巧.

猜测字谜游戏:输入单词和猜测,并输出结果。题目非常的直白,没有什么难度,唯一需要注意的是,我采用了set这个类型强行将guess里面重复的猜测去掉,并且用重复值的个数初始化错误出现的次数.

def hangman(ans,guess):
    is_win_lost_chk = 0
    length = len(guess)
    guess = ''.join(set(guess))
    mistake_occur = length - len(guess)
    right_occur = 0
    msg = ['You win','You chickened out','You lose']
    for each in guess:
        tmp_is_occur = False
        for ch in ans:
            if each==ch:right_occur+=1;tmp_is_occur = True
        if(not tmp_is_occur):mistake_occur+=1
        if(mistake_occur==7):break
    if(mistake_occur==7):is_win_lost_chk = 2
    elif(right_occur==len(ans)):is_win_lost_chk = 0
    else:is_win_lost_chk = 1
    print(msg[is_win_lost_chk])
hangman('cheese','abcdefgij')

1UVA133:这题有点烦…因为第一次指的时候,那个人是算一次的,但是指完一次以后,那个人由于被移走了,所以官员应该指向下一个人.


    def dole_queue(n,k,m):
    queue = [i+1 for i in range(0,n)]
    index_a = 0;index_b = n-1;
    left = n
    def go(index,d,t):#d代表顺逆,t代表走的步数
        nonlocal n
        while(True):
            if(queue[index]!=0):t-=1
            if(t==0):break;
            index = (index+d)%n
        return index

    while(left>0):  
        index_a = go(index_a,1,k)
        index_b = go(index_b,-1,m)
        if(index_a!=index_b):
            left-=2
            print(index_a+1,index_b+1)
        else:
            left-=1
            print(index_a+1)
        queue[index_a],queue[index_b] = 0,0
dole_queue(10,4,3)

下面采用队列进行人走之后移动元素,换一种解题思路:

def myqueue(n,k,m):
    queue = deque([i+1 for i in range(0,n)])
    a = 1;b = n; 
    def go():
        def getnext():#获取下一个位置
            _p1,_p2 = (p1+1)%n,(p2-1)%n
            while(_p1 == p2):_p1 = (_p1+1)%n
            while(_p2 == p1):_p2 = (_p2-1)%n
            return queue[_p1],queue[_p2]
        nonlocal n,a,b
        p1 = queue.index(a)
        p1 = (p1+k-1)%n#获取走k-1步之后的位置
        p2 = queue.index(b)
        p2 = (p2-(m-1))%n#获取走顺时针m-1
        t1,t2 = queue[p1],queue[p2]
        a,b = getnext()
        if(p1==p2):print(t1);queue.remove(t1)
        else:print(t1,t2);queue.remove(t1);queue.remove(t2)
        n = n - (1 if(p1==p2)else 2)
    while(n>1):
        go()
    print(queue[0])

下面是书本上的解法,有些地方非常巧妙:

def dole_queue(n,k,m):
    queue = [i for i in range(0,n+1)]
    p1 = n;p2 = 1;
    left = n
    def go(index,d,t):#d代表顺逆,t代表走的步数
        nonlocal n
        while(t>0):
            while(True):
                index = (index+d+n-1)%n+1
                if(queue[index]!=0):break
            t-=1
        return index

    while(left>0):
        p1 = go(p1,1,k)
        p2 = go(p2,-1,m)
        if(p1!=p2):
            left-=2
            print(p1,p2)
        else:
            left-=1
            print(p1)
        queue[p1],queue[p2] = 0,0
dole_queue(10,4,3)

首先. index = (index+d+n-1)%n + 1这句话就让人有些困惑,仔细研究了一下,对于d = 1,其实就是index = (index+1)%n + 1(范围变成1,n),但是这样写对于d = -1的情况是不一样的,注意,如果写成index = (index-1)%n+1,当index = 1,结果变成了0,这不符合我们的预期,它应该是n才对,用书上的方法它就可以从1变成n.

当然,我们直接用0-n-1进行映射,最后输出加1的方式好像也不错,但是这个式子还是很巧妙,值得思考一下.它避免了对index==0的情况作特殊处理.

其次,它的初始值是n,1,这里不得不说方法很巧妙,我们的直觉都是写成1,n。包括我的第一种方法,但是这样做会陷入一种情况,那就是第一次数数和后面数数的情况有点不一样,第一次指向的人是没有数过的,但是后面每一次开始数的时候,第一个指向的人总是已经被移走的了!!!所以我必须将index 的变化放在循环内部的最后面,保证检测过后再变化…稍微有点不自然.

Uva213:信息解码问题,用字典来存储相应的编码情况.然后在解码的时候直接查询.

def mydecode(code,message):
    def getnext(coding):
        if(coding==''):return '0'
        length = len(coding)
        x = int(coding,2)
        if(x == (2**(length) -2)):return '0'*(length+1)
        else: return bin(x+1)[2:] 
    decoding = {}
    coding = ''
    for ch in code:#存储编码
        coding = getnext(coding)
        #print(coding)
        decoding[coding] = ch
    pos = 0;n = len(message)
    while(pos<n):
        length = int(message[pos:pos+3],2)
        if(length == 0):break
        pos += 3
        #print(length)
        while(True):
            tmp = message[pos:pos+length]
            if(tmp==('1'*length)):break
            print(decoding[tmp],end = '')
            #print(tmp,decoding[tmp])
            pos += length
        pos += length
mydecode('$#**\\','0100000101101100011100101')

追踪电子表格:UVa512。这个题目有两种思路,当然最好的办法不是模拟操作,而是计算要查询的表格的位置.我根据书上的第二种办法写的代码:仔细思考一下.算法复杂度大约是O(QC),qc分别是命令的条数和查询的次数.如果查询次数不太多的话这种办法的效率是更高的.需要注意插入和删除的区别。删除的比较条件是大于和等于分开处理,而插入则是大于等于一起处理.

def tracing_sheet(command,R,C,q):
    def query(r,c):
        pos = [[[i,j] for j in range(C+1)] for i in range(R+1)]#位置矩阵
        def swap(r1,c1,r2,c2):
            pos[r1][c1],pos[r2][c2] = pos[r2][c2],pos[r1][c1]
        def del_or_insert(curr_cmd,i_list):
            if(curr_cmd == 'IR' ):x_y = 0;d = 1;
            if(curr_cmd == 'IC' ):x_y = 1;d = 1;
            if(curr_cmd == 'DR' ):x_y = 0;d = -1;
            if(curr_cmd == 'DC' ):x_y = 1;d = -1
            tot_diff = 0
            x = pos[r][c][x_y] + (d+1)//2
            for i in i_list:
                if(x>i):pos[r][c][x_y] +=d#注意使一次性删除的
                elif(curr_cmd[0] == 'D' and x==i):return False#代表查询失败
            return True
        for tmp_cmd in command:
            if(tmp_cmd[0] == '('):
                val = [int(i) for i in tmp_cmd if i.isdigit()]
                #print('swap command:',val )
                swap(val[0],val[1],val[2],val[3])
            else:#删除插入
                cmd = tmp_cmd[1:3]
                val = [int(i) for i in tmp_cmd if i.isdigit()][1:]
                #print('swap command:',val )
                if(not del_or_insert(cmd,val)):print('Deleted ');return False
        print('(%d,%d) => (%d,%d)'%(r,c,pos[r][c][0],pos[r][c][1]))
        return True
    for each_query in q:
        query(each_query[0],each_query[1])

最后一个例题:这个题目真的比较麻烦,有点像作业题….但是没什么思维上的难度.唯一需要注意的是最后一个需求,输出各种信息,这里有一个问题:到底是按照功课来进行循环还是学生来循环,从输出看似乎应该是选择功课,但是观察到最后的需求,要输出所有的人通过的情况,这样按照功课来循环就很不好办了——好像必须这样做:把每一门功课内的学生情况记录下来然后最后汇总比较,这样就多了一个O(n)的空间需求。

因此按照学生来进行循环就比较符合最后需求的比较了,至于前面的输出问题可以把相关数据存储起来,最后再输出.

另外关于排名的问题,这里我就没有采取对数据排序的方式了,而是直接遍历一遍获得排名,这里涉及到一个权衡的问题,如果我们保持数据存储是线性的,那么每次插入就是O(N),但是输出排名的时候就只需要O(N)的复杂度.如果不保持线性,那么每次查询的时候就考虑是对整个表排序(桶排序是O(N)的时间|空间复杂度,快排式O(NlogN),这里可以考虑桶排),这样每M次查询就是O(MN+N)或者O(MN+NlogN),也可以考虑不排序,这样每一次查询都遍历一遍表,复杂度是O(MN*N),具体怎么选择还要看操作的频率了,另外]还可以考虑把表的结构作成复合结构,每一个班级是一个节点,班级内部有序而班级间无序,这样也可以提高效率…总之值得思考的地方很多.不要局限于做出来题目.

import pdb
class Student:
    def __init__(self,args):
        self.sid,self.cid,self.name,self.chinese,self.math,self.english,self.programming = args
        self.tot_scores = int(self.chinese)+int(self.math)+int(self.english)+int(self.programming)
def Sheet():
    record = []
    SID = [0]*101
    def print_menu():
        print('Weclcome to student Performence\n')
        print('1 - Add')
        print('2 - Remove')
        print('3 - Query')
        print('4 - Show ranking')
        print('5 - Show statistic')
        print('0 - Exit')
    def print_std(std):
        def rank():
            ranks = 1
            for each in record:
                if(std.tot_scores < each.tot_scores ):ranks += 1
            return ranks
        average_scores = std.tot_scores/4
        print('*'*15)
        print(rank(),std.sid,std.cid,std.name,std.chinese,std.math,std.english,std.programming,
        std.tot_scores,'%.2f'%average_scores)
    def add():
        while(True):
            print('Please enter the SID,CID,name,and four scores. Enter o to finsih')
            curr_input = input()
            if(curr_input == '0'):break
            val = curr_input.split(' ')
            print('*'*10,val)
            if(SID[int(val[0])] != 0):print('Duplicate SID');
            else:record.append(Student(val));SID[int(val[0])] = 1;#添加
        print(dir(Student(val)))
    def DQ(isq_d):
        while(True):
            print('Please enter the SID,or name. Enter o to finsih')
            curr_input = input()
            if(curr_input == '0'):break
            val = curr_input.split(' ')[0]
            count = 0
            for each_std in record:
                if(each_std.sid == val or each_std.name == val):
                    if(isq_d == 'd'):count += 1;SID[int(each_std.sid)] = 0;each_std = 0;
                    elif(isq_d == 'q') :print_std(each_std)
            if(isq_d == 'd'):
                print('%d student(s) removed'%count)

    def stat():
        if(record == []):return
        print('please enter the class ID,0,for the whole statistics')
        try:
            ID = int(input())
        except:
            return
        conditions = ['chinese','math','english','programming']
        information = {'chinese':[0,0],'math':[0,0],'english':[0,0],'programming':[0,0],'overall':[0,0,0,0,0]}
        count = 0
        for std in record:
            passed_course = 0#用来统计通过的课程数目
            if(ID == 0 or ID == int(std.cid)):
                for each in conditions:
                    tmp_grade = int(getattr(std,each,None))#获取相应的属性
                    is_passed = 1 if(tmp_grade>=60) else 0
                    passed_course += is_passed
                    information[each][0] += tmp_grade
                    information[each][1] += is_passed
                information['overall'][passed_course] += 1
                count += 1
        for each in conditions:
            print(each)
            print('Average Score: %.2f'% (information[each][0]/count))
            print('Number of passed students: %d'%information[each][1])
            print('Number of failed students: %d'%(count-information[each][1]))
    #输出相关信息
        print('Overall\nNumber of students who passed all subjects: %d'%(information['overall'][4]))
        for i in range(3,0,-1):
            print('Number of students who passed %d or more subjects: %d'%(i,information['overall'][i]))
        print('Number of students who failed all subjects: %d'%(information['overall'][0]))
    while(True):
        print_menu()
        choice = input()
        if(choice=='0'):break
        elif(choice=='1'):add()
        elif(choice=='2'):DQ('d')
        elif(choice=='3'):DQ('q')
        elif(choice=='4'):print('Don\'t do that')
        elif(choice=='5'):stat()
Sheet()

上面的代码简单测试了一下没有问题,可能有隐藏的bug,但是具体的框架没问题

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值