算法Day07-算法研习指导之双指针
原创
©著作权归作者所有:来自51CTO博客作者攻城狮Chova的原创作品,请联系作者获取转载授权,否则将追究法律责任
双指针技巧
基本概念
快慢指针
- 初始化指向链表的头结点head
- 前进时快指针fast在前,慢指针slow在后
- 可以巧妙解决链表中相关的问题
判断链表中是否包含环
- 每个节点只知道下一个节点
- 所以一个指针是无法判断链表中是否包含环的
- 如果链表中不包含环,那么这个指针最终会遇到空指针null. 表示这个链表已经到头了,表示这个链表不包含环
boolean hasCycle(ListNode head) {
while (head != null) {
head = head.next();
}
return false;
}
- 如果链表中含有环,那么这个指针就会陷入死循环. 因为环形数组中没有null指针作为尾部节点
- 使用双指针的快慢指针算法:
- 一个快指针fast, 一个慢指针slow
- 如果不含有环,跑得快的指针快指针fast最终会遇到null, 说明链表中不含有环
- 如果含有环,快指针fast最终会超过慢指针slow一圈,最终和慢指针slow相遇,说明链表中含有环
boolean haCycle(ListNode head) {
ListNode fast, slow;
fast = slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
return true;
}
}
return false;
}
返回有环链表中环的起始位置
ListNode detectCycle(ListNode head) {
ListNode fast, slow;
fast = slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
break;
}
}
slow = head;
while (slow != fast) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
- 当快慢指针相遇时,让其中的任意指针指向头结点head. 然后快慢指针以相同的速度前进,再次相遇时所在的节点位置就是环的开始位置:
- 第一次相遇时,如果慢指针slow走了k步,那么快指针fast一定走了2k步,即比慢指针slow多走了k步,也就是环长度的倍数
- 假设相遇点距环的起点的距离为m, 那么环的起点距离头结点head的距离为k-m, 也就是说如果从头结点head前进k-m步就能到达环的起点
- 如果从相遇点继续前k-m步,也恰好到达环起点
- 因此,只要将快慢指针中的任意一个指针重新指向head, 然后两个指针以相同的速度前进,那么相遇之处就是环的起点
寻找链表的中点
- 可以让快指针一次前进两步,慢指针一次前进一步,当快指针到达链表的尽头时,慢指针就处于链表中间的位置
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
- 当链表的长度为奇数时 ,slow恰巧停在中点位置.如果长度为偶数 ,slow最终的位置是中间位置偏右
- 寻找链表中点的作用: 对链表进行归并排序
- 归并排序的重点就在于二分:
- 求中点,然后递归地将数组进行二分
- 最后合并两个有序数组
- 对于链表而言,合并两个有序链表的难点就在于二分
寻找链表的倒数第k个元素
- 可以让快指针fast先走k步,然后快慢指针开始以相同的速度前进.这样当快指针fast走到链表的末尾null时,慢指针slow所在的位置就是倒数第k个链表节点,即慢指针slow所在位置的元素就是链表的倒数第k个元素
ListNode slow, fast;
slow = fast = head;
while (k --> 0) {
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
左右指针
- 左右指针在数组中实际是指两个索引值
- 初始化left = 0, right = nums.length - 1
二分查找
int BinarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left)/2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
}
}
return -1;
}
两数之和
- 只要数组有序,就应该使用双指针技巧
- 通过调节left和right可以调整sum的大小
int twoSum(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) {
return new int[] {left + 1, right + 1};
} else if (sum < target) {
left++;
} else if (sum > target) {
right--;
}
}
return new int[] {-1, -1};
}
反转数组
void reverse(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
// 转换数组左指针和右指针的元素
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
right--;
}
}
滑动窗口
- 使用双指针的滑动窗口算法,可以解决字符串匹配问题
- 一般遇到字符串匹配问题以及子串相关问题,首先想到使用滑动窗口算法