概念:
通过两个指针,不断的调整区间,从而求出问题最优解的算法就叫“尺取法”,由于利用的是两个双指针,所以也叫作“双指针”算法,这里的“尺”的含义,主要是因为这类问题,最终要求解的都是连续的序列(子串),就好比一把尺子,故而得名
1.最长不重复子串
给定一个字符串
s
,请你找出其中不含有重复字符的 最长子串 的长度。
1.初步分析
- n<=10^7;
- 最长
- 所有的字符不重复
- 子串
根据上面的几个关键词,我们可以得出一些结论,首先,根据n的范围已经能大致确认这是一个需要O(n)或者O(nlongn)的算法才能解决这个问题;其次,“最长”这个词告诉我们,可能是一个动态规划问题或者是贪心问题,判断字符是否重复可以用hash表在O(1)的时间内进行判断,最后枚举所有的子串是O(n^2)的
2.朴素算法
用max记录我们所需要的最大不重复子串的长度,用一个hash表代表某个字符是否出现过,算法的描述如下:
1.枚举子串的右端点i=0~n-1;
2.当hash表中出现hash[i]>1的时候,相当于双指针指向的区域范围内出现了重复字符,然后移动左指针,直到移出重复字符的时候,对指针区域长度求值,不断的移动,直到找到最大的无重复子串
public int lengthOfLongestSubstring(String s) {
if (s == null || s.length() == 0) {
return 0;
}
int left=0;
int max=0;
//将字符串转换为char
char[] str=s.toCharArray();
int[] hash=new int[128];
for(int right=0;right<s.length();right++){
char c=str[right];
hash[c]++;
while(hash[c]>1){
hash[str[left++]]--;
}
int val=right-left+1;
max=Math.max(val,max);
}
return max;
}
下面附一张不断枚举左端点的动态图,方便大家理解记忆(如果枚举左端点的话,最后还有额外的去判断是否越界,枚举右端点则不需要)
当区间内[i,j]中存在重复(红色)字符时,左指针i自增;否则右指针j自增,这部分大家可以自行下去尝试
2.算法描述
算法描述如下:
1.初始化i=0 j=i-1 代表一开始“尺子”的长度为0;
2.增长“尺子”的长度,即j=i+1;
3.判断当前这把“尺子”[i,j]是否满足题目给出的条件
1.如果满足,记录最优解
2.如果不满足,则减少“尺子”长度,缩小左端点 i=i+1 再次判断是否符合条件
满足条件时,j++,不满足条件时,i++;
双指针满足的前提条件:
- 单调性
举个栗子:[2 6 9 5 12 5] 这种数组可以用双指正解决么?每次移动它的情况是不确定的
任意一个指指针的增加,条件满足与否只会出现两种情况,即:
满足—>不满足或者是不满足—>满足,不会出现 满足—>不满足—>满足
- 时效性
必须要在O(1)或者O(log2n)的时间内,求出当前区间[i,j]是否满足既定条件,否则无法用这种算法进行求解
leetcode题单:
反转字符串
public void reverseString(char[] s) {
int i=0;
int j=s.length-1;
while(i<j){
char temp=s[i];
s[i]=s[j];
s[j]=temp;
i++;
j--;
}
}
判断子序列
public boolean isSubsequence(String s, String t) {
if(s==null||t==null){
return false;
}
int i=0;
int j=0;
while(i<s.length()&&j<t.length()){
if(s.charAt(i)==t.charAt(j)){
i++;
j++;
}else{
j++;
}
}
return i==s.length();
}
两数之和
public int[] twoSum(int[] nums, int target) {
int[] num = new int[2];
if (nums == null || nums.length == 0) return num;
int left=0;
int right=nums.length-1;
Arrays.sort(nums);
while(left<right){
if(nums[right]+nums[left]==target){
num[0]=left;
num[1]=right;
return num;
}else if(nums[right]+nums[left]<target){
left++;
}else{
right--;
}
}
return num;
}
无重复字符的最长子数组
public int lengthOfLongestSubstring(String s) {
if (s == null || s.length() == 0) {
return 0;
}
int left=0;
int max=0;
//将字符串转换为char
char[] str=s.toCharArray();
int[] hash=new int[128];
for(int right=0;right<s.length();right++){
char c=str[right];
hash[c]++;
while(hash[c]>1){
hash[str[left++]]--;
}
int val=right-left+1;
max=Math.max(val,max);
}
return max;
}
统计公平对的数目
public long countFairPairs(int[] nums, int lower, int upper) {
//同向双指针
Arrays.sort(nums);
long count = 0;
for (int i = 0; i < nums.length; i++) {
int num = nums[i];
//upper-num+1意思就是大于这个目标值的最左侧,右端是不包含
int m = nearIndex(nums, 0, i, upper - num + 1);
//lower-num意思就是大于lower-num的最左侧,左端是包含的
int n = nearIndex(nums, 0, i, lower - num);
count += m - n;//左闭右开
}
return count;
}
//二分搜索最左侧
public int nearIndex(int[] nums, int left, int right, int target) {
if (left >= right) {
return left;
}
int i = left;
int j = right;
while (i < j) {
int mid = i + (j - i) / 2;
if (nums[mid] >= target) {
j = mid;
} else {
i = mid + 1;
}
}
return i;
}