LWC 64: 753. Cracking the Safe
Problem:
There is a box protected by a password. The password is n digits, where each letter can be one of the first k digits 0, 1, …, k-1.
You can keep inputting the password, the password will automatically be matched against the last n digits entered.
For example, assuming the password is “345”, I can open it when I type “012345”, but I enter a total of 6 digits.
Please return any string of minimum length that is guaranteed to open the box after the entire string is inputted.
Example 1:
Input: n = 1, k = 2
Output: “01”
Note: “10” will be accepted too.
Example 2:
Input: n = 2, k = 2
Output: “00110”
Note: “01100”, “10011”, “11001” will be accepted too.
Note:
- n will be in the range [1, 4].
- k will be in the range [1, 10].
- k^n will be at most 4096.
思路:
此题是贪心,比如n = 4, k = 3的情况下,初始构造了”0000”,为了让密码序列最小,我们最大化利用”0000”中的后三位,这样一来就有三种情况:”00000”和”00001”和”00002”,其中第一种是重复的,所以直接舍去。接着继续看最后三位并构造。典型的子问题,用dfs解决。
这里讲些细节问题,先来看初始版本:
初始Java版本:
public String crackSafe(int n, int k) {
int size = (int) Math.pow(k, n);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; ++i) {
sb.append("0");
}
String init = sb.toString();
Set<String> vis = new HashSet<>();
vis.add(init);
ans = init;
valid = false;
int len = n - 1;
dfs(init, vis, k, size, len);
return ans;
}
String ans;
boolean valid = false;
void dfs(String init, Set<String> vis, int k, int size, int len) {
if (vis.size() == size) {
ans = init;
valid = true;
}
else {
if (valid) return;
int n = init.length();
for (int j = 0; j < k; ++j) {
String ns = init.substring(n - len, n) + j;
if (!vis.contains(ns)) {
String nxt = new String(init + j);
vis.add(ns);
dfs(nxt, vis, k, size, len);
vis.remove(ns);
}
}
}
}
stackoverflow了,堆栈溢出,后来找了下原因,是因为这句话:
String nxt = new String(init + j);
为了让递归返回的时候状态还原,我使用了clone的方法,但这条语句占了大量的内存,即使在指定递归层数内,但也容易stackoverflow。所以做下优化:
String ans;
boolean dfs(String init, Set<String> vis, int k, int size, int len) {
if (vis.size() == size) {
ans = init;
return true;
}
else {
int n = init.length();
for (int j = 0; j < k; ++j) {
String ns = init.substring(n - len, n) + j;
if (!vis.contains(ns)) {
vis.add(ns);
init = init + j;
if (dfs(init, vis, k, size, len)) return true;
vis.remove(ns);
init = init.substring(0, init.length() - 1);
}
}
}
return false;
}
这样就能AC了,但需要注意状态还原。不过你可以直接在参数中传值,这样不会影响init!
代码如下:
String ans;
boolean dfs(String init, Set<String> vis, int k, int size, int len) {
if (vis.size() == size) {
ans = init;
return true;
}
else {
int n = init.length();
for (int j = 0; j < k; ++j) {
String ns = init.substring(n - len, n) + j;
if (!vis.contains(ns)) {
vis.add(ns);
if (dfs(init + j, vis, k, size, len)) return true;
vis.remove(ns);
}
}
}
return false;
}
Python版本:
def crackSafe(self, n, k):
"""
:type n: int
:type k: int
:rtype: str
"""
goal = k ** n
init = "0" * n
self.ans = init
vis = set()
vis.add(init)
def dfs(init, vis, n, k, goal):
if goal == len(vis):
self.ans = init
return True
else:
nlen = len(init)
for i in range(k):
ns = init[nlen - n + 1 : nlen] + str(i)
if ns not in vis:
vis.add(ns)
if (dfs(init[:] + str(i), vis, n, k, goal)): return True
vis.remove(ns)
return False
dfs(init, vis, n, k, goal)
return self.ans