本文将双指针分为快慢指针和左右指针两大类进行介绍。
目录
一、快慢指针
快慢指针一般都初始化指向链表的头结点 head,前进时快指针 fast 在前,慢指针 slow 在后。
(1)判断链表中是否含有环
算法思路:
1. 设置快指针fast、慢指针slow,然后让fast以2的步长,slow以1的步长进行遍历
2.如果链表中不含有环,fast指针最后就会遇到空指针就代表到头了
3. 如果链表含有环,快指针肯定会在某一个时刻超过慢指针一圈,最后相遇
此时关于环有几点需要说明:
1. 首先,此时slow走了k步 / fast 走了2k步,相遇点距离圈的切点BC等于m,
那么如果要判断B点的位置就只需要让其中一个结点回到A起点,然后两者同速进行,相遇就是切点
解释:BC = m,AB = k-m, C转一圈到B = k - m
代码实现
ListNode detectCycle(ListNode head){
ListNode fast,slow;
fast = slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next; //2的步长
slow = slow.next; //1的步长
if(fast == slow) break;
}
//接下来是找圈的切点的位置
slow = head; //先让一个结点回到起点
while(fast != slow){
fast = fast.next;
slow = slow.next; //以相等步长前进
}
return slow;
}
(2)寻址链表的中点
算法思路:
依旧是fast以slow的两倍速进行遍历,当fast到链表结尾就代表slow到中点了。
奇偶分析:
1.当长度为奇数时,slow正好在中点
2. 当长度为偶数时,slow在中偏右一个节点
代码演示
ListNode slow,fast;
slow = fast = head;
while (fast != nu1l && fast.next != nu1l) {
fast = fast. next.next;
slow = slow. next;
}
// slow就在中间位置
return slow;
(3)寻找链表的倒数第 k 个元素
思路和上面类似,就不再赘述。
代码演示
ListNode slow, fast;
slow = fast = head;
while (k-- > 0)
fast = fast . next;
while (fast != nu1l) {
slow = slow. next;
fast = fast. next;
}
return slow;
二、左右指针
(1)二分查找
算法思路不再赘述,可以从我的二分查找算法一锅端学习。
(2)反转数组
算法思路:
一左一右进行遍历并交换,直到数组中点。
代码演示
void reverse(int[ ] nums) {
int left = 0;
int right = nums.length -1;
while (left < right) {
// swap(nums[left], nums[right])
int temp = nums [left];
nums[left] = nums[right];
nums[right] = temp;
left++; right--;
}
}
(3)滑动窗口,下面详解
三、滑动窗口算法
整体思路
int left = 0, right = 0;
while (right < s.size()) {
// 增大窗口
window.add(s[right]);
right++;
while (window needs shrink) {
// 缩小窗口
window.remove(s[left]);
left++;
}
}
leetcode例题讲解
(1)LeetCode 76 题 最小覆盖字串
思路:
1、我们在字符串
S
中使用双指针中的左右指针技巧,初始化left = right = 0
,把索引左闭右开区间[left, right)
称为一个「窗口」。2、我们先不断地增加
right
指针扩大窗口[left, right)
,直到窗口中的字符串符合要求(包含了T
中的所有字符)。3、此时,我们停止增加
right
,转而不断增加left
指针缩小窗口[left, right)
,直到窗口中的字符串不再符合要求(不包含T
中的所有字符了)。同时,每次增加left
,我们都要更新一轮结果。4、重复第 2 和第 3 步,直到
right
到达字符串S
的尽头
实际代码
public String minWindow(String s, String t) {
HashMap<Character,Integer> need = new HashMap();
HashMap<Character,Integer> window = new HashMap();
int left = 0,right = 0;
Character temp = null;
for(int i = 0; i < t.length(); i++){ //初始化need
need.put(t.charAt(i),need.getOrDefault(t.charAt(i),0) + 1);
}
int count = 0;//记录window中符合条件的字符数
int maxLen = Integer.MAX_VALUE;//记录最小字串的长度
int start = 0;
int end = 0;//记录最小字串的开始和结束
while(right < s.length()){
//取出字符
temp = s.charAt(right);
window.put(temp,window.getOrDefault(temp,0) + 1);
if(need.containsKey(temp)){
//在windw中添加,累积出现次数
//判断此时window中的字符是否完全包含了target
//如果right所指的字符在target和window中含有相同个数,count++
if(window.get(temp).compareTo(need.get(temp)) == 0){
count++;
}
}
right++;
while(count == need.size()){
//满足情况,需要更新结果
if((right - left) < maxLen){
start = left;
end = right;
maxLen = right - start;
}
//开始优化
char c = s.charAt(left);
if(need.containsKey(c)){ //如果当前字符包含target中,就更新window出现的值
window.put(c,window.getOrDefault(c,1) - 1);
if(window.get(c) < need.get(c)){
count--;
}
}
left++;
}
}
return maxLen == Integer.MAX_VALUE ? "" : s.substring(start,end);
}
总结难点,易错点
- maxLen 要初始化为 Integer.MAX_VALUE
- 只要是字符就要加入到window中,但是只有同时属于target的字符,才需要进行判断是否满足target中的个数,如果满足要进行count++;
- 优化时,通过count数减小来退出循环
- 进行比较时,要使用compareTo
(2)其它类似例题,思路都是一致的
- LeetCode 567 题 --- 字符串排列
- LeetCode 第 438 题 --- 找所有字母异位词
- LeetCode 第 3 题 --- 最长无重复子串
相信大家对双指针的使用应该都没啥问题了,最后几道题目有兴趣的可以自行去leetcode进行练习!