递归与记忆化算法出现运行时错误,求解Hackerrank密码破解问题
Hey there! I’ve worked through this exact problem before, so let’s break down why your recursive approach might be hitting runtime errors and how to fix it.
Common Causes of Runtime Errors
1. Stack Overflow from Deep Recursion
If your login attempt string is long (think hundreds of characters made up of short passwords), your recursive calls can stack up way too deep. Most languages have a default recursion depth limit (Python’s is around 1000), and exceeding that will throw a stack overflow error.
2. Inefficient Memoization Leading to Timeouts
If your memoization only tracks whether a position is solvable (a boolean) instead of storing the actual password path, you’ll end up re-computing paths over and over. This leads to exponential time complexity, which will time out for larger test cases (Hackerrank counts timeouts as runtime errors too).
3. Mishandled Edge Cases
Forgetting to handle edge cases like:
- No valid password combination (needing to return "WRONG PASSWORD" instead of crashing)
- Prefixes that match multiple passwords but lead to dead ends later on
- Going out of bounds when checking substrings
Optimized Recursive + Memoization Solution
Here’s a Python implementation that fixes these issues. I’ll walk through the key improvements:
def password_cracker(passwords, login_attempt): from functools import lru_cache # Speed up password lookups with a set password_set = set(passwords) # Get max password length to avoid checking unnecessary long prefixes max_pass_length = max(len(p) for p in passwords) if passwords else 0 @lru_cache(maxsize=None) def crack_from_position(pos): # Base case: we've successfully matched the entire string if pos == len(login_attempt): return [] # If we've gone beyond the string, this path is invalid if pos > len(login_attempt): return None # Try all possible password lengths up to our max length for length in range(1, max_pass_length + 1): end_pos = pos + length if end_pos > len(login_attempt): continue # Don't go out of bounds current_substring = login_attempt[pos:end_pos] if current_substring in password_set: # Recursively check the rest of the string remaining_result = crack_from_position(end_pos) if remaining_result is not None: # Build the full path by adding current password + remaining result return [current_substring] + remaining_result # No valid password found starting from this position return None # Start cracking from position 0 result = crack_from_position(0) return ' '.join(result) if result is not None else "WRONG PASSWORD"
Key Improvements Explained
- Set for Password Lookups: Checking if a substring is a valid password is O(1) instead of O(N) with a list, cutting down on lookup time.
- Max Password Length: We don’t waste time checking prefixes longer than the longest password in our list—this reduces unnecessary recursive calls drastically.
- Memoize the Path: Instead of just storing "can we solve from here?", we store the actual list of passwords that solve the substring. This avoids re-computing paths and lets us build the final answer directly.
- Clear Base Cases: We explicitly handle reaching the end of the string (success) or going past it (failure), preventing infinite loops or incorrect returns.
Additional Tips to Avoid Runtime Issues
- Test Edge Cases: Make sure to test scenarios where there’s no valid solution, where the login attempt is exactly one password, and where it’s a long chain of short passwords.
- Custom Memoization for Other Languages: If you’re using a language without built-in memoization (like Java), use a hash map to store results for each position instead of relying on function decorators.
- Iterative DP as a Fallback: If recursion depth is still a problem, switch to an iterative dynamic programming approach. You can build a DP array where
dp[pos]stores the password path from positionposto the end.
内容的提问来源于stack exchange,提问作者MaPY

