介紹
雙指針是LeetCode裏面非常實用並且常用的一種技巧,而且有應用的範圍也很廣。比如二分法(binary search)也可以看成是左右兩個指針縮小搜索範圍,確定最終結果;快慢指針確定鏈表是不是含有環;等等。不過這裏筆者介紹用在滑動窗口結合雙指針的技巧。下面先引入一個簡單的例子:LeetCode 3,難度中等,要求找到無重複字符的最長子串。
那麼對於上述的字符串來說最長的無重複字符的子串是abc(其中的一種),所以長度是3。那麼最直觀的就是暴力搜索,用一個map或者set存儲遍歷到的字符,如果出現重複的話就退出,然後比較長度。
但是這樣的解法顯然時間複雜度太高。我們可以嘗試構建一個窗口,判斷這個窗口是否滿足要求;如果滿足要求,那麼縮小窗口;如果不滿足要求,繼續往前滑動。重複以上操作,得到最終的結果。
那麼這類問題的核心代碼就是:
//定義左右指針
int left = 0, right = 0;
while(right<s.length()){
//窗口添加元素,且right指針往右前進
while(meet){
//窗口裏面的元素減少,且left指針向右前進
}
}
實例
可以用滑動窗口雙指針解決的leetcode問題有:LeetCode 3,LeetCode 713, LeetCode 438。
LeetCode 3的代碼如下:
public int lengthOfLongestSubstring(String s) {
char ch[] = s.toCharArray();
HashMap<Character, Integer> map = new HashMap();
int left=0, right=0,ans=0;
while(right<s.length()){
char c1 = ch[right++];
map.put(c1, map.getOrDefault(c1,0)+1);
while(map.get(c1)>1){
char c2 = ch[left];
map.put(c2, map.get(c2)-1);
left++;
}
ans = Math.max(ans,right-left);
}
return ans;
}
LeetCode 713,這題比較簡單,要求找到乘積小於k的連續子數組的個數。
public int numSubarrayProductLessThanK(int[] nums, int k) {
if(k<=1) return 0;
int left =0, len =0,pro=1;
for(int j=0;j<nums.length;j++){
pro *=nums[j];
while(pro>=k) pro/=nums[left++];
len+=j-left+1;
}
return len;
}
LeetCode 438,要求找到異位詞的起始位置。所謂異位詞,就是和模板字符串裏出現的字符都一樣,並且可以對應位置不同。比如abc的異位詞可以是cba,cab等。
public List<Integer> findAnagrams(String s, String p) {
List<Integer> list = new ArrayList();
int[] arrS = new int[26];
int[] arrP = new int[26];
int count=0;
for(char c:p.toCharArray()){
arrP[c-'a']++;
if(arrP[c-'a']==1) count++;
}
int left=0, right=0,match=0;
while(right<s.length()){
char c1 = s.charAt(right);
if(arrP[c1-'a']>0){
arrS[c1-'a']++;
if(arrS[c1-'a']==arrP[c1-'a']) match++;
}
right++;
while(match==count){
if(right-left==p.length()){
list.add(left);
}
char c2 = s.charAt(left);
if(arrP[c2-'a']>0){
arrS[c2-'a']--;
if(arrS[c2-'a']<arrP[c2-'a']) match--;
}
left++;
}
}
return list;
}
總結
當題目中出現字符串匹配、連續子序列等字樣的時候,如果只是簡單的使用暴力解法,那麼時間複雜度一般都會超過O(),這是可以考慮使用滑動窗口+雙指針解決問題。