刷题论 03|双指针、滑动窗口、前缀和,专题总结

368 阅读1分钟

本文为视频的原稿,访问 B 站看视频,通过视频更能领会要义哦!

数组里的双指针

用暴力解法一定可解,双重循环得出结果。用双指针的方法,借助额外空间(一个变量),实现了降维优化。

左右指针,交替相向而行

两个指针,分别在数组的头和尾,都往中间移动,直到相遇。

题目特点:每个循环,仅看数组中的 2 个元素,双指针意在找到符合条件的 2 个元素,求出对应的结果。

典型例题:盛最多水的容器

image.png

// 暴力解法
public class Solution {
    public int maxArea(int[] height) {
        int len = height.length;
        if (len < 2) {
            return 0;
        }
        int res = 0;
        for (int i = 0; i < len - 1; i++) {
            for (int j = i + 1; j < len; j++) {
                res = Math.max(res, Math.min(height[i], height[j]) * (j - i));
            }
        }
        return res;
    }
}
// 双指针解法
public class Solution {
    public int maxArea(int[] height) {
        int len = height.length;
        if (len < 2) {
            return 0;
        }
        int left = 0;
        int right = len - 1;
        int res = 0;
        while (left < right) {
            int minHeight = Math.min(height[left], height[right]);
            res = Math.max(res, minHeight * (right - left));
            if (height[left] == minHeight) {
                left++;
            } else {
                right--;
            }
        }
        return res;
    }
}

模板

func template(nums[]int) {
    left, right := 0, len(nums)-1
    for left < right {
        if () {
                left++
        }
        if () {
                right--
        }
    }
}

同类型练习题

167. 两数之和 II - 输入有序数组

15. 三数之和

344. 反转字符串

345. 反转字符串中的元音字母

125. 验证回文串

🏆 5. 最长回文子串 推荐!

704. 二分查找

快慢指针,交替同向而行:滑动窗口

两个指针,都位于数组的头部,快指针走到尾部则循环结束,慢指针视条件移动。

题目特点:每个循环,看子区间是否满足某个条件,子区间是由双指针框起来,输出的是子区间的变形。

典型例题:最小覆盖子串

image.png

func minWindow(s string, t string) string {
    need, window := make(map[byte]int), make(map[byte]int)
    left, right := 0, 0
    match := 0
    res := ""
    for i := 0; i < len(t); i++ {
        need[t[i]]++
    }
    for right < len(s) {
// 1. 不满足输出条件,延长窗口
        r := s[right]
        right++
        // 对右侧指针所对应的字符,做处理
        if _, ok := need[r]; ok {
            window[r]++
            if window[r] == need[r] {
                    match++
            }
        }
// 2. 满足输出条件,缩短窗口
        for match == len(need) {
            // 更新匹配的最小子串长度
            if res == "" || right-left < len(res) {
                res = s[left:right]
            }
            l := s[left]
            left++
            if _, ok := need[l]; ok {
                if window[l] == need[l] {
                    match--
                }
                window[l]--
            }
        }
    }
    return res
}

模板

left, right := 0, 0
for right < len(s) {
    // 增大窗口范围
    window.add(s[right])
    right++;
    // 满足某个条件,需要缩小窗口范围
    for () {
        window.remove(s[left])
        left++;
    }
}

同类型练习题

前缀和

需要用到子区间和时,考虑借助一个数组,去存储前缀和,需要区别于双指针的用法。

image.png

func subarraySum(nums []int, k int) int {
    ans := 0
    sum := make([]int, len(nums)+1)
    sum[0] = 0
    for i := 0; i < len(nums); i++ {
            sum[i+1] = sum[i] + nums[i]
    }
    i := 0
    j := len(sum) - 1
    for i < j {
        if sum[j]-sum[i] < k {
            j--
        }
        if sum[j]-sum[i] == k {
            ans++
        }
        if sum[j]-sum[i] > k {
            i++
        }
    }
    return ans
}

链表里的双指针

快慢指针,快指针一次 2 步,慢指针一次 1 步。

典型例题:环形链表

image.png

func hasCycle(head *ListNode) bool {
// 快慢指针法,有环必套圈,无环快指针会到 null
// 了解即可,在实际开发中应用较少,可以用哈希表来存储,没必要检查环
    fast := head
    for (fast != nil && fast.Next != nil) {
        fast = fast.Next.Next
        head = head.Next
        if (fast == head) {
            return true
        }
    }
    return false
}

同类型练习题

19. 删除链表的倒数第 N 个结点

876. 链表的中间结点

142. 环形链表 II