携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情
快到金(铁)9银(铜)10了,最近也准备提桶跑路了,从简简单单的题开始刷算法,这段时间发现不少题目通过双指针可以更快或者更清晰的去解,来分享一下。至于为什么是教弟弟呢,因为我不想当弟弟。
快慢指针
特征
- 快慢指针需要使用两个变量来标记前后。
- 快指针的移动速度快或者说始终在慢指针前面,而慢指针移动速度慢或者说在达到某个条件后才进行前进。
例子
快指针fast就像古代的斥候,而慢指针slow就代表已方阵营。斥候fast不断向前侦察敌情,并汇报给己方阵营slow,而slow则根据斥候fast探查的消息选择是否前进、同斥候汇合。 我们可以发现,快指针作为打头阵的,必然是始终在前方领路的,而慢指针只有在满足条件的情况下才会与快指针汇合,利用这个特性,我们就可以搞点事情了。
实际应用一——寻找合适开会的时间
// 寻找合适开会的时间
// [start, end]含有0~24的整数,意味着当前时间段已经有安排。
// 以下是组员全员的安排表。
// [
// [[13,15], [11,12], [10,13]], //成员1的安排
// [[8, 9]], //成员2的安排
// [[13, 18]] //成员3的安排
// ]
// 请实现一个findMeetingSlots()来查找全员都有空的时间段。
// 上述的例子的话,需要返回如下时间段:
// [[0,8],[9,10],[18,24]]
// 补充// 传入的时间段并没有事先排序
// 即使是一个人的安排,也有可能出现时间段的重复
const findMeetingSlots = (planTimes) => {
const schedule = new Array(24).fill(0);
planTimes.flat(1).forEach(item => {
schedule.fill(1, item[0], item[1]);
})
console.log(schedule);
let start = 0, end = 0, lens = schedule.length, result = [];
while (end < lens) {
if (schedule[start] === 1 && schedule[end] === 0) { // [1,0], reset start index, incre end
start = end;
end++;
continue;
}
if (schedule[end] === 1) { // meet end tag
if (schedule[start] === 0) { // [0, ..., 1]
result.push([start, end]);
}
start = end; // [0, ..., 1] [1, ..., 1], update start index
}
end++;
}
if (end === lens && end - start >= 1) {
result.push([start, end]);
}
return result;
}
我们先求得所有成员的空闲时间和已安排时间schedule。
[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0]
是不是思路就明朗起来了。
那么不同数字就代表不同阵营,此时我们需要统计的阵营是“空闲时间”,也就是0所代表的阵营。
斥候fast不断向前打探,最终探查到前方为敌方阵营,此时汇报已方阵营,已方阵营slow再与斥候fast汇合,这个时候对于我们来说就知道了,slow到fast这条路上全是自己人且安全的,可以进行汇合,那么我们将结果不断进行一个统计,就可以求得最终解了。
对撞指针
对撞指针指的是两个指针不断向里逼近,搜寻最终答案的解法。
特征
-
对撞指针需要使用两个变量来标记首尾。
-
首指针一般在头部位置,尾指针一般在末尾。
-
当首指针和尾指针“相撞”时,判断结束。
根据解法名字我们可以发现对撞指针也是需要两个变量来标记位置的,且是不断逼近的过程。
实际应用一——回文字符串
// 给定一个字符串 s ,验证 s 是否是 回文串
const str = 'abccba';
const isPalindrome = (str) => {
let start = 0, end = str.length - 1;
while (start < end) {
if (str[start] !== str[end]) return false;
start++;
end--;
}
return true;
}
console.log(isPalindrome(str));
这道题就可以使用对撞指针来解。
字符串用str表示,头指针用变量start表示,尾指针用变量end表示。start指向字符串第一个字符str[start],end指向字符串尾str[end],我们只需要判断头指针所在字符和尾指针所在字符是否相等,如果相等则头指针往后一步,尾指针往前一步,继续比较,一直到str[start]==str[end]返回false,或start≧end时返回true时结束。
实际应用二——二分查找
var searchInsert = function(nums, target) {
if(nums.length == 1) return nums[0] >= target ? 0 : 1;
let start = 0, end = nums.length, mid;
while(start < end) {
mid = Math.floor((start + end) / 2);
if(nums[mid] == target) return mid;
if(target > nums[mid]) {
start = mid + 1;
} else if(target < nums[mid]) {
end = mid;
}
}
return start;
};
二分查找的前提是有序数组,比如升序,我们通过将目标值target与有序数组的中位数nums[mid]进行比较,如果target>nums[mid],则说明我们的目标值所在区间是[mid,end]之间,反之则说明在[start, mid-1]之间。通过不断的缩小区间,达到更快的查找效率。
结束语
双指针能够解决的问题不止于此,本文意在教会大家一种思路,见到类似题目时可以考虑是否可以使用双指针进行求解,同时也是对自己刷算法过程的一个总结。
希望大家高抬贵嘴。