介绍
双指针是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 159。
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;
}
LeetCode 159,至多包含两个不同字符的最长子串,这个也是典型的滑动窗口问题:
public int lengthOfLongestSubstringTwoDistinct(String s) {
Map<Character,Integer> map = new HashMap();
int maxL = 0;
int left=0,right=0;
while(right<s.length()){
char c1 = s.charAt(right);
map.put(c1, map.getOrDefault(c1,0)+1);
while(left<=right&&map.size()>2){
char c2= s.charAt(left);
left++;
if(map.get(c2)==1) map.remove(c2);
else map.put(c2,map.get(c2)-1);
}
maxL = Math.max(maxL, right-left+1);
right++;
}
return maxL;
}
总结
当题目中出现字符串匹配、连续子序列等字样的时候,如果只是简单的使用暴力解法,那么时间复杂度一般都会超过O( n 2 n^2 n2),这是可以考虑使用滑动窗口+双指针解决问题。