双指针

双指针也是所谓滑动窗口算法。

一般用于求解具有某种特质的子数组、子字符串、链表查询的分隔问题,通过两个指针来标识窗口的边界(子数组、子字符串、链表标记)。

顾名思义,有2个指针,头指针和尾指针(窗口边界),结合while使用。二分也是利用双指针的思想。

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Title:NC41-最长无重复, 数组中最长连续无重复长度
 * Description:双指针、Set、Map
 * @author WZQ
 * @version 1.0.0
 * @date 2021/2/24
 */
public class NC41 {

    /**
     * @param arr int整型一维数组 the array
     * @return int整型
     */
    public int maxLength (int[] arr) {
        int length = arr.length;
        if (length < 2){
            return length;
        }
        // 双指针
        // j纪录尾部,遍历到尾部
        // i纪录头部,遇到重复的向后遍历删除
        int max = 0, i = 0, j = 0;
//        Map<Integer, Integer> map = new HashMap<>();
//        while (j < length){
//            //包含
//            if (map.containsKey(arr[j])){
//                //存在相同的元素
//                // 123412 1234 i=0 j=4,
//                i = map.get(arr[j])
//            }else {
//                //不包含
//                map.put(arr[j], j);
//            }
//            max = Math.max(max, j - i + 1);
//            j++;
//        }

        Set<Integer> set = new HashSet<>();
        while (j < length){
            //包含
            if (set.contains(arr[j])){
                //头指针, 删除到重复元素的下一个位置
                set.remove(arr[i]);
                i ++;
            }else {
                //不包含
                set.add(arr[j]);
                j ++;
            }
            max = Math.max(max, set.size());
        }
        return max;
    }

}
/**
 * Title:NC22-合并2个有序数组, 不能开辟额外的空间,结果存放在A数组中
 * Description:双指针
 * @author WZQ
 * @version 1.0.0
 * @date 2021/3/3
 */
public class NC22 {

    public static void merge(int A[], int m, int B[], int n) {
        //双指针,纪录2个集合确定合并的位置
        int a = 0, b = 0, temp = 0;
        while (a < (m+n) && b < n){
            //小于,A[a]位置不变
            while (a < (m+b) && A[a] < B[b]){
                a ++;
            }
            //等于,大于,A[a]后未判断的全体后退
            if (m + b - a >= 0) System.arraycopy(A, a, A, a + 1, m + b - a);
//            for (int i = m+b-1; i>=a; i--) {
//                A[i+1] = A[i];
//            }
            //B[b]放到A[a]位置
            A[a] = B[b];
            a ++;
            b ++;
        }
    }

    /**
     * 1 3 7
     * 1 5 6
     * 1 1 3 5 6 7
     *
     * @param args
     */
    public static void main(String[] args) {
        int[] arr1 = {1, 3, 7, 0, 0, 0};
        int[] arr2 = {1, 5, 6};
        merge(arr1, arr1.length/2, arr2, arr2.length);
    }

}
import java.util.HashMap;
import java.util.Map;

/**
 * Title:leetcode-76. 最小覆盖子串
 * Description:滑动窗口+Hash
 *
 * 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。
 * 如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
 * 
 * @author WZQ
 * @version 1.0.0
 * @date 2021/3/26
 */
public class Main76 {

    public String minWindow(String s, String t) {
        String res = "";
        // 窗口,统计字符的方式
        Map<Character, Integer> window = new HashMap<>();
        // 子串t字符统计
        Map<Character, Integer> chars = new HashMap<>();
        int tLen = t.length();
        int sLen = s.length();
        for (int i = 0; i < tLen; i++) {
            chars.put(t.charAt(i), chars.getOrDefault(t.charAt(i), 0) + 1);
        }

        // 双指针, left到right窗口长度 > tLen开始check
        int left = 0, right = 0, min = Integer.MAX_VALUE;
        while (right < sLen) {
            char ch = s.charAt(right);
            // 纪录到窗口
            window.put(ch, window.getOrDefault(ch, 0) + 1);
            // check成功,则left向后,压缩窗口,直到check失败,纪录min,right接着向后,依次类推
            while (check(window, chars)) {
                if ((right - left + 1) < min) {
                    min = right - left + 1;
                    res = s.substring(left, right + 1);
                }
                // left向后压缩窗口
                char ch2 = s.charAt(left);
                window.put(ch2, window.get(ch2) - 1);
                if (window.get(ch2) == 0) {
                    window.remove(ch2);
                }
                left++;
            }
            // check失败,则right继续向后,直到符合
            right++;
        }
        
        return res;
    }

