最长的子串,超过java的时间限制

时间:2015-04-15 20:07:24

标签: java string algorithm

给定一个字符串,找到最长子字符串的长度而不重复字符。例如,没有重复“abcabcbb”字母的最长子字符串是“abc”,长度为3.对于“bbbbb”,最长的子字符串是“b”,长度为1.

public static int lengthOfLongestSubstring(String s) {
    if (s.length()==0)
        return 0;
    int maxlen = 1;

    HashMap<Character, ArrayList<Integer>> check = new HashMap<Character,ArrayList<Integer>>();
    for (int i = 0; i < s.length(); i++) {
        for (int j = i; j < s.length(); j++) {
            if (!check.containsKey(s.charAt(j))) {
                ArrayList<Integer> value= new ArrayList<>();
                value.add(j);
                check.put(s.charAt(j), value);
            }
            else {
                maxlen = Math.max(j - i, maxlen);
                ArrayList<Integer> temp = check.get(s.charAt(j));
                i=temp.get(temp.size()-1);  
              // get the last index(biggest index) of the key value
                check.clear();
                break;
            }
            if(j==s.length()-1) {
                maxlen = Math.max(j - i + 1, maxlen);
            }

        }
    }
    return maxlen;
  }
}

对于长可重复字符串的最后一次测试,超出了时间限制。不知道如何优化。寻求改进,谢谢

4 个答案:

答案 0 :(得分:3)

这是一个相当简单的解决方案,应该更快地解决您的问题:

public static int longestNonRepeating(final String s) {
    final Set<Character> unique = new HashSet<>();
    int max = 0;
    for (int i = 0; i < s.length(); ++i) {
        final char c = s.charAt(i);
        if (!unique.add(c)) {
            for (int j = i - unique.size(); j < i; ++j) {
                if (s.charAt(j) != c) {
                    unique.remove(s.charAt(j));
                } else {
                    break;
                }
            }
        }
        max = Math.max(max, unique.size());
    }
    return max;
}

这是如何运作的?

我们沿String走,并将字符添加到Set。如果我们添加的字符已经包含在Set中,那么我们知道当前子字符串中有一个副本。

在这种情况下,从当前子字符串的开头(必须与unique的大小相同)开始,我们一起走。如果我们发现一个不是我们发现的重复的字符,副本必须更进一步,我们继续搜索。一旦我们找到副本,我们就可以停止搜索。

要对过程进行可视化:

a  b  c  a  b  c
0  1  2  3  4  5
^
|
i

我们在a唯一的Set。{/ p>

a  b  c  a  b  c
0  1  2  3  4  5
   ^
   |
   i

我们在a,b唯一的Set。{/ p>

a  b  c  a  b  c
0  1  2  3  4  5
      ^
      |
      i

我们在a,b,c唯一的Set。{/ p>

a  b  c  a  b  c
0  1  2  3  4  5
^        ^
|        |
j        i

我们尝试将add a添加到唯一的Set,这是重复的。从唯一子字符串的开头,尝试找到a。幸运的是,这是0,我们不需要从唯一中删除任何内容。

a  b  c  a  b  c
0  1  2  3  4  5
   ^        ^
   |        |
   j        i

我们尝试将add b添加到唯一的Set,这是重复的。从唯一子字符串的开头,尝试找到b。幸运的是,这是1,我们不需要从唯一中删除任何内容。

a  b  c  a  b  c
0  1  2  3  4  5
      ^        ^
      |        |
      j        i

我们尝试将add c添加到唯一的Set,这是重复的。从唯一子字符串的开头,尝试找到c。幸运的是,这是1,我们不需要从唯一中删除任何内容。

我们已经完成了。最长的唯一子字符串是3

答案 1 :(得分:2)

以下是时间复杂度为O(n)的解决方案。由于两个原因,它可能比其他解决方案更快。

  1. 它仅比较链断开时或当达到String结束时不同连续字符链的长度与当前最大值。

  2. 通过跟踪每个字符的最后一个索引而不是 子串中的字符集,从来没有任何理由 删除任何元素。这当然意味着如果String有许多不同的字符,它会占用大量内存。

    public static int subStringLength(final String s) {
        final Map<Character, Integer> indices = new HashMap<>();
        int max = 0;
        int start = 0;
        final int length = s.length();
        for (int i = 0; i < length; i++) {
            Integer k = indices.put(s.charAt(i), i);
            if (k != null && k >= start) {
                max = Math.max(max, i - start);
                start = k + 1;
            }
        }
        return Math.max(max, length - start);
    }
    

答案 2 :(得分:1)

这个问题可以在O(N)中解决,其中N是字符串的长度。

算法:

1)迭代字符串的字符。跟踪每个角色的最后一次出现。在每个信件商店,前一个相同的字符有多远。 E. g。:有字符串“abccdae”,我们会得到列表[1,2,3,1,5,5,7]。请注意,如果在我们将字符设置为长度到单词的开头之前没有出现过字符。

2)让我们调用我们得到V的列表(例如V = [1,2,3,1,5,5,7])。

3)定义函数f(x),它计算最长的单词而不重复以索引x结尾的字符。

