Search code examples
pythonalgorithmrecursionbacktrackingmemoization

hackerrank password cracker timeout due to recursion


This problem simply restated is this: given a bunch of strings and a target string, what all combinations from the given string can combine together to form target string with and without repetition.

e.g.

strings: we do what we must because we can

target: wedowhatwemustbecausewecan

output: we do what we must because we can

Approach I took is to remove every longer word from the target until target becomes empty. If targets becomes empty then just return the output. If longer words doesn't lead to a solution then try with shorter words and so on. I am also using memoization to make sure that if the target is already tried then just return, same as backtracking with memoization.

This apporach passed all the testcases except 2, where I am getting timeout.

def recursions(word_map, paswd, output, remember):
    flag = 0
    if len(paswd) == 0:
        return 1
    if paswd in remember:
        return flag
    for char in paswd:
        for word in (word_map[char] if char in word_map else []):
            if paswd.startswith(word):
                output.append(word + " ")
                if recursions(word_map, paswd[len(word):], output, remember):
                    return 1
                output.pop()
        remember[paswd] = 1
    return flag

Please help in providing a hint. Complete solution is here.


Solution

  • You could try dynamic programming approach where you mark the ending locations of each password. Start by trying every password at the beginning of the longer string. If it fits there mark down the ending position in the longer string. You could then repeat the same process for every location in longer string where previous location is marked as ending position.

    Hope this helps you getting started, I've intentionally left out some of the information required for full solution so let me know in comments if you're still stuck.

    EDIT Here's a short example on what I'm talking about. It doesn't allow you to reconstruct the solution but it shows how to do the matching without recursion:

    passwords = ['foo', 'bar']
    login = 'foobar'
    
    ends_here = [False] * len(login)
    
    for i in range(len(ends_here)):
    
        # Match password at the beginning or if password match
        # ended to previous index
        if i == 0 or ends_here[i - 1]:
            for pw in passwords:
                if login.find(pw, i, i + len(pw)) != -1:
                    ends_here[i + len(pw) - 1] = True
    
    print(ends_here)
    print('We can match whole login attempt:', ends_here[-1])
    

    Output:

    [False, False, True, False, False, True]
    We can match whole login attempt: True
    

    EDIT Took a closer look at the code provided in the question. The issue is on the line where matched strings are filtered by the characters contained in the target: for char in paswd:. Instead of doing filtering for every character in the target string the filtering should be done for every unique character: for char in set(paswd):. Fix that and solution runs much faster but would probably be even faster if there wouldn't be that kind of filtering at all since the maximum number of shorter strings is 10.