    // 当前窗口是否符合题目条件
    public boolean check(Map<Character, Integer> window, Map<Character, Integer> chars) {
        for (char ch : chars.keySet()) {
            // window存在所有子串字符
            if (!window.containsKey(ch) || (window.get(ch) < chars.get(ch))) {
                return false;
            }
        }
        return true;
    }

}

快慢指针

快慢指针,一般用于解决链表问题,快指针每次走n步或者提前走n步,而慢指针还是一步一步原地走

/**
 * Title:NC53-删除链表倒数第n个值,并返回头结点
 * Description: 要求O(n)
 * @author WZQ
 * @version 1.0.0
 * @date 2021/2/27
 */
public class NC53 {

    /**
     * @param head ListNode类
     * @param n int整型
     * @return ListNode类
     */
    public ListNode removeNthFromEnd (ListNode head, int n) {
        if(head == null){
            return head;
        }

        // 双指针,快慢指针
        // 采用两个指针,对前指针,使其先走出N步,随后两个指针同时前进,
        // 当前指针到达链表尾部时,后指针到达倒数第N个节点的位置。
        ListNode slow = head;
        ListNode fast = head;

        // 先走n步
        for(int i = 0; i <n; i++){
            fast = fast.next;
        }

        // //如果n的值等于链表的长度,直接返回去掉头结点的链表
        // 删除的节点是头节点的情况
        if(fast == null) {
            return head.next;
        }

        // 快指针走到尾部,慢指针位置删除
        while(fast.next != null){
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;

        return head;
    }

}
import java.util.List;

/**
 * Title:NC4-寻找链表闭环
 * Description:
 * @author WZQ
 * @version 1.0.0
 * @date 2021/1/7
 */
public class NC4 {

    /**
     * 思路1:hash map/set
     * 开一个哈希表,标记访问过的节点,
     * 如果重复访问了某一节点,说明有环。
     * 需要额外O(n)的空间复杂度。
     */


    /**
     * 思路2:快慢指针
     * 用一快一慢指针,开始两个指针都指向链表头部。
     * 慢指针每次向前走一步,快指针每次向前走两步。
     * 如果有环,则两个指针最终一定会相遇。
     * 这种方法无须额外的空间。
     * @param head
     * @return
     */
    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null){
            return false;
        }
        ListNode s = head;
        ListNode q = head;
        while (q != null && q.next != null){
            s = s.next;
            q = q.next.next;
            if (s == q){
                return true;
            }
        }
        return false;
    }

    //Definition for singly-linked list.
    class ListNode {
        int val;
        ListNode next;

        ListNode(int x) {
            val = x;
            next = null;
        }
    }

}

链表闭环升级版,寻找环的入口节点

/**
 * Title:NC-3,链表的入口节点,环的入口节点
 * Description:双指针
 *
 * @author WZQ
 * @version 1.0.0
 * @date 2021/3/5
 */
public class NC3 {

    /**
     * 1)首先判断是否有环,有环时,返回相遇的节点,无环,返回null
     * 2)有环的情况下, 求链表的入环节点
     * fast再次从头出发,每次走一步,
     * slow从相遇点出发,每次走一步,
     * 再次相遇即为环入口点。
     */
    public ListNode detectCycle(ListNode head) {
        if (head == null || head.next == null){
            return null;
        }

        ListNode meetNode = meetingNode(head);
        //说明无环
        if (meetNode == null) {
            return null;
        }

        //fast再次从头出发,每次走一步,
        //slow从相遇点出发,每次走一步,
        ListNode fast = head;
        ListNode slow = meetNode;
        //再次相遇即为环入口点
        while (slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }

        return slow;
    }

    //寻找相遇节点,如果无环,返回null
    public ListNode meetingNode(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            // 快指针走2步
            slow = slow.next;
            fast = fast.next.next;
            // 相遇点
            if (slow == fast) {
                return slow;
            }
        }
        return null;
    }

    //节点
    class ListNode {
        int val;
        ListNode next;
        ListNode(int x) {
            val = x;
            next = null;
        }
    }

}