当前位置:   article > 正文

[Lesson Learn] LeetCode #3 Longest Substring Without Repeating Characters_项目lesson learn

项目lesson learn

首先先赞一下LeetCode,这道题暴力解法(会超时)原本的Java版参考答案中有一处错误,反馈后仅仅5分钟就解决了,算上读题理解代码的时间,意味着刚反馈就开始回应了。点赞。


首先想到的就是暴力破解,穷举各种可能性,然后逐一判断是否是不包含重复字母的子串,然后记录其中最长的子串的长度。

LeetCode上提供的修正了错误之后的暴力破解的代码如下:

  1. public class Solution {
  2. public int lengthOfLongestSubstring(String s) {
  3. int n = s.length();
  4. int ans = 0;
  5. for (int i = 0; i < n; ++i)
  6. for (int j = i + 1; j <= n; ++j)
  7. if (allUnique(s, i, j)) ans = Math.max(ans, j - i);
  8. return ans;
  9. }
  10. public boolean allUnique(String s, int start, int end) {
  11. Set<Character> set = new HashSet<>();
  12. for (int i = start; i < end; ++i) {
  13. Character ch = s.charAt(i);
  14. if (set.contains(ch)) return false;
  15. set.add(ch);
  16. }
  17. return true;
  18. }
  19. }
以上代码是完全遍历了所有的情况,一个不漏。

如果一个子集包含重复字母,则它的超集必然也是。所以可以在内层for循环的if判断后边加一句:

else break;
进行剪枝。

然而依旧超时。


每次判断是否是不包含重复字母的子串时,可以不用从头判断该子串的每一个字母,只需判断新加入的这一个字母是否在原子串中存在即可。

如果不在原子串中,则无事继续。如果已经在原来的子串中,则这两个字母之间的内容保留,而这两个重复字母则只能取其一。因为是从一端开始扫描过来的,假设是左端,那左端的所有信息已经全部掌握了,所以需要将左端的重复字母舍弃,保留右端的那个字母,然后继续向右扫描,直至结束。

为了迅速判断是否已经存在,要用到Map<Character, Integer>的containsKey()方法。为什么这里不是使用Set<Character>的contains()方法,而非得用Map的?Set和Map的区别在哪?或者换个类似的问题,集合和数组的区别在哪?一个无序一个有序,有序的数组的下标中可以包含位置信息。通过加一个Integer,来存储字母的位置信息。通过字母的位置信息,来判断一个字母是否符合要求。

假设用(i, j]来表示没有重复字母的子串中的字母的位置区间,j-i即中间所包含的数量,半开半闭区间的好处就在于,右侧减左侧即为差值,不用再考虑加一或者减一的问题。

读取的数据保存在字符串s中,下标从0开始。所以初始值,i为-1,j为0。用Map<Character, Integer>来保存已经扫描过的字母的信息,其中Integer保存的是已扫描部分中,值为Character的字母的最右边的位置。

  1. public class Solution {
  2. public int lengthOfLongestSubstring(String s) {
  3. int n = s.length(), ans = 0;
  4. Map<Character, Integer> map = new HashMap<>();
  5. for (int j = 0, i = -1; j < n; ++j) {
  6. char thisChar = s.charAt(j);
  7. if (map.containsKey(thisChar)) {
  8. i = Math.max(map.get(thisChar), i);
  9. }
  10. map.put(thisChar, j);
  11. ans = Math.max(ans, j - i);
  12. }
  13. return ans;
  14. }
  15. }

每一次循环读取一个值,设为thisChar。首先先判断已经扫描过的值中是否包含该值,然后由于map中保存的是该字母最右边的位置,i保存的是当前有效区间的左端点的位置,即可做出判断:如果map.get(thisChar) < i,即表示当前有效区间内并没有该值,i值不更新,将该字母的位置信息添加进map,j-i为当前有效区间的长度;如果map.get(thisChar) > i,即表示当前有效区间内有该值,将i更新为该值,同时将该字母在map中的位置信息进行更新,此时j指向的是该类字母在已扫描部分中最右边的那个字母,i指向的是该类字母在已扫描部分中从右边数第二个字母,(i, j)范围是没有重复字母的且与i和j处的字母不同,i和j位置是相同的字母,则(i, j]范围没有重复字母,且j-i即为长度。

  1. if (map.containsKey(thisChar)) {
  2. i = Math.max(map.get(thisChar), i);
  3. }
  4. map.put(thisChar, j);
无论thisChar在map中是否存在,语句都一样。不存在则加入,存在则更新。

更新完当前有效区间之后,j-i即为当前有效区间的长度,将该值与历史最大值ans进行比较,更新ans的值。

ans = Math.max(ans, j - i);
如此循环下去,经过一次遍历,即可得到结果。


此题不再贴LeetCode官方提供的“参考答案”,因为并没有上边的代码容易理解。

半开半闭区间相比全开区间或者全闭区间的优势就在于,不用考虑加一或者减一的修正问题,而一旦一个地方进行修正,其他所有相关联的地方都要进行相应的修正不能遗漏,而且该加一还是该减一,稍微思路不清楚,就容易写错。



附:

LeetCode #3 题目

LeetCode #3 官方“参考答案”

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/42518
推荐阅读
相关标签
  

闽ICP备14008679号