1.快慢指针
主要解决链表中的问题,比如典型的判定链表中是否包含环
解决的问题包括:
(1)判定链表中是否含有环
经典解法就是用两个指针,一个每次前进两步,一个每次前进一步。
如果不含有环,跑得快的那个指针最终会遇到 null,说明链表不含环;如果含有环,快指针最终会超慢指针一圈,和慢指针相遇,说明链表含有环。
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow=head,fast=head;
while(fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
return true;
}
}
return false;
}
}
(2)已知链表中含有环,返回这个环的起始位置
当快慢指针相遇时,让其中任一个指针重新指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。
下面代码包含三种情况:
(1)无环,则返回null
(2)有环+小尾巴
(3)有环+无尾巴
其实无尾巴是有尾巴的特殊情况
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head!=null && head.next==null){
return null;
}
ListNode slow=head,fast=head;
while(fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow) break;//找到环的入口
}
//如果没有环,则经过while循环之后fast和slow指向位置不同,则返回null
if(fast!=slow){
return null;
}
//寻找环的入口
//如果是环形链表带小尾巴,就会进入while循环寻找环的入口;
//如果不带尾巴,则上个while循环fast和slow总会在入口处(head)相遇:因为fast速度是slow的二倍,相遇时fast走了两圈,slow走了一圈,相遇处就是head,则不会进入下面的while循环。
fast=head;
while(fast!=slow){
fast=fast.next;
slow=slow.next;
}
return fast;
}
}
(3)寻找链表的中点
让快指针一次前进两步,慢指针一次前进一步,当快指针到达链表尽头时,慢指针就处于链表的中间位置。
当链表的长度是奇数时,slow 恰巧停在中点位置;如果长度是偶数,slow 最终的位置是中间偏右。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode middleNode(ListNode head) {
ListNode fast=head,slow=head;
while(fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
return slow;
}
}
(4)寻找链表的倒数第 k 个元素
让快指针先走 k 步,然后快慢指针开始同速前进。这样当快指针走到链表末尾 null 时,慢指针所在的位置就是倒数第 k 个链表节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
if(head==null){
return null;
}
//防止k超过链表长度
int len=0;
ListNode temp=head;
while(temp!=null){
temp=temp.next;
len++;
}
if(k!=len){
k=k%len;
}
ListNode fast=head,slow=head;
//fast先走k步
while(k>0){
fast=fast.next;
k--;
}
//一起走,最后slow的位置就是倒数第k个节点
while(fast!=null){
fast=fast.next;
slow=slow.next;
}
return slow;
}
}
(5)有序数组/链表去重
快指针遇到不同的数就和慢指针交换,最后慢指针以及慢指针之前的数组部分都是不重复的
数组:
class Solution {
public int removeDuplicates(int[] nums) {
int n=nums.length;
int fast=0,slow=0;
while(fast<n){
if(nums[fast]!=nums[slow]){
slow++;
nums[slow]=nums[fast];
}
fast++;
}
return slow+1;
}
}
链表:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head==null) return null;
ListNode fast=head,slow=head;
while(fast!=null){
if(fast.val!=slow.val){
slow.next=fast;
slow=slow.next;
}
fast=fast.next;
}
slow.next=null;//处理末尾
return head;
}
}
(6)移除元素
设要移除的元素是val,则快指针遇到不等于val元素的时候,就和慢指针指向的元素进行交换,最后从数组开头到慢指针的位置都是不包含要移除元素的,元素长度即为慢指针+1
class Solution {
public int removeElement(int[] nums, int val) {
int n=nums.length;
if(n==0) return 0;
int fast=0,slow=0;
while(fast<n){
if(nums[fast]!=val){
nums[slow]=nums[fast];
slow++;
}
fast++;
}
return slow;
}
}
(7)移动零
fast指针遇到不为0的元素就和slow指针指向的元素进行交换
class Solution {
public void moveZeroes(int[] nums) {
int n=nums.length;
if(n==0) return;
int fast=0,slow=0;
while(fast<n){
if(nums[fast]!=0){
int temp=nums[slow];
nums[slow]=nums[fast];
nums[fast]=temp;
slow++;
}
fast++;
}
}
}
2.左右指针
左右指针在数组中实际是指两个索引值,一般初始化为 left = 0, right = nums.length - 1
解决的问题包括:
(1)二分查找
(2)两数之和
在一个有序数组中寻找两个数,这两个数的和是题目要求的target