📜  门| GATE-CS-2003 |问题 6(1)

📅  最后修改于: 2023-12-03 15:12:40.623000             🧑  作者: Mango

门 | GATE-CS-2003 | 问题 6

这是 GATE-CS-2003 真题中的第 6 题,考察的是程序员对于递归算法的掌握和分析能力。

题目描述

下面是问题的故事背景:

假设有一扇钢铁大门,由一些特殊的锁组成。每个锁都是由一个数字串表示的。为了打开大门,所有的锁必须打开。

每个数字串可以有下面两种操作之一:

  • 压缩操作:如果数字串长度不为 1,可以将它分为两个数字串,并将分出来的两个数字串进行某种操作(递归调用对应的函数),最后将结果合并为一个数字串。操作可以是“相加”或“相乘”。
  • 解锁操作:如果数字串长度为 1,则表示这个锁已经打开了。

假设有一个大门,由下面的锁组成:

9763 说明:这是一个锁,需要输入数字 9、7、6、3 才能打开。 194 说明:这是一个锁,需要输入数字 1、9、4 才能打开。 7881 说明:这是一个锁,需要输入数字 7、8、8、1 才能打开。 34 说明:这是一个锁,需要输入数字 3、4 才能打开。 57 说明:这是一个锁,需要输入数字 5、7 才能打开。 63 说明:这是一个锁,需要输入数字 6、3 才能打开。

现在假设你所拥有的信息只有这个大门需要打开,你需要写一个程序来打开这扇门。

我们将数字串操作分别表示为“+”和“”两种。例如,1234 表示“1234”。

请编写一个函数:

def unlock_door(door, steps):
    pass

其中:

  • door 是一个包含数字串的列表,每个数字串对应大门上的一个锁,例如 [ '9763', '194', '7881', '34', '57', '63' ]
  • steps 是可以尝试的最大步数,如果尝试步数超过了 steps,函数应该返回一个空列表。

函数应该返回一个包含可以打开大门的数字串列表,如果无法打开大门,返回空列表。

例如:

assert unlock_door( [ '9763', '194', '7881', '34', '57', '63' ], 3 ) == ['9763']
assert unlock_door( [ '1234', '234', '34' ], 3 ) == ['34']
assert unlock_door( [ '12', '23', '34' ], 3 ) == []
题目分析

这道题目的核心是递归。对于每个数字串,它可以进行两种操作:

  • 压缩:将数字串分为两部分,分别进行递归调用,然后将结果合并。
  • 解锁:如果数字串长度为 1,表示这个锁已经打开了。

对于压缩操作,我们需要考虑两个递归结果的合并方式。如果我们在递归调用时返回了两个结果,我们应该如何将它们合并?

我们首先可以考虑使用“加”和“乘”两种运算。例如,对于一个数字串 1234,我们可以将其分为两部分:1234。如果我们的运算是“加”,我们会将递归调用的结果 1+23+4 做加法运算,得到结果 6,表示整个数字串的值为 6。如果我们的运算是“乘”,我们会将递归调用的结果 1*23*4 做乘法运算,得到结果 24,表示整个数字串的值为 24

其次,我们需要考虑每个数字串只能使用一次的限制。为了避免在递归调用中重复使用数字,我们可以通过将已经被使用的数字串替换为一个特殊的字符来避免重复使用。这样做的好处是,我们可以将一个数字串替换为特殊字符后,传递给下一层递归调用,这样下一层递归调用就不会再次使用这个数字串。

这个特殊字符可以是通常不能用于数字串中的任何字符,例如 @ 或者 % 等。

综合考虑,我们可以设计一个递归函数 find_ship,它的输入参数为:

  • door:需要打开的门,即一个数字串列表;
  • used:已经使用的数字串(或者特殊字符)列表;
  • max_steps:最大递归次数;
  • current_steps:当前已经递归的次数。

递归函数需要返回一个包含所有可以打开门的数字串列表。递归函数的执行流程如下:

  • 如果当前已经递归了最大次数 max_steps,返回空列表。
  • 对于每个数字串 cs,如果它已经被使用过,直接跳过。
  • 如果数字串 cs 长度为 1,表示这个数字串已经是一个解锁的锁,将其加入结果列表中。
  • 对于数字串 cs,我们可以将它分为两部分,并将其传递给一个新的递归函数 find_ship。调用 find_ship 函数返回两个结果列表,表示数字串的两部分可以分别打开门。
  • 对于两个结果列表,对它们中的每个数字串都执行上述判断。
参考代码

下面是 Python 代码实现:

def find_ship( door, used, max_steps, current_steps ):

    if current_steps >= max_steps:
        return []

    results = []

    for i, cs in enumerate(door):

        if cs in used:
            continue

        used[i] = '@'
        
        if len(cs) == 1:
            results.append(cs)
            
        else:
            for j in range(1, len(cs)):
                cs1 = cs[:j]
                cs2 = cs[j:]
                
                r1 = find_ship(door, used[:], max_steps, current_steps+1)
                r2 = find_ship(door, used[:], max_steps, current_steps+1)
            
                for c1 in r1:
                    for c2 in r2:
                        results.append(c1+c2)

        used[i] = cs

    return results

def unlock_door(door, steps):
    results = []
    for i in range(1, steps+1):
        results += find_ship(door, ['@']*len(door), i, 0)

    return list(set(results))

对于 unlock_door([ '9763', '194', '7881', '34', '57', '63' ], 3),我们可以得到正确结果 ['9763']。对于 unlock_door([ '1234', '234', '34' ], 3),我们可以得到正确结果 ['34']。对于 unlock_door([ '12', '23', '34' ], 3),我们可以得到正确结果 []

注意在 Python 代码中,used[:] 表示将 used 序列拷贝一份,避免递归函数之间的干扰。list(set(results)) 表示去除重复元素。