前言
今天是我参加东哥打卡活动的第二天,复习的是数组双指针这边的题目。😲
题目
1. 快慢指针
删除有序数组的重复项
链接:26. 删除有序数组中的重复项 - 力扣(LeetCode)
描述:
题目的意思就是,希望你,原地去修改数组,将数组中所有重复的数全部删除
解题思路:
- 我们使用了两个指针
fast和slow,让fast一直在前面走 - 当我们发现
nums[fast]== nums[slow]的时候 - 也就是出现了重复项了,那么我们直接将
fast++(类似于跳过这个元素) - 反之,当我们遇到
nums[fast]!= nums[slow] - 其实就是不是重复项,那么我们将
nums[slow] = nums[fast] - 这样的操作,其实就是为了保证,
num[0...slow]都是无重复的
代码:
function removeDuplicates(nums: number[]): number {
// 这边我们使用双指针,fast和slow,fast一直往前遍历,当slow !== fast时,slow向前一个,否则slow不懂
// 这样一直到结尾,这样[...slow]都是无重复的
let slow = 0;
let fast = 0;
while(fast < nums.length){
if(nums[fast] !== nums[slow]){
slow++;
// 并且将fast的值放到slow里
nums[slow] = nums[fast]
}
fast++
}
// 数组的长度为索引加1
return slow+1
};
因为这边返回的是数组的长度,所以,数组长度等于索引+1,所以就是slow+1
移除元素
描述:
希望你,原地修改一个数组,使他不含有某个元素
思路:
- 有了第一题的引导,其实我们也可以很快的想到,这题我们也是使用双指针
- 让
fast指针在前面走,当nums[fast] == val时,那么继续往后走 - 当我们
nums[fast] != val,那么我们让nums[slow] = nums[fast]
代码:
function removeElement(nums: number[], val: number): number {
// 采取双指针思路,slow和fast,当fast的值和val不相同时,那么让slow承接这个值,如果相同的话,slow不动,fast继续走
let fast = 0;
let slow = 0;
while(fast<nums.length){
if(nums[fast] !== val){
nums[slow] = nums[fast];
// 这个其实保证了0到slow-1都是非val的,那么里面的个数自然就是slow个
slow++
}
fast++
}
// 为什么这里面不是slow+1?
// 这是因为,在每次判断的时候,我们都会+1
return slow
};
比较:
第一题和第二题都采用的了相同的策略,并且题目要求也都一样,希望返回数组长度
我们也知道:数组长度= 索引+1
那么,为什么这题我们不用slow+1呢?
对于第一题来说,我们希望找到无重复的,当fast = 1,slow = 0的时候,并且nums[fast]!= nums[slow]的时候,我们其实是将nums[++slow] = nums[fast],也就是其实我们是放在了nums[1]的位置上,因为对于数组而言nums[0],他也是无重复的
但是对于第二题来说,当fast = 1,slow = 0,并且nums[fast] != val,那么我们会将nums[slow] = nums[fast],然后再slow++,我们保证的时[nums0...slow-1 ,之间都是没有val的,所以个数是slow个
2.左右指针
就是一种二分查找的感觉
两数之和2
链接:167. 两数之和 II - 输入有序数组 - 力扣(LeetCode)
描述:
简单概括,就是希望你找到这个数组中nums[i]+nums[j] = val的值,并将它们的下标返回,这边我们假设,下标是从1开始的
思路:
- 设计两个指针
left和right,我们希望他们相对而行,然后判断是不是和为val - 因为题目说了,非递减的,所以当
nums[left]+nums[right] < val,也就意味着,目前我的和偏小的,希望再大一点,所以left++,如果nums[left]+nums[right]>val,那么我们就需要缩小一点,那么就需要right-- - 因为题目说,下标从1开始,那么我们就需要将获取到的结果都+1
代码:
function twoSum(numbers: number[], target: number): number[] {
// 二分查找,因为非递减顺序排列
let left = 0;
let right = numbers.length-1;
while(left<=right){
if(numbers[left]+numbers[right] === target){
return [left+1,right+1]
}else if (numbers[left]+numbers[right]<target){
left++
}else if (numbers[left]+numbers[right]>target){
right--
}
}
return []
};
总结:
当我们遇到题目中说,数组是非递减的,那么一把都会采用二分法
最长回文字串
描述:
希望你找出,这个字符串里面最长的回文串
回文串:手和尾都相同的串
思路:
- 判断回文串,其实我们大家都知道怎么做
- 使用两个双指针
left和right,相向判断,判断两个是不是相同
- 使用两个双指针
- 但是这个我们需要求最长的串,那我们其实就不采用这种方案
- 回文串的特点
- 首尾是相同的
- 由中心向左右两边扩散,左右两边是相同的
- 我们利用的其实是第二个特点
- 但是我们需要注意的是,奇数还是偶数问题
- 对于奇数而言,中心只有一个
- 但是对于偶数而言,中心会有两个
- 现在我们只要依次去判断每个元素可能存在的回文串
- 选取他们间最大的那个,就可以了
代码:
function longestPalindrome(s: string): string {
// 首先,回文子串是需要考虑奇数还是偶数
// 积数的话,其实就是s[i]为中心的回文链
// 偶数的话,其实就是s[i]和s[i+1]的回文链
// 那么我们现在需要的就是,需要一个函数来判断,两个节点为中心的回文判断
let res = "";
for(let i = 0;i<s.length;i++){
let s1 = palindrome(s,i,i);
let s2 = palindrome(s,i,i+1);
// 更新答案
res = (s1.length>res.length)?s1:res;
res = (s2.length>res.length)?s2:res;
}
return res
};
//以s[mid1]和s[mid2]为中心,去判断回文串
function palindrome(s: string,mid1:number,mid2:number): string{
while(mid1>=0 && mid2 <s.length && s[mid1] === s[mid2]){
mid2++;
mid1--;
}
// 获取mid1到mid2的string
// console.log(s.slice(mid1+1,mid2))
return s.slice(mid1+1,mid2)
}
参考链接:双指针技巧秒杀七道数组题目 :: labuladong的算法小抄
总结
数组中的双指针有两种
-
快慢指针
一般就是需要你原地修改的时候使用
我觉得,其实我们在做滑动窗口的时候,利用的也是快慢指针的想法
-
左右指针
可以理解为二分查找这种