双指针算法
1.快慢指针
快慢指针也是双指针,但是两个指针从同一侧开始遍历数组,将这两个指针分别定义为快指针(fast)和慢指针(slow),两个指针以不同的策略移动,直到两个指针的值相等(或其他特殊条件)为止,如fast每次增长两个,slow每次增长一个。
案例
LeetCode876. 链表的中间结点
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL
移动策略:快指针移动速度为慢指针两倍,当快指针移动完毕时,慢指针移动到的位置即为中间位置。
/** * 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; } }
LeetCode19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
移动策略:快指针先走n步,快慢指针一起走,当快指针走到末尾时,慢指针走到进行删除操作位置。(注意:当快指针走完n步,需判断此时是否走到尽头)
/** * 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 removeNthFromEnd(ListNode head, int n) { ListNode fast = head, slow = head; for(int i = 0; i < n; i++) { fast = fast.next; } if(fast == null){ return head.next; } while (fast.next != null){ slow = slow.next; fast = fast.next; } slow.next = slow.next.next; return head; } }
2.左右指针
左右指针是指在有序数组中,将指向最左侧的索引定义为左指针(left),最右侧的定义为右指针(right),然后从两头向中间进行数组遍历。
伪代码
function fn (list) { var left = 0; var right = list.length - 1; //遍历数组 while (left <= right) { left++; // 一些条件判断 和处理 ... ... right--; } }
案例
LeetCode704.二分查找 (具体二分查找算法https://www.cnblogs.com/kyoner/p/11080078.html)
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
class Solution { public int search(int[] nums, int target) { int low = 0, high = nums.length - 1; while (low <= high) { int mid = (high - low) / 2 + low; //防止溢出 int num = nums[mid]; if (num == target) { return mid; } else if (num > target) { high = mid - 1; } else { low = mid + 1; } } return -1; } }
LeetCode344. 反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
输入:s = ["h","e","l","l","o"] 输出:["o","l","l","e","h"]
class Solution { public void reverseString(char[] s) { int left = 0, right = s.length - 1; while(left <= right){ char temp; temp = s[left]; s[left] = s[right]; s[right] = temp; left++; right--; } } }
3.滑动窗口
基本概念
滑动窗口是一种基于双指针的一种思想,两个指针指向的元素之间形成一个窗口。
分类:窗口有两类,一种是固定大小类的窗口,一类是大小动态变化的窗口。
应用:什么情况可以用滑动窗口来解决实际问题呢?
- 一般给出的数据结构是数组或者字符串
- 求取某个子串或者子序列最长最短等最值问题或者求某个目标值时
- 该问题本身可以通过暴力求解
窗口的形成
在具体使用之前,我们知道窗口实际是两个指针之间形成的区域,那关键就是这两个指针是如何移动的。
- 初始时,左右指针left,right都指向第0个元素,窗口为[left,right),注意这里是左闭右开,因此初始窗口[0,0)区间没有元素,符合我们的初始定义
- 开始循环遍历整个数组元素,判断当前right指针是否超过整个数组的长度,是退出循环,否则执行第3步
- 然后right指针开始向右移动一个长度,并更新窗口内的区间数据
- 当窗口区间的数据满足我们的要求时,右指针right就保持不变,左指针left开始移动,直到移动到一个不再满足要求的区间时,left不再移动位置
- 执行第2步
模板
int left = 0,right =0; while(right指针未越界){ char ch = arr[right++]; //右指针移动,更新窗口 ... //窗口数据满足条件 对于固定窗口而言,就是窗口的大小>=固定值;对于动态窗口,就是从left出发,窗口不断扩充,第一次满足题意的位置 while(窗口数据满足条件){ //记录或者更新全局数据 ... //右指针不动,左指针开始移动一位 char tmp = arr[left++]; //左指针移动,窗口缩小,更新窗口数据 ... } //返回结果 ... }
案例
LeetCode 3. 无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是"abc",所以其长度为 3。
思路:

class Solution { public int lengthOfLongestSubstring(String s) { int length = s.length(); if (length <= 1) //长度小于1,无重复字符的最长字串就是自己本身 return length; int maxlength = 1;
//滑动窗口 int left = 0, right = 0; HashSet<Character> window = new HashSet<Character>(); //存储窗口元素 while (right < length){ char rightChar = s.charAt(right); while (window.contains(rightChar)){ window.remove(s.charAt(left)); left++; } maxlength = Math.max(maxlength, right - left + 1); window.add(rightChar); right++; } return maxlength; } }

浙公网安备 33010602011771号