它认为: f(0)= 0 f(x)= min(f(x-1)+1,V [x]),x> 0

4)迭代单词并在每个索引处计算f。

5)找到最大值f。

每一步都是O(N),但如果你玩的话,你可以同时做所有这些,甚至不变。

希望这有帮助。

答案 3 :(得分:1)

在下面找到优化版本。与初始版本相比的增强功能:

  • 它不会创建其他对象
  • 它不会复制任何字符,它适用于字符串数据本身
  • 减少了比较步骤的数量

edit2 我上传了一个JMH基准here,它比较了这个问题的三个答案的算法。直接链接到benchmark result

public static void main(String[] args) {
    String[] strings = {"abcabcdebb", "abcbacde", "abb", "bba",
        "cbaabc", "abccba", "xabccba", "abcxcba", "abccbax",
        "", "a", "aa", "ab"
    };
    for (String s : strings) {
        System.out.printf("string: %-10s   maxSubStringLength: %d%n", s,
            maxSubStringLength(s));
    }
}

static int maxSubStringLength(String string) {
    if (string.isEmpty()) {
        return 0;
    }
    int maxLength = 1;
    int low = 0;
    for (int high = 1; high < string.length(); high++) {
        for (int pos = high - 1; pos >= low; pos--) {
            if (string.charAt(pos) == string.charAt(high)) {
                low = pos + 1;
                break;
            }
        }
        maxLength = Math.max(maxLength, high - low + 1);
        if (string.length() - low <= maxLength) {
            break;
        }
    }
    return maxLength;
}

如何运作

  • 我们会指向最左边的字符low
  • 我们将右字符high的指针增加到字符串
  • 的最大值
  • 如果我们增加了high指针,我们会向后搜索,如果最右边的字符(位置high上的那个字符)出现在从索引lowhigh的子字符串中
  • 如果没有发生
    • 我们更新或保持最大长度,具体取决于当前选定的子字符串是否长于先前找到的最大长度
  • 否则
    • 我们将左侧字符索引移动到左侧出现后的位置
  • 如果要测试的剩余子字符串长度不超过最大长度,我们可以在此时停止检查
  • 继续第二个要点

首发帖子,看看代码的演变

一个非常务实的解决方案可能是

public static void main(String[] args) {
    String s = "abcabcbb";
    int maxLength = 1;
    for (int i = 0; i < s.length(); i++) {
        for (int j = i+2; j <= s.length(); j++) {
            String substring = s.substring(i, j);
            if (hasNoDupeChars(substring) && substring.length() > maxLength) {
                System.out.println("substring = " + substring);
                maxLength = substring.length();
            }
        }
    }
    System.out.println("maxLength = " + maxLength);
}

private static boolean hasNoDupeChars(String substring) {
    Set<Character> chars = new HashSet<>();
    for (Character c : substring.toCharArray()) {
        if (!chars.add(c)) {
            return false;
        }
    }
    return true;
}

编辑正如鲍里斯所提到的,仍然可以进行优化。在Donald E. Knuth之后我不会这样做:&#34;过早的优化是所有邪恶的根源。&#34; ; - )