基于前一个问题的朴素解决方案
commonPrefix 应该是(根据评论)数组中最长的前缀,直到索引 n。那是什么意思?您需要计算所有前缀并选择最长的。
static String commonPrefix(String arr[], int n) {
String longestPrefix = "";
for (int i = 0; i < n; i++) {
final String currentPrefix = commonPrefixUtil(arr[i], arr[n]);
if (currentPrefix.length() > longestPrefix.length()) {
longestPrefix = currentPrefix;
}
}
return longestPrefix;
}
因此,"00" 将产生 arr = ["000", "1110", "01", "001", "110", "11"]; n = 3。
现在我们有了最长的前缀,什么?我们需要找到以该前缀开头的最接近n 的索引...
static int closestIndex(String[] arr, String longestPrefix, int n) {
for (int i = n - 1; i >= 0; i--) {
if (arr[i].startsWith(longestPrefix)) {
return i + 1; // + 1 because the solution wants starting index with 1
}
}
return 0;
}
如何组合?只需为每个输入调用这两个方法
public static void main(String[] args) {
String[] words = { "000", "1110", "01", "001", "110", "11" };
int[] output = new int[words.length];
for (int i = 0; i < words.length; i++) {
final String longestPrefix = commonPrefix(words, i);
output[i] = closestIndex(words, longestPrefix, i);
}
System.out.println(Arrays.toString(output));
}
您已从问题中删除了您的 commonPrefixUtil 实现,因此我添加了自己的:
static String commonPrefixUtil(String str1, String str2) {
int shorterStringLength = Math.min(str1.length(), str2.length());
int length = 0;
for (; length < shorterStringLength; length++) {
if (str1.charAt(length) != str2.charAt(length)) {
break;
}
}
return str1.substring(0, length);
}
优化解决方案
我使用带有制表的动态编程创建了一个新的解决方案(如果我理解正确的话),即我使用了一个已经包含所有前缀的哈希图,这些前缀指向前缀来自的单词的索引。 Map 的值是一个排序树,因此可以很容易地确定哪个具有公共前缀的单词最接近当前索引。 HashMap 保证恒定时间操作,TreeSet 保证 log(n) 时间成本。
更简单的解释,我处理所有单词的第一个字母,然后是下一个等等。在这个过程中,我记住所有前缀子字符串的位置,同时它们会自动排序。我在处理完最长单词的最后一个字母后停止循环。
public static void main(String[] args) {
String[] words = { "000", "1110", "01", "001", "110", "11" };
int[] result = new int[words.length];
final int firstWordLength = words.length > 0 ? words[0].length() : 8;
// prefix -> [indexes of prefix occurrence]
HashMap<String, TreeSet<Integer>> prefixes = new HashMap<>(words.length * (firstWordLength + 1) * 2);
int wordLength = 1;
boolean isUpdatedResult;
do { // O(k)
isUpdatedResult = false;
for (int wordIdx = 0; wordIdx < words.length; wordIdx++) { // O(n)
if (words[wordIdx].length() < wordLength) {
continue;
}
final String currentPrefix = words[wordIdx].substring(0, wordLength); // Java >= 7 update 6 ? O(k) : O(1)
final TreeSet<Integer> prefixIndexes = prefixes.get(currentPrefix); // O(1)
if (prefixIndexes != null) {
// floor instead of lower, because we have put `wordIdx + 1` inside
final Integer closestPrefixIndex = prefixIndexes.floor(wordIdx); // O(log n)
if (closestPrefixIndex != null) {
result[wordIdx] = closestPrefixIndex;
isUpdatedResult = true;
}
}
// take the previous index for the result if no match
if (result[wordIdx] == 0) {
result[wordIdx] = wordIdx;
}
final TreeSet<Integer> newPrefixIndexes = prefixes.computeIfAbsent(currentPrefix, p -> new TreeSet<>()); // O(1)
// the result index must be + 1
newPrefixIndexes.add(wordIdx + 1); // O(log n)
}
wordLength++;
} while (isUpdatedResult);
System.out.println(Arrays.toString(result));
}
我已经用大 O 时间复杂度标记了所有操作。 n 是输入数组中的单词数,即words.length 和k 是最长单词的长度。根据Jon Skeet's post,Java 7 update 6 中子字符串的时间复杂度已更改为线性。
所以我们可以计算:
O(k) * O(n) * (O(log n) + O(k))
希望代码是可以理解的,并且我正确计算了复杂度。