我正在尝试解决https://www.hackerrank.com/challenges/password-cracker/problem问题 这是问题陈述
在CuteKittens.com网站上注册了N个用户。它们中的每一个都有一个唯一的密码,由pass [1],pass [2],...,pass [N]表示。由于这是一个非常可爱的网站,许多人想要访问那些可爱的小猫可爱的照片。但坚定的管理员不希望这个网站可供一般公众使用。所以只有拥有密码的人才能访问它。
Yu是一个很棒的黑客,他们在密码验证系统中发现了一个漏洞。密码验证系统也接受以任何顺序串联一个或多个密码的字符串。任何密码都可以在该字符串中出现0次或更多次。他可以访问每个N个密码,并且还有一个字符串loginAttempt,他必须告诉该字符串是否被网站的密码验证系统接受。例如,如果有3个用户使用密码{“abra”,“ka”,“dabra”},那么一些有效组合是“abra”(通过[1]),“kaabra”(通过[ 2] +传递[1]),“kadabraka”(传球[2] +传球[3] +传球[2]),“kadabraabra”(传球[2] +传球[3] +传球[1])等等上。
输入格式
第一行包含整数T,即测试用例的总数。然后是T测试案例。 每个测试用例的第一行包含N,即具有密码的用户数。第二行包含N个空格分隔的字符串,传递[1]传递[2] ...传递[N],表示每个用户的密码。第三行包含一个字符串loginAttempt,Yu必须判断它是否被接受
我尝试使用递归和memoization来解决它 这是我在python3中的解决方案
#!/bin/python3
import sys
def passwordCracker(passwords, attempt, memo):
if attempt in memo:
return memo[attempt]
for pwd in passwords:
if attempt.startswith(pwd):
if len(pwd) == len(attempt):
memo[attempt] = [pwd]
return [pwd]
prefix_size = len(pwd)
cracked_pwds = passwordCracker(passwords, attempt[prefix_size:], memo)
if len(cracked_pwds) != 0:
cracked_pwds.append(pwd)
memo[attempt] = list(cracked_pwds)
return cracked_pwds
memo[attempt] = []
return []
def print_passwords(passwords, attempt, memo):
#passwords.sort(key = len)
result = passwordCracker(passwords, attempt, memo)
result.reverse()
#print (memo)
if len(result) == 0:
print ("WRONG PASSWORD")
else:
res_str = result[0]
for w in result[1:]:
res_str = res_str + " " + w
print(res_str)
if __name__ == "__main__":
t = int(input().strip())
for a0 in range(t):
memo = {}
n = int(input().strip())
passwords = input().strip().split(' ')
attempt = input().strip()
print_passwords(passwords, attempt, memo)
但是,在运行时错误或超出时间限制的情况下,很多测试用例都失败了。 失败的一个特定测试用例是输入
1
10
a aa aaa aaaa aaaaa aaaaaa aaaaaaa aaaaaaaa aaaaaaaaa aaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab
我无法弄清楚如何改善这一点。 任何人都可以建议一个更好的替代解决方案。
答案 0 :(得分:2)
您的memo[]
缓存非常低效。
本练习所要做的只是在attempt
中插入空格以便将其分隔为有效密码,或者如果不可能则返回WRONG PASSWORD
。
因此,您需要跟踪的唯一信息是应添加这些空格的位置。
您可以在没有dict的情况下执行此操作,也无需创建任何匹配字符串的部分列表。
从简单的数字列表开始(例如memo = [-1] * len(attempt)
)。每当找到匹配项时,请存储下一个不匹配字符的索引。如果此索引恰好等于len(attempt)
,那么您显然可以停止搜索。如果特定位置不存在匹配项,也存储该结果。
获得成功匹配后,请逐步查看字符串偏移列表,找出需要插入空格的位置。
修改强>
我在HackerRank解决了这个问题。上面的方法基本上工作正常,但需要一些小的调整来通过所有的测试。特别是,您可能需要调用sys.setrecursionlimit
以允许更高级别的递归。
答案 1 :(得分:2)
我想在@ squeamish-ossifrage的回答中添加一些提示。您正在探索大量的可能性。假设attempt
的大小为n
,您有k
个密码。您可以将您的探索想象成一棵大树,其中每个节点都有k
个子节点,即序列中的下一个可能的密码。如果密码的最小长度为l
,则您的算法的时间复杂度大约为O(k^n/l)
,因为您将构建每个n/l
密码字符串,这就是您的解决方案以{结尾{ {1}}。
正如@ squeamish-ossifrage所写,你可以改进你的记忆技巧。但还有其他一些要考虑的问题。
首先,在您的示例中,Time limit exceeded
包含缺少每个密码的attempt
。更正式地说,如果b
的字符集是密码字符集的严格超集,那么您确定无法找到解决方案。如果attempt
是密码的最大长度,则时间复杂度为O(n + kL)。
其次,尝试修剪研究树(即减少每个节点的子节点数)。这该怎么做?只需尝试限制可接受的密码数量。
基本思想是,如果L
不包含密码,则应从探索中排除此密码。您可以在每个步骤重复此操作,因为attempt
可能不包含attempt[prefix_size:]
中包含的密码。时间复杂度:在最佳情况下使用Python搜索算法的O(kn / l),每一步。
更复杂的想法是删除由其他密码组成的密码。由于您有attempt
,因此您不需要a
,aa
等。如何?您只需将aaa
与passwordCracker
一起作为尝试,将其余密码应用为p
(我在此不再详述)。如果您发现一系列其他密码等于passwords
,则不需要p
,并且可以将其从p
列表中删除。它应该在开始时完成一次。
只使用这些技术和增加递归限制,保持passwords
缓存,才能解决这一挑战。
答案 2 :(得分:2)
这里有一些传递代码。我们不一定需要检查超集或使用聪明的预处理启发式方法。一个诀窍是,尽管可能存在过多的可能性,但它足以返回其中一个。这意味着,如果我们有一个解决方案直到索引i
,我们可以自由地忽略形成相同长度前缀loginAttempt
的所有其他可能性。这个Python代码使用两个列表,一个用于启动我们已访问过的索引,另一个用于保存所选择的密码序列,这些密码构成前缀,直到我们要检查的下一个起始索引。
def passwordCracker(passwords, loginAttempt):
memo = [[]] + [None for i in xrange(len(loginAttempt))]
stack = [0]
visited = set()
while stack:
i = stack.pop() # next index
if i in visited:
continue
visited.add(i)
for idx, p in enumerate(passwords):
if i + len(p) > len(loginAttempt):
continue
if loginAttempt[i:i + len(p)] == p:
memo[i + len(p)] = memo[i][:] + [idx]
stack.append(i + len(p))
if memo[len(memo) - 1]:
return " ".join(map(lambda i: passwords[i], memo[len(memo) - 1]))
else:
return "WRONG PASSWORD"