标签:leetcode linkedhashset longest substring
Given a string, find the length of the longest substring without repeating characters. For example, the longest substring without repeating letters for "abcabcbb" is "abc", which the length is 3. For "bbbbb" the longest substring is "b", with the length of 1.
涉及longest substring的题目一般都有着明显的DP特征。这题的一种一维DP思路是:将所有不重复的子字符串按结尾的数组下标分类,让maxEndsWith[i]表示字符串的结尾以i为数组下标,并取最长长度。
对于一个给定的不存在重复字符的字符串,以及一个试图在尾部新添的字符:
1)如果新添字符并不跟所给字符串里的字符重复,那么最长长度加1,即maxEndsWith[i] = maxEnds[i - 1] + 1;
2)如果新添加重复,那么添加此字符的代价是要删除之前的重复字符,以及不计数该重复字符之前的字符。
这种思路其实看起来有些类似于数据流算法,核心是维护一个sliding window,保证这个window里所有元素都不重复。涉及去重操作,使用set是最好不过啦。不过这里的难度在于发现重复字符后,不光删除改重复字符,还要删除在此之前的所有字符。
对于满足删除操作,可以用一种很"别扭"的实现方式,即不用set而改用map,key存字符,value存index,然后另外把map里涵盖的所有index存在一个queue(或者说deque)里。这样一来,插入和删除都同时需要修改map和queue。
其实对于这种DP,还可以有一种更简洁优雅的实现方式,就是利用LinkedHashSet这个数据结果。不同于HashSet,LinkedHashSet会根据元素插入的顺序,把各个元素串联起来成一个链表,所以在遍历的时候会严格遵循插入顺序。由此观之,使用LinkedHashSet的好处很明显,由于我们这里维护的是一个sliding window,在重复字符位置之前的字符肯定都是在之前插入window的,插入顺序和遍历顺序都排在重复字符的前面。因此,一旦遇到重复字符,就可以从sliding window的开头开始删除,一直按遍历顺序删到重复字符出现,并一起删掉。
public int lengthOfLongestSubstring(String s) { if (s.length() == 0) return 0; int ret = 1; Set<Character> set = new LinkedHashSet<Character>(); int[] maxEndsWith = new int[s.length()]; maxEndsWith[0] = 1; set.add(s.charAt(0)); for (int i = 1; i < s.length(); ++i) { char c = s.charAt(i); if (!set.contains(c)) { set.add(c); maxEndsWith[i] = maxEndsWith[i - 1] + 1; } else { Iterator<Character> it = set.iterator(); while (it.hasNext()) { char front = it.next(); it.remove(); if (front == c) { break; } } set.add(c); maxEndsWith[i] = set.size(); } ret = Math.max(maxEndsWith[i], ret); } return ret; }
遍历顺序删到重复字符出现,并一起删掉。注意以上代码虽然有2层循环,但是均摊的时间复杂度还是O(N),因为每个字符都仅仅会被加入的window一次,并且仅仅会从window中删除一次。另外这里对数据总共就只有一次扫描,典型的数据流算法特点。
[LeetCode] Longest Substring Without Repeating Characters (LinkedHashSet的妙用)
标签:leetcode linkedhashset longest substring
原文地址:http://blog.csdn.net/whuwangyi/article/details/40446577