双指针技巧总结

参考:https://labuladong.gitee.io/algo/2/21/53/ -- labuladong 的算法小抄

快慢指针

主要解决链表中的问题,比如典型的判定链表中是否包含环

1、判定链表中是否含有环

力扣141. 环形链表

经典解法就是用两个指针,一个跑得快,一个跑得慢。如果不含有环,跑得快的那个指针最终会遇到 null,说明链表不含环;如果含有环,快指针最终会超慢指针一圈,和慢指针相遇,说明链表含有环。

boolean hasCycle(ListNode head) {
    ListNode fast, slow;
    fast = slow = head;
    while (fast != null && fast.next != null) {   
        fast = fast.next.next;
        slow = slow.next;
        
        if (fast == slow) return true;
    }
    return false;
}

注意 fast.next!=null, 因为fast要为next.next

方法2: 可以用hash表的方式,访问过的放在hash表里。

public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> seen = new HashSet<ListNode>();
        while (head != null) {
            if (!seen.add(head)) {
                return true;
            }
            head = head.next;
        }
        return false;
    }
}

Set<ListNode> seen = new HashSet<ListNode>();

力扣142-环形链表找节点

证明:

证明过程:b为环节点数,a为非环节点数。f为快指针步数,s为慢指针步数,快2慢1,f=2s,f=s+nb,n为圈数。快慢第一次相遇时,有2s=s+nb,推出s=nb(即多走的为b的n倍)。

又到环节的的步数 k = a+nb 一定成立。此时s已经在nb了,再走a步就是k点了,重置快指针/慢指针为head(都行的),此时快指针一步一步走,走a步,一定也会到k点,因此,此时快慢第二次相遇,一定是k点。

易错点:循环判断条件中不要进行fast == slow的判断,因为两者都是从head出发,就会达到退出循环的条件,应该把判断相等放在循环中 --可用do while

如何判断环的长度:找到头节点后,再走一遍

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast, slow;
        fast = slow = head;
        while(fast!=null && fast.next!=null){   //fast.next!=null && fast.next.next!=null   这里的条件写错了
            fast=fast.next.next; 
            slow=slow.next;
            if(fast==slow)
               break;
        }
        if(fast==null || fast.next==null){   //条件错了,不是fast==null || slow==null,要和上面一样
            return null;
        }

        fast=head;
        while(fast!=slow){   
            fast=fast.next;    //这里错了,不是fast.next.next;
            slow=slow.next;
        }
        return slow;
    }
}

三处错误

寻找链表的中点--力扣876

快指针到终点,慢指针到达中间。

注意:当链表的长度是奇数时,slow恰巧停在中点位置;如果长度是偶数,slow 最终的位置是中间偏右:

class Solution {
    public ListNode middleNode(ListNode head) {
      ListNode fast,slow;
      fast=slow=head;
      while(fast!=null && fast.next!=null){
          fast=fast.next.next;
          slow=slow.next;
      }
      return slow;
    }
}

寻找链表中点的一个重要作用是对链表进行归并排序

两种简单的方法:一个是放到数组里面,一个是遍历两次

力扣19--寻找链表的倒数第 n个元素

思想: 前一个指针先走n步,然后一起走,这样快的到了根节点慢的就是倒数n,注意此时要快节点走到null前一个停止,便于删除

注意点: 要注意只有一个节点的情况考虑删除第一个节点时的情形,直接返回head.next

    public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode fast, slow;
    fast = slow = head;

    while (n-- > 0) {
        fast = fast.next;
    }
    if (fast == null) {    //注意考虑特殊情况。
        // 如果此时快指针走到头了,
        // 说明倒数第 n 个节点就是第一个节点
        return head.next;
    }
   
    while (fast != null && fast.next != null) {    //注意这里是走不到末尾的,只能走到最后一个节点,这样slow的next就是真正的倒数第n个
        fast = fast.next;
        slow = slow.next;
    }
    // slow.next 就是倒数第 n 个节点,删除它
    slow.next = slow.next.next;
    return head;

技巧:可以使用dummy节点,该节点指向头节点,返回时也可以返回dummy.next

也可以用栈, 递归的方法也很巧妙,算长度时倒数第n个

左右指针

后者主要解决数组(或者字符串)中的问题,比如二分查找

1、二分查找,再具体看

2、力扣167-两数之和

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        
        int left=0;int right=numbers.length-1;    //错误,不用length(),不用加()
        while(left<right){
        if(numbers[left]+numbers[right]==target){
            return   new int[] {left+1,right+1};  //如何retrun 数组,技巧 ,注意要new,new int[] {left+1,right+1
        }
        if(numbers[left]+numbers[right]<target)
            {
                left++;
            }
         if(numbers[left]+numbers[right]>target)
            {
                right--;
            }
        }
        return null;
    }
}

两个地方出错

其他方法:二分查找

补充:

1、

给你一个array [1, 9, 134 , 10, 25 , 40, 4, 7],

给一个target,返回所有 A+B=target 的 (paire 对) 上面比如是target = 11 , 返回 [(1, 10), (4, 7)], 每个元素只能用一次 腾讯面试,167题的变形,同时说出时间复杂度和空间复杂度

感觉还是这个题的思路吧,唯一不同的是sum = target后不结束,而是双指针同时移动,继续遍历直到L,可以先排序?

java public static List<List<Integer>>
find(int[] array,int target){ 
    List<List<Integer>> ans=new LinkedList<>(); 
    HashMap<Integer,Integer> map=new HashMap<>(); 
    for(int i=0;i<array.length;i++){ 
        int diff=target- array[i]; 
        if(map.containsKey(diff)) { 
            Integer[] temp={i,map.get(diff)}; 
            ans.add(Arrays.asList(temp)); }  //数组转化为l
        
        map.put(array[i],i); } 
    
    return ans; } 

2、

稍微变形,与target差距最小的两数

3、反转数组--力扣344

简单题

void reverseString(char[] arr) {
    int left = 0;
    int right = arr.length - 1;
    while (left < right) {
        // 交换 arr[left] 和 arr[right]
        char temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;
        left++; right--;
    }
}

4、滑动窗口算法