首页 > 代码库 > string类问题之滑动窗口类型
string类问题之滑动窗口类型
Longest Substring Without Repeating Characters
Given a string, find the length of the longest substring without repeating characters.
Examples:
Given "abcabcbb"
, the answer is "abc"
, which the length is 3.
Given "bbbbb"
, the answer is "b"
, with the length of 1.
Given "pwwkew"
, the answer is "wke"
, with the length of 3. Note that the answer must be a substring, "pwke"
is a subsequence and not a substring.
原题链接: http://oj.leetcode.com/problems/longest-substring-without-repeating-characters/
这道题用的方法是在LeetCode中很常用的方法,对于字符串的题目非常有用。 首先brute force的时间复杂度是O(n^3), 对每个substring都看看是不是有重复的字符,找出其中最长的,复杂度非常高。
public class Solution { public int lengthOfLongestSubstring(String s) { //暴力解法 if (s == null || s.length() == 0) { return 0; } int len = s.length(); int max = 1; for (int i = 0; i < len; i++) { for (int j = i + 1; j < len; j++) { if (isValid(s, i ,j) == true) { max = Math.max(max, j - i + 1); } } } return max; } private static boolean isValid(String s, int start, int end) { HashSet<Character> letters = new HashSet<Character>(); for (int i = start; i <= end; i++) { if(letters.contains(s.charAt(i))){ return false; } letters.add(s.charAt(i)); } return true; } }
优化一些的思路是稍微动态规划一下,每次定一个起点,然后从起点走到有重复字符位置,过程用一个HashSet维护当前字符集,认为是constant操作,这样算法要进行两层循环,复杂度是O(n^2)。
最后,我们介绍一种线性的算法,也是这类题目最常见的方法。基本思路是维护一个窗口,每次关注窗口中的字符串,在每次判断中,左窗口和右窗口选择其一向前移动。同样是维护一个HashSet, 正常情况下移动右窗口,如果没有出现重复则继续移动右窗口,如果发现重复字符,则说明当前窗口中的串已经不满足要求,继续移动有窗口不可能得到更好的结果,此时移动左窗口,直到不再有重复字符为止,中间跳过的这些串中不会有更好的结果,因为他们不是重复就是更短。因为左窗口和右窗口都只向前,所以两个窗口都对每个元素访问不超过一遍,因此时间复杂度为O(2*n)=O(n),是线性算法。空间复杂度为HashSet的size,也是O(n). 代码如下:
public class Solution { public int lengthOfLongestSubstring(String s) { if (s == null || s.length() == 0) { return 0; } int len = s.length(); int max = 1; int slow = 0, fast = 0; HashSet<Character> letters = new HashSet<Character>(); while (fast < len && slow < len ) { if (!letters.contains(s.charAt(fast))) { letters.add(s.charAt(fast)); fast++; } else { letters.remove(s.charAt(slow)); slow++; } max = Math.max(max, fast - slow); } return max; } }
--------------------------------------------------------------------------------
Minimum Window Substring
- Total Accepted: 74481
- Total Submissions: 326266
- Difficulty: Hard
Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).
For example,
S = "ADOBECODEBANC"
T = "ABC"
Minimum window is "BANC"
.
Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).
For example,
S = “ADOBECODEBANC”
T = “ABC”
Minimum window is “BANC”.
一个最短摘要算法的简化版本。
用到滑动窗口的概念。整个算法的运行过程可通过下图表示,注意仔细体会!
注意
在上图中灰色部分,当窗口中出现了所有目标字符后,需要从窗口的最左边开始往右滑动窗口边界,将左边窗口中多出来的目标字符移到窗口外,从而找到一个备选的最短窗口!
这个题较难,值得好好理解。
java代码:
public class Solution { public String minWindow(String s, String t) { int[] needToFind = new int[256]; int[] hasFound = new int[256]; int findAll = 0; int start = 0, end = 0; int s_len = s.length(); int t_len = t.length(); int minLen = Integer.MAX_VALUE; int minStart = 0; int minEnd = s.length() - 1; char c; intial(t, needToFind); while (end < s_len) { c = s.charAt(end); if (needToFind[c] == 0) { end++; continue; } hasFound[c]++; if (hasFound[c] <= needToFind[c]) { findAll++; } if (findAll == t_len) { while (needToFind[s.charAt(start)] == 0 || hasFound[s.charAt(start)] > needToFind[s.charAt(start)]) { if (hasFound[s.charAt(start)] > needToFind[s.charAt(start)]) { hasFound[s.charAt(start)]--; } start++; } if(minLen > (end-start+1)){ minLen = end-start+1; minStart = start; minEnd = end; } } end++; }// end of while (end < s_len) if (findAll < t_len) { return ""; } return s.substring(minStart, minEnd + 1); } private static void intial(String t, int[] needToFind) { int len = t.length(); for (int i = 0; i < len; i++) { char c = t.charAt(i); needToFind[c]++; } return; } }
真心很经典的题目
1. 利用find all 解决了每次新增加一个节点要用o(256)来判断是否合理的问题
2.两个指针,类似于counting sort的思想
3.用required char[i] 是否大于0来表示是否是我们需要的char
4.循环体的控制,外层循环是不断的把end往后移动,内层循环是把start向前移动
5. substring里面 end index 要注意+ 1
----------------------------------------------------------------------------------
Substring with Concatenation of All Words
You are given a string, s, and a list of words, words, that are all of the same length. Find all starting indices of substring(s) in s that is a concatenation of each word in words exactly once and without any intervening characters.
For example, given:
s: "barfoothefoobarman"
words: ["foo", "bar"]
You should return the indices: [0,9]
.
(order does not matter).
这道题看似比较复杂,其实思路和Longest Substring Without Repeating Characters差不多。因为那些单词是定长的,所以本质上和单一个字符一样。和Longest Substring Without Repeating Characters的区别只在于我们需要维护一个字典,然后保证目前的串包含字典里面的单词有且仅有一次。思路仍然是维护一个窗口,如果当前单词在字典中,则继续移动窗口右端,否则窗口左端可以跳到字符串下一个单词了。假设源字符串的长度为n,字典中单词的长度为l。因为不是一个字符,所以我们需要对源字符串所有长度为l的子串进行判断。做法是i从0到l-1个字符开始,得到开始index分别为i, i+l, i+2*l, ...的长度为l的单词。这样就可以保证判断到所有的满足条件的串。因为每次扫描的时间复杂度是O(2*n/l)(每个单词不会被访问多于两次,一次是窗口右端,一次是窗口左端),总共扫描l次(i=0, ..., l-1),所以总复杂度是O(2*n/l*l)=O(n),是一个线性算法。空间复杂度是字典的大小,即O(m*l),其中m是字典的单词数量。
首先是先把所给的字典利用HashMap (toFind)建一下,key存word,value存这个word出现的个数。
因为每个单词长度一样,外层循序只许循环wordLen次,每次指针挪一次,每一次循环遍历整个字符串。
内层循环每次遍历一个单词 (即 j = j + wordLen),把整个S字符串遍历检查。
需要在每次大循环维护一个count,看是不是达到了给的字典字符串数量,同时维护一个left 指针表示窗口的左端,也就是起始的index---每个符合条件的字符串的起始index,需要存到返回结果中。
为了能够检查是不是合格字符串,在这里维护一个hasFind的HashMap。用于记录窗口内找到的单词及其数量。
首先检查一个单词是不是在原始字典中出现,没出现的话说明这个单词肯定不符合标准,left指针指向下一个单词的起始点,计数器和hasFind都要清零。即string一旦被打断,就要从新的起始位置开始重新查找所有的wrod。
如果这个单词在原始字典里出现过,用更新原始字典的方法更新hasFind,如果这个单词出现的次数没有超过原始字典里记录的次数,那么count++。如果超过了,就需要挪动指针,并把超过的从hasFind里面删掉。用超过的这个单词作为while循环的控制变量,来从左侧开始删除单词。 注意,如果删除的单词在hasFind里面的数量比需要查找的数量小了,也就是说减少了已找到的数量,那么我们需要减小计数器。
最后,如果count达到了L的length,说明找到了一个合格的字符串,那么将index存入返回结果res中,再把left挪到下一个单词处,更新hasFind即可。
public class Solution { public List<Integer> findSubstring(String S, String[] L) { // Note: The Solution object is instantiated only once and is reused by each test case. List<Integer> result = new ArrayList<Integer>(); if(S==null || S.length()==0 || L==null || L.length==0) { return result; } HashMap<String,Integer> toFind = new HashMap<String,Integer>(); for(String temp : L) { if (!toFind.containsKey(temp)){ toFind.put(temp, 1); } else{ toFind.put(temp, toFind.get(temp) + 1); } } int wordLen = L[0].length(); int sLen = S.length(); int lLen = L.length; for(int i = 0; i < wordLen; i++) { HashMap<String,Integer> hasFind = new HashMap<String,Integer>(); int count = 0; int left = i; for(int j = i; j <= sLen - wordLen; j += wordLen) { String current = S.substring( j, j + wordLen); if(!toFind.containsKey(current)){ hasFind.clear(); count = 0; left = j + wordLen; } else { //update hasFind dictionary if(hasFind.containsKey(current)) { hasFind.put(current,hasFind.get(current) + 1); } else { hasFind.put(current,1); } //update count if(hasFind.get(current) <= toFind.get(current)) { count++; } else { while (hasFind.get(current) > toFind.get(current)) { String temp = S.substring(left,left + wordLen); hasFind.put(temp, hasFind.get(temp) - 1); left += wordLen; if(hasFind.get(temp) < toFind.get(temp)){ count--; } } } if(count == lLen) { result.add(left); String temp = S.substring(left, left + wordLen); hasFind.put(temp,hasFind.get(temp) - 1); count--; left += wordLen; } } } //end for i } //end for j return result; } }
string类问题之滑动窗口类型