【面试算法题总结07】双指针法

双指针法:

主要有:
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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值