算法--双指针

 

N数之和/差

Array.sort(xxx);

while(终止条件 head<tail)

  1. num[head]+num[tail]<target head++
  2. num[head]+num[tail]>target tail--
  3. num[head]+num[tail]==target head++;tail-- 要找到所有满足感条件的组合就不返回继续移动指针

使用对撞的双指针,一个指向首一个指向尾,不断向中间逼近,先把数组排好序,复杂度O(nlogn),再根据和目标值的关系移动首尾指针,在一个for循环下完成两个for循环的工作。对于三数之和使用双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。

1.两数之和

给定一个整数数组 nums, 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标

若数组无序且返回下标时不能用双指针,因为双指针需要排序会打乱下标。若给出有序数组则可用双指针法,复杂度为O(Nlogn)(排序复杂度nlogn 循环n)

Hashmap方法
Map<Integer,Integer> map=new HashMap<Integer,Integer>();
for(int i=0;i<numbers.length;i++)
        {
            if(map.containsKey(target-numbers[i]))
            {
                return new int[]{map.get(target-numbers[i])+1,i+1};
            }
            map.put(numbers[i],i);
        }

15.三数之和

一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

 


//与两数之和相比,target为nums[first]。多一个for循环遍历nums[first]
 for(first=0;first<nums.length;first++)  
        {
            if(first!=0&&nums[first]==nums[first-1])continue; first不重复
            second=first+1;third=nums.length-1;
            while(second<third)
            {
                sum=nums[second]+nums[third];
                if(sum==-nums[first])
                {
                    List<Integer> tmp=new ArrayList<Integer>();
                    tmp.add(nums[first]);tmp.add(nums[second]);tmp.add(nums[third]);
                    ans.add(tmp);
                    while(second<third&&nums[second]==nums[second+1])//本题的坑在这儿 在循环内部改变循环变量后要及时判断是否还满足循环条件。也要保证second不重复
                        second++;
                    while(second<third&&nums[third-1]==nums[third])//third不重复
                        third--;
                    second++;
                    third--;
                }
                else if(sum<-nums[first])
                {
                    second++;
                }
                else if(sum>-nums[first])
                {
                    third--;
                }
                
            }
        }

16. 最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

 while(second<third)
            {
                sum 三数和 tmp:与target差距 minDistance:最小差距
                int sum=nums[second]+nums[third]+nums[first];
                int tmp=Math.abs(sum-target); //
                if(tmp<=minDistance) //对遍历到的每个组合判断其是不是符合题目要求
                {
                    result=sum;  
                    minDistance=tmp;
                }
                if(sum<target)    //双指针定义数组的可选范围 不断夹逼target    
                    second++;
                else if(sum>target)
                    third--;
                else
                    return result;
                
            }
return result;

面试题 16.06. 最小差

给定两个整数数组ab,计算具有最小差绝对值的一对数值(每个数组中取一个值),并返回该对数值的差

        Arrays.sort(a);
        Arrays.sort(b);
        int pa=0,pb=0;
        long minDiff=Long.MAX_VALUE;
        while(pa<a.length&&pb<b.length)
        {
            long diff=Math.abs((long)a[pa]-(long)b[pb]);
            if(diff<minDiff)
            {
                minDiff=diff;
            }
            if(a[pa]<b[pb])  //为了使两个数尽可能接近,两个指针你追我赶,这种逼近有一种贪心思想
                pa++;
            else if(a[pa]>b[pb])
                pb++;
            else
                return (int)minDiff;
        }
        return (int)minDiff;
    }

链表

链表类型的双指针通常是快慢指针

剑指 Offer 24. 反转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

å¾ç

  ListNode pre=null;
        ListNode cur=head;
        while(cur!=null) 
        {
            ListNode tmp=cur.next; //这些变量的修改是循环的
            cur.next=pre;
            pre=cur;
            cur=tmp;
        }
        return pre;

41. 环形链表

给定一个链表,判断链表中是否有环。

142. 环形链表 II

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

设入环前长度为a,环长度为b。 slow每次走一步,fast每次走两步。fast走过的路径长为f,slow走过的长度为s。则第一次相遇时 f=s+nb(fast比slow多走n圈)=2s ----> s=nb.

入环点位于a+nb的位置,所以从第一次相遇的位置开始再走a步即可到入环点,但a是未知的,让另一个指针从头开始走,两指针速度一致,各走a步后在入环点相遇。

 public ListNode detectCycle(ListNode head) {
        ListNode slow=head,fast=head;
        do //这个题目的坑在slow fast必须在同一起点出发,因此用do-while结构
        {
            if(fast==null||fast.next==null)return null;
            slow=slow.next;
            fast=fast.next.next;
        }while(slow!=fast);
        fast=head;
        while(slow!=fast) //从第一次相遇的位置开始再走a步即可到入环点 a未知可借助双指针
        {
            slow=slow.next;
            fast=fast.next;
        }
        return slow;
    }

指针a从链表headA开始走,走到头就再重新从headB开始走 。指针b从链表headB开始走,走到头就再重新从headA开始走,这样两指针会在交点处第一次相遇。如果两链表不相交,则pa,pb同时指向两个链表末尾的null;

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode pa=headA,pb=headB;
         while(pa!=pb)  
        {
            pa=pa==null?headB:pa.next; //这里的坑是不能判断pa.next==null,对于不相交两链表,pa,pb要同时指向null才能退出循环。
            pb=pb==null?headA:pb.next;
        }
        return pa;
    }

