“你的时间有限,所以不要浪费时间过别人的生活。”
----- 乔布斯
前言
通过一周对数组和链表算法题的练习,发现双指针法在其中运用非常广泛且好用,并对TS的应用更加熟悉,故将这周写过的双指针法总结如下。
移除元素
function removeElement(nums: number[], val: number): number {
let fast:number=0;
let slow:number=0;
while(fast<nums.length){
if(nums[fast]!=val){
nums[slow]=nums[fast];
slow++
}
fast++
}
return slow
};
本题思路:
双指针的应用:这个双指针,快的指针发现目标数字时继续向下移动,而慢指针则指向目标数字后便不再移动等待快指针的覆盖。
易错点:
- 数组无法直接删除元素,只能通过覆盖的形式去移除元素,因为 在TS中,数组存储的地址是连续的
有序数组的平方
function sortedSquares(nums: number[]): number[] {
let left=0;
let right=nums.length-1;
let tag=nums.length-1;
let i=0;
let arr = new Array(nums.length);
while(i<nums.length){
nums[i]=nums[i]*nums[i];
i++;
}
while(left<=right){
if(nums[left]>nums[right]){
arr[tag--]=nums[left++];
}else if(nums[right]>nums[left]){
arr[tag--]=nums[right--];
}else{
arr[tag--]=nums[left++];
arr[tag--]=nums[right--];
}
}
return arr;
};
本题思路:
双指针的应用:两边大中间小,通过两个指针从两边进行遍历,排序到新数组里。
易错点:
- 第一次做题目的时候创建新数组arr,直接把nums赋值给了arr,导致出错,以后操作nums也会影响到arr数组,创建新数组语法
let arr = new Array(nums.length);
可以尝试用Math.max
长度最小的子数组
function minSubArrayLen(target: number, nums: number[]): number {
let left:number = 0,
length:number=0,
sum:number=0.,
res:number=Infinity;
for(let right=0;right<nums.length;right++){
sum+=nums[right];
while(sum>=target){
length=right-left+1;
res=Math.min(res,length);
sum-=nums[left++];
}
}
return res===Infinity?0:res;
};
本题思路:
题目要求求大于目标数字的最小子数组,同样使用双指针法,快的指针往前遍历数组直到里面的加数大于目标数,遍历停止,返回长度,然后开始移动慢指针,往后移,看是否同样满足。满足后返回长度直到它小于目标数快指针再继续遍历。
该方法又叫滑动窗口
在 TypeScript 中,Infinity 是一个特殊的数值,表示正无穷大。它的类型是 number。
删除链表的倒数第N个节点
function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
const newNode= new ListNode(0,head);
let current=newNode;
let slow=newNode;
let count=0;
if(n<=0){
return head;
}
while(current){
if(count>n){
slow=slow.next;
}
current=current.next;
count++;
}
if(slow&&slow.next){slow.next=slow.next.next;}
return newNode.next;
};
本题思路:
第一次写的时候是分两步,第一步先通过一遍遍历找到倒数第N个节点的位置,再进行删除操作。这样稍微有些啰嗦,知道快慢指针法之后尝试的去做了,通过指针之间的逻辑关系,快指针遍历了第n次之后,慢指针开始从头节点移动正好可以找到想要删除节点的前一个节点。
易错点:
- 这题最好使用虚拟头节点,这样的好处是不用思考头删的情况,头删在本题非常的麻烦。
环形链表II
function detectCycle(head: ListNode | null): ListNode | null {
const newNode=new ListNode(0,head);
let fast=newNode;
let slow=newNode;
let index=newNode;
let now:ListNode|null=null;
while(slow&&slow.next&&fast&&fast.next&&fast.next.next){
slow=slow.next;
fast=fast.next.next;
if(slow==fast){
now=slow;
break;
}
}
while(now&&now.next&&index&&index.next){
now=now.next;
index=index.next;
if(now==index){return now;}
}
return now;
};
本题思路:
本题算是使用了两次双指针,第一次通过快慢指针确定相遇点位置,第二次通过推导出的数学等式利用双指针找到链表的起始节点,但是本题使用的双指针算是最基础的使用方法。
双指针法总结
为什么要使用双指针?
在处理数组,链表,字符串进行删除,反转等操作的时候,往往需要操作两个以上的元素,使用双指针法可以巧妙地解决这些问题。
此外,它还能降低时间复杂度,使代码运行更高效更简洁。
如何使用双指针去解决问题?
通过做过一些题目,首先在写代码之前画出使用双指针所要达到的效果,例如:
在删除链表倒数第N个节点中,你需要找到倒数第N个节点并删掉它,换句话说,你需要找到倒数第N个节点的前一个节点,你才能删掉它。
假设只有一个指针,那么它能做的工作是可以遍历整个链表知道它有x个元素,那么第二个指针就可以通过和第一个指针的联系找到x-n个元素,那就是第一个指针走到n时,它再从起点出发。
无论是滑动窗口,快慢指针,左右指针,根据题目要求画出效果,再使用代码去实现即可。