乘风破浪:LeetCode真题_003_Longest Substring Without Repeating Characters
乘风破浪:LeetCode真题_003_Longest Substring Without Repeating Characters
一、前言
在算法之中出现最多的就是字符串方面的问题了,关于字符串的模式匹配,回文字符串,等等问题都是非常经典的,同样的在字符串之中寻找最长的不重复的字符串的长度也是比较有意义的。
二、LeetCode真题_003_Longest Substring Without Repeating Characters
2.1 问题
2.2 分析与解决
问题是非常清晰的,我们最先想到的就是使用暴力算法,通过读取当前的字符和已经读取过的字符进行比较,如果冲突则调整开始的位置,并记录当前的最大长度,直至遍历结束,这样的时间复杂度就要到达O(n~3)了。那么有没有比较好的解决方案的,我们又想到了hash算法,这样就省去了比较需要花费的遍历时间,增加了程序需要占用的空间。
首先看看官方的作答:
暴力算法:
public class Solution { public int lengthOfLongestSubstring(String s) { int n = s.length(); int ans = 0; for (int i = 0; i < n; i++) for (int j = i + 1; j <= n; j++) if (allUnique(s, i, j)) ans = Math.max(ans, j - i); return ans; } public boolean allUnique(String s, int start, int end) { Set<Character> set = new HashSet<>(); for (int i = start; i < end; i++) { Character ch = s.charAt(i); if (set.contains(ch)) return false; set.add(ch); } return true; } }
使用HashSet:
public class Solution { public int lengthOfLongestSubstring(String s) { int n = s.length(); Set<Character> set = new HashSet<>(); int ans = 0, i = 0, j = 0; while (i < n && j < n) { // try to extend the range [i, j] if (!set.contains(s.charAt(j))){ set.add(s.charAt(j++)); ans = Math.max(ans, j - i); } else { set.remove(s.charAt(i++)); } } return ans; } }
使用HashMap:
public class Solution { public int lengthOfLongestSubstring(String s) { int n = s.length(), ans = 0; Map<Character, Integer> map = new HashMap<>(); // current index of character // try to extend the range [i, j] for (int j = 0, i = 0; j < n; j++) { if (map.containsKey(s.charAt(j))) { i = Math.max(map.get(s.charAt(j)), i); } ans = Math.max(ans, j - i + 1); map.put(s.charAt(j), j + 1); } return ans; } }
假设只有ASCII的字符出现,没有中文:
public class Solution { public int lengthOfLongestSubstring(String s) { int n = s.length(), ans = 0; int[] index = new int[128]; // current index of character // try to extend the range [i, j] for (int j = 0, i = 0; j < n; j++) { i = Math.max(index[s.charAt(j)], i); ans = Math.max(ans, j - i + 1); index[s.charAt(j)] = j + 1; } return ans; } }
我们的算法:
1 import java.util.Arrays; 2 import java.util.HashMap; 3 import java.util.Map; 4 5 public class Solution { 6 7 // 可以处理所有的UTF-8字符 8 public int lengthOfLongestSubstring(String s) { 9 // 字符串输入不合法 10 if (s == null) { 11 return 0; 12 } 13 14 // 当前处理的开始位置 15 int start = 0; 16 // 保存结果 17 int result = 0; 18 // 访问标记,记录最新一次访问的字符和位置 19 Map<Character, Integer> map = new HashMap<>(s.length()); 20 21 for (int i = 0; i < s.length(); i++) { 22 char ch = s.charAt(i); 23 // 如果字符已经出现过(在标记开位置算起),就重新标记start 24 if (map.containsKey(ch) && map.get(ch) >= start) { 25 start = map.get(ch) + 1; 26 } 27 // 如果没有出现过就求最大的非重复子串的长度 28 else { 29 result = Math.max(result, i - start + 1); 30 } 31 32 // 更新访问记录 33 map.put(ch, i); 34 } 35 return result; 36 } 37 38 // 只考虑ASCII字符 39 public int lengthOfLongestSubstring2(String s) { 40 // 字符串输入不合法 41 if (s == null) { 42 return 0; 43 } 44 45 // 标记字符是否出现过,并且记录是的最新一次访问的元素的位置 46 int[] appear = new int[256]; 47 // 初始化为-1 48 Arrays.fill(appear, -1); 49 // 当前处理的开始位置 50 int start = 0; 51 // 保存结果 52 int result = 0; 53 54 for (int i = 0; i < s.length(); i++) { 55 // 如果字符已经出现过(在标记开位置),就重新标记start 56 if (appear[s.charAt(i)] >= start) { 57 start = i + 1; 58 } 59 // 如果没有出现过就求最大的非重复子串的长度 60 else { 61 result = Math.max(result, i - start + 1); 62 } 63 // 标记第i个字符已经被访问过(最新是第i个位置) 64 appear[s.charAt(i)] = i; 65 } 66 67 return result; 68 } 69 }
三、总结
字符串方面的问题我们要非常熟悉String的一些工具函数,以及考虑到hash算法的数据结构来优化和解决问题。