class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode half=divide(head);
        ListNode first=head,second=reverse(half);
        while(first!=null)  
        {
            if(first.val!=second.val)return false;
            first=first.next;
            second=second.next;
        }
       return true;
    }
    //将链表分为前后两部分,返回后半部分链表的起点,快指针一次走两步,慢指针走一步。
    //节点数目为奇数后半部分节点比前半部分多一个正中间那个。
    public ListNode divide(ListNode head)
    {
        ListNode slow=head,fast=head,pre=head;
        while(fast!=null&&fast.next!=null)
        {
            pre=slow;  
            slow=slow.next;
            fast=fast.next.next;
        }
        pre.next=null;   //前半部分末尾节点指向null,这样判断回文时循环条件更简单。
        return slow;
    }
    //反转后半部分链表
    public ListNode reverse(ListNode head)
    {
        ListNode pre=null,cur=head;
        while(cur!=null)
        {
            ListNode tmp=cur.next;
            cur.next=pre;
            pre=cur;
            cur=tmp;
        }
        return pre;
    }
}

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

如果要删除头节点,则需要用到哑节点(新建一个伪头节点)。

只有当cur不是重复节点才改变pre,因为我们不希望如cur由3变4时,pre由2变3,而是希望pre保持2。否则只改变pre.next;

    public ListNode deleteDuplicates(ListNode head) {
        ListNode pHead = new ListNode(0);
        pHead.next=head;
        ListNode pre=pHead,cur=pHead.next;
        while(cur!=null){
            if(cur.next!=null&&cur.val==cur.next.val){
                while(cur.next!=null&&cur.val==cur.next.val){
                    cur=cur.next; 
                }
                cur=cur.next;
                pre.next=cur;
            }
            else{
            pre=pre.next;
            cur=cur.next;}
        }
        return pHead.next;

    }

保留一个重复节点

 public ListNode deleteDuplicates(ListNode head) {
        ListNode pHead = new ListNode(0);
        pHead.next=head;
        ListNode pre=pHead,cur=pHead.next;
        while(cur!=null){
            if(cur.next!=null&&cur.val==cur.next.val){
                while(cur.next!=null&&cur.val==cur.next.val){
                    cur=cur.next; 
                }
                pre.next=cur;//仅在这里有变化
                cur=cur.next;
                pre=pre.next;
            }
            else{
            pre=pre.next;
            cur=cur.next;}
        }
        return pHead.next;
    }

23. 合并K个升序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]

方法一:将合并k个链表转化为两两顺序合并。设k个链表中最长长度为n,合并两个链表O2n,i个Oin,n+2n+...kn=O(k^2*n)

方法二:分治合并。递归从下到上 ,第一次合并k/2组链表,每组合并两个链表复杂度2n 总复杂度为nk 第二次合并k/4组链表,每组4个复杂度4n,总复杂度nk,.......第log2k-1次合并两组,每组k/2个。 复杂度为O(nk*logk)

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists.length==0)return null;
        ListNode dum=new ListNode((int)Math.pow(-10,5));
        ListNode result=dum;
        for(int i=0;i<lists.length;i++)
        {
            result=mergeTwoLists(result,lists[i]);
        }
        return dum.next;

    }
     分治合并
     public ListNode mergeKLists(ListNode[] lists) {
        if(lists.length==0)return null;
        return partitionMerge(lists,0,lists.length-1);

    }
    public ListNode partitionMerge(ListNode[] lists,int l,int r)
    {
        if(l==r)return lists[l];
        else if(l>r)return null;
        else
        {
            int mid =(l+r)>>>1; //坑 l+(r-l)/2溢出错误
            return mergeTwoLists(partitionMerge(lists,l,mid),partitionMerge(lists,mid+1,r));
        }
    }
    public ListNode mergeTwoLists(ListNode head1,ListNode head2)
    {
        ListNode dum=new ListNode(0);
        ListNode l1=head1,l2=head2,cur=dum;
        while(l1!=null&&l2!=null)
        {
            if(l1.val<=l2.val)
            {
                cur.next=l1;
                l1=l1.next;
                cur=cur.next;
            }
            else
            {
                cur.next=l2;
                l2=l2.next;
                cur=cur.next;
            }
        }
        if(l1!=null)
        {
            cur.next=l1;
        }
        else
            cur.next=l2;
        return dum.next;
    }
}

字符串

344. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。

首尾双指针交换内容,逐步向中间移动
public void reverseString(char[] s) {
        int head=0,tail=s.length-1;
        while(head<tail)
        {
            char tmp=s[head];
            s[head]=s[tail];
            s[tail]=tmp;
            head++;
            tail--;
        }
    }

27. 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并「原地」修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。

public int removeElement(int[] nums, int val) {
        int fast=0,slow=0;
        //fast作用时遍历整个原本的数组,slow表示修改后的数组该位置是什么元素。
        //所以若fast不是val就把fast移到slow处,fast是val就跳过这个元素
        for(;fast<nums.length;fast++)
        {
            if(nums[fast]==val)
            {
                continue;
            }
            nums[slow++]=nums[fast];
        }
        return slow;
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值