算法 双指针技巧

双指针

双指针用于处理数组和链表等线性结构。同时用2个指针遍历有序的数据结构,减少时间复杂度。

leetcode167 两数之和

分析

由于数组是有序的,因此两个指针,一个从前向后遍历,一个从后往前遍历,可以在O(n)时间复杂度完成查询。
假设结果为[i, j]。那么左指针遍历到i时,如果右指针大于j,那么和偏大,右指针左移,不会出现左指针右移的情况。直到右指针走到j,返回结果。右指针遍历到j也是同理,因此不会出现左指针大于i,右指针小于j的情况。

题解

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int first = 0; // 第一个指针
        int last = numbers.length - 1;  // 第二个指针
        while (first < last) {
            int sum = numbers[first] + numbers[last];
            if (sum == target) {
                return new int[]{first + 1, last + 1};
            } else if (sum < target) {
                first++;
            } else {
                last--;
            }
        }
        return new int[]{-1, -1};
    }
}

leetcode88 合并两个有序数组

分析

2个有序数组,每个数组用一个指针遍历。因为合并结果存在nums1数组,而nums1数组的前半部分有值,后半部分是空的。所以与正向双指针相比,逆向双指针更快。

题解

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int tail = m + n - 1;
        int i = m - 1; // 第一个指针
        int j = n - 1; // 第二个指针
        int cur;
        while (i >= 0 || j >= 0) {
            if (i < 0) {
                cur = nums2[j];
                j--;
            } else if (j < 0) {
                cur = nums1[i];
                i--;
            } else if (nums1[i] > nums2[j]) {
                cur = nums1[i];
                i--;
            } else {
                cur = nums2[j];
                j--;
            }
            nums1[tail] = cur;
            tail--;
        }
    }
}

leetcode142 环形链表

分析

在这里插入图片描述
假设slow指针和fast指针均从head节点出发,每次slow移动1个节点,fast移动2个节点。它们相遇时的位置如图。a表示头节点到入环点的距离,b表示入环点到相遇点的距离,c表示相遇点到入环点的距离。
slow = a + b, fast = a + n(b + c) + b
其中n表示fast节点在环中多走的次数。
fast = 2 * slow,则
a + n(b + c) + b = 2(a + b),变换得到
(n - 1)(b + c) + c = a,这个等式意味着,分别从相遇点出发和从头结点出发的两个节点终会在入环点相遇。

非公式分析

现在fast和slow在相遇点相遇,slow走过的距离是x。此时再来一个节点ptrslow的速度从头结点走到相遇点,走过的距离也是x。那么slow在这期间走过的距离是多少?是2x,恰好是之前fast走过的距离。ptrslow相遇的位置恰好是slowfast相遇的位置。
由于slowptr在环里相遇,它们速度又相同,因此它们在环里的每个位置都相遇,自然包括入环点。

题解

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next; // 快指针
            slow = slow.next;  // 慢指针
            if (slow == fast) {
                fast = head; // 不浪费指针,fast表示ptr
                while (fast != slow) {
                    fast = fast.next;
                    slow = slow.next;
                }
                return fast;
            }
        }
        return null;
    }
}

leetcode76 最小覆盖子串

分析

左右指针在同一个字符数组上,分别表示子串的左右边界。由于要求最小子串,因此先用右指针保证覆盖子串内容,再停止右指针并用左指针压缩子串范围。

题解

    public static String minWindow(String s, String t) {
        char[] sArray = s.toCharArray();
        char[] tArray = t.toCharArray();

        int[] cnt = new int[128];
        int count = 0;
        for (char c : tArray) {
            cnt[c]++;
            count++;
        }

        int start = -1;
        int end = sArray.length;
        int l = 0;
        for (int r = 0; r < sArray.length; r++) {
            if (--cnt[sArray[r]] >= 0) {
                count--;
            }
            while (count == 0) {
                if (++cnt[sArray[l]] > 0) {
                    if (r - l < end - start) {
                        end = r;
                        start = l;
                    }
                    count++;
                }
                l++;
            }
        }
        return start == -1 ? "" : s.substring(start, end + 1);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值