双指针法:
主要有:
1 快慢指针:主要解决链表中的问题
2 左右指针:二分搜索法(见“二分搜索法总结”)、反转数组
3 滑动窗口:快慢指针在数组(字符串)上的应用,主要解决字符串匹配的问题(见“滑动窗口总结”)
例题1:环形链表
简单的方法有哈希表存储节点,但空间复杂度高。用快慢指针
/**
* 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(slow==fast){
return true;
}
}
return false;
}
}
例题2:环形链表 II
简单的方法有哈希表存储节点,但空间复杂度高。用快慢指针
/**
* 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) {
ListNode slow=head,fast=head;
boolean hasCycle=false;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(slow==fast){
hasCycle=true;
break;
}
}
if(hasCycle==false){
return null;
}
slow=head;
while(slow!=fast){
fast=fast.next;
slow=slow.next;
}
return slow;
}
}
例题3:链表中倒数第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) {
ListNode slow=head,fast=head;
for(int i=0;i<k;++i){
if(fast!=null){
fast=fast.next;
}else{
return null;
}
}
while(fast!=null){
fast=fast.next;
slow=slow.next;
}
return slow;
}
}
例题4:删除链表的倒数第 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 dummy=new ListNode(-1,head);
ListNode slow=dummy,fast=head;
for(int i=0;i<n;++i){
fast=fast.next;
}
while(fast!=null){
fast=fast.next;
slow=slow.next;
}
slow.next=slow.next.next;
return dummy.next;
}
}
例题4:相交链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode a=headA,b=headB;
while(true){
if(a==null&&b==null){
return null;
}
if(a==b){
return a;
}
if(a!=null&&b!=null){
a=a.next;
b=b.next;
}else if(a==null){
a=headB;
}else if(b==null){
b=headA;
}
}
}
}
例题5:调整数组顺序使奇数位于偶数前面
左右指针
class Solution {
public int[] exchange(int[] nums) {
int left=0,right=nums.length-1;
while(left<right){
while(left<nums.length&&nums[left]%2==1){
++left;
}
while(right>=0&&nums[right]%2==0){
--right;
}
if(left<right){
int temp=nums[left];
nums[left]=nums[right];
nums[right]=temp;
}
}
return nums;
}
}
例题6:两数之和 II - 输入有序数组
左右指针
class Solution {
public int[] twoSum(int[] numbers, int target) {
int left=0,right=numbers.length-1;
while(left<=right){
int sum=numbers[left]+numbers[right];
if(sum==target){
return new int[]{left+1,right+1};
}else if(sum>target){
--right;
}else if(sum<target){
++left;
}
}
return new int[2];
}
}
例题7:三数之和
与「两数之和」相似。
这里注意答案中不可以包含重复的三元组,所以去重要格外注意。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
//首先排序
Arrays.sort(nums);
List<List<Integer>> result=new ArrayList<>();
for(int p1=0;p1<nums.length-2;++p1){
if(nums[p1] > 0) break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
if(p1>0&&nums[p1]==nums[p1-1])continue; //p1去重
int p2=p1+1;
int p3=nums.length-1;
while(p2<p3){
int sum=nums[p1]+nums[p2]+nums[p3];
if(sum==0){
result.add(Arrays.asList(nums[p1],nums[p2],nums[p3]));
while(p2<p3&&nums[p2]==nums[p2+1])++p2; //p2去重
while(p2<p3&&nums[p3]==nums[p3-1])--p3; //p2去重
++p2;
--p3;
}else if(sum<0){
++p2;
}else if(sum>0){
--p3;
}
}
}
return result;
}
}
例题8:最接近的三数之和
与「两数之和」相似。
这里注意是最接近。
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int min=Integer.MAX_VALUE;
int result=-1;
for(int p1=0;p1<nums.length;++p1){
int p2=p1+1,p3=nums.length-1;
while(p2<p3){
int sum=nums[p1]+nums[p2]+nums[p3];
if(sum==target){
return target;
}else if(sum<target){
if(min>Math.abs(sum-target)){
min=Math.abs(sum-target);
result=sum;
}
++p2;
}else if(sum>target){
if(min>Math.abs(sum-target)){
min=Math.abs(sum-target);
result=sum;
}
--p3;
}
}
}
return result;
}
}
例题9:四数之和
与「三数之和」相似,解法也相似:排序 + 左右指针
使用两重循环分别枚举前两个数,然后在两重循环枚举到的数之后使用双指针枚举剩下的两个数。假设两重循环枚举到的前两个数分别位于下标 i 和 j,其中 i<j。初始时,左右指针分别指向下标 j+1和下标 n-1
还有四个剪枝操作
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> quadruplets = new ArrayList<List<Integer>>();
if (nums == null || nums.length < 4) {
return quadruplets;
}
Arrays.sort(nums);
int length = nums.length;
for (int i = 0; i < length - 3; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
break;
}
if ((long) nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target) {
continue;
}
for (int j = i + 1; j < length - 2; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) {
continue;
}
if ((long) nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {
break;
}
if ((long) nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target) {
continue;
}
int left = j + 1, right = length - 1;
while (left < right) {
int sum = nums[i] + nums[j] + nums[left] + nums[right];
if (sum == target) {
quadruplets.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
left++;
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
}
}
return quadruplets;
}
}
例题10:接雨水
左右指针
对于下标 i,下雨后水能到达的最大高度等于下标 i 两边的最大高度的最小值,下标 i 处能接的雨水量等于下标 i 处的水能到达的最大高度减去height[i]。
class Solution {
public int trap(int[] height) {
int result=0;
int left=0,right=height.length-1;
int maxLeft=0,maxRight=0;
while(left<=right){
maxLeft=Math.max(maxLeft,height[left]);
maxRight=Math.max(maxRight,height[right]);
if(maxLeft<maxRight){ //如果maxLeft<maxRight时,则left位置接的雨水就可以确定了
result+=maxLeft-height[left];
++left;
}else{
result+=maxRight-height[right];
--right;
}
}
return result;
}
}
例题11:盛最多水的容器
动最差的部分可能找到更好的结果,但是动另一边总会更差或者不变。而且过程中会记录所有结果,所以动最差的部分就算变差也没事。
class Solution {
public int maxArea(int[] height) {
int result=0;
int left=0,right=height.length-1;
while(left<right){
if((right-left)*Math.min(height[left],height[right])>result){
result=(right-left)*Math.min(height[left],height[right]);
}
if(height[left]<height[right]){
++left;
}else{
--right;
}
}
return result;
}
}
例题12:合并两个排序的链表
用双指针
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode p1=l1,p2=l2;
ListNode dummy=new ListNode(-1);
ListNode p=dummy;
while(p1!=null&&p2!=null){
if(p1.val<p2.val){
p.next=p1;
p1=p1.next;
}else{
p.next=p2;
p2=p2.next;
}
p=p.next;
}
if(p1!=null){
p.next=p1;
}
if(p2!=null){
p.next=p2;
}
return dummy.next;
}
}
例题13:合并K个升序链表
题解1:分而治之
时间复杂度:O(kn*log k)
空间复杂度:O(logk)
/**
* 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 mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0) return null; //这一行也是必须的
return merge(lists, 0, lists.length - 1);
}
public ListNode merge(ListNode[] lists,int left,int right){
if(left>=right){
return lists[left]; //这个返回内容要尤其记住
}
int mid=left+(right-left)/2;
ListNode p1=merge(lists,left,mid);
ListNode p2=merge(lists,mid+1,right);
return mergeTwoLists(p1,p2);
}
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode p1=l1,p2=l2;
ListNode dummy=new ListNode(-1);
ListNode p=dummy;
while(p1!=null&&p2!=null){
if(p1.val<p2.val){
p.next=p1;
p1=p1.next;
}else{
p.next=p2;
p2=p2.next;
}
p=p.next;
}
if(p1!=null){
p.next=p1;
}
if(p2!=null){
p.next=p2;
}
return dummy.next;
}
}
题解2:优先级队列
时间复杂度:O(kn*log k)
空间复杂度:O(k)
/**
* 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 mergeKLists(ListNode[] lists) {
Queue<ListNode> queue=new PriorityQueue<>((e1,e2)->e1.val-e2.val);
for(int i=0;i<lists.length;++i){
if(lists[i]!=null){
queue.offer(lists[i]);
}
}
ListNode dummy=new ListNode(-1);
ListNode p=dummy;
while(!queue.isEmpty()){
p.next=queue.poll();
p=p.next;
if(p.next!=null){
queue.offer(p.next);
}
}
return dummy.next;
}
}
例题14:最短无序连续子数组
解法1:排序+判断是否相同
class Solution {
public int findUnsortedSubarray(int[] nums) {
int[] temp=new int[nums.length];
for(int i=0;i<nums.length;++i){
temp[i]=nums[i];
}
Arrays.sort(temp);
int left=0,right=nums.length-1;
while(left<nums.length){
if(nums[left]==temp[left]){
++left;
}else{
break;
}
}
while(right>=0){
if(nums[right]==temp[right]){
--right;
}else{
break;
}
}
return right>left?right-left+1:0;
}
}
解法2:
性质理解+左右指针
大佬题解里面这个图不错
class Solution {
public int findUnsortedSubarray(int[] nums) {
//我就故意分开写,不把放在一个循环里
int maxn = nums[0], right = -1;
for (int i = 0; i < nums.length; i++) {
if (maxn > nums[i]) {
right = i;
} else {
maxn = nums[i];
}
}
//我就故意分开写,不把放在一个循环里
int minn = nums[nums.length-1], left = -1;
for (int i = 0; i < nums.length; i++) {
if (minn < nums[nums.length - i - 1]) {
left = nums.length - i - 1;
} else {
minn = nums[nums.length - i - 1];
}
}
return right == -1 ? 0 : right - left + 1;
}
}
例题15:颜色分类
解法1:记录个数
统计出数组中 0, 1, 2的个数,再根据它们的数量,重写整个数组
时间复杂度:O(n)。
循环两趟
空间复杂度:O(1)。
class Solution {
public void sortColors(int[] nums) {
int count0=0,count1=0,count2=0;
for(int i=0;i<nums.length;++i){
if(nums[i]==0){
++count0;
}else if(nums[i]==1){
++count1;
}else{
++count2;
}
}
for(int i=0;i<nums.length;++i){
if(i<count0){
nums[i]=0;
}else if(i<count0+count1){
nums[i]=1;
}else{
nums[i]=2;
}
}
}
}
解法2:双指针
统计出数组中 0, 1, 2的个数,再根据它们的数量,重写整个数组
时间复杂度:O(n)。
循环一趟
空间复杂度:O(1)。
class Solution {
public void sortColors(int[] nums) {
int n=nums.length;
int p1=0,p2=n-1;
for(int i=0;i<=p2;++i){
while(i<=p2&&nums[i]==2){ //i==2和i==0的顺序不能调换
int temp=nums[i];
nums[i]=nums[p2];
nums[p2]=temp;
--p2;
}
if(nums[i]==0){
int temp=nums[i];
nums[i]=nums[p1];
nums[p1]=temp;
++p1;
}
}
}
}
例题16:移动RED球,使其彼此相邻
一次移动只能交换相邻的两个球,求最小移动次数
import java.util.*;
public class T3 {
public static void main(String[] args) {
String S="WRRWWR";
int len = S.length();
// 核心代码:
List<Integer> reds=new ArrayList<>();
for(int i=0;i<len;++i){
if(S.charAt(i)=='R'){
reds.add(i);
}
}
int n=reds.size();
if(n==0){
System.out.println(0);
return;
}
//核心逻辑
int start_ptr = 0;
int end_ptr = n - 1;
int count = 0;
while(start_ptr < end_ptr){
count += (reds.get(end_ptr) - reds.get(start_ptr)) - (end_ptr - start_ptr);
start_ptr += 1;
end_ptr -= 1;
}
int result= count > 1000000000?-1:count;
System.out.println(result);
}
}