LeetCode 560和为 K 的子数组(有负数、一次遍历前缀和)、LeetCode 438找到字符串中所有字母异位词(优化滑动窗口)、LeetCode 141环形链表I(快慢指针)、142II

本文介绍了如何利用快慢指针法解决LeetCode上的两道经典链表问题:141环形链表I和142环形链表II,以及560和为K的连续子数组问题。通过一次遍历,巧妙地找到链表中的环以及数组中和为K的连续子数组,实现了高效的时间复杂度。
摘要由CSDN通过智能技术生成

Top1:LeetCode 560和为 K 的连续子数组(有负数、一次遍历前缀和)

题目描述:
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的连续子数组的个数 。

示例 1:
输入:nums = [1,1,1], k = 2
输出:2
解释:连续子数组[1, 1]和[1, 1]

示例 2:
输入:nums = [1,2,3], k = 3
输出:2
解释:连续子数组[1, 2]和[3]

一、一次遍历,pre为[0…i]的和,如果pre[i]-pre[j-1]=k,通过递推可得,那么[j..i] 这个子数组和为 k 这个条件我们可以转化为:通过hashmap存在pre - k可知,一定存在包含i的子区间和为k的连续子数组

!!!map的key存pre,value存pre出现的次数。由于我们是先计算pre+=nums[i],所以要先map.put(0,1)【如果value=2,!!!说明前面有两次和为pre的,每一次我们都可以往前接上插值为k的连续子数组

如下:假如我们把4换成7,那么下一次会有两个满足条件的子数组。
map.put(pre, map.getOrDefault(pre, 0) + 1);

!!!说明前面有两次和为14,每一次我们都可以接上插值为k=7
!!!说明前面有两次和为14,每一次我们都可以接上插值为k=7
!!!说明前面有两次和为14,每一次我们都可以接上插值为k=7
在这里插入图片描述

在这里插入图片描述

  • 时间复杂度:O(n)。其中n为数组长度,我们是一次遍历所以为O(n)
  • 空间复杂度:O(n)。其中 n 为数组的长度。哈希表在最坏情况下可能有 n 个不同的键值,因此需要 O(n) 的空间复杂度。

可通过完整代码:

public int subarraySum(int[] nums, int k) {
    int count = 0, pre = 0;
    HashMap<Integer, Integer> map = new HashMap<>();
    map.put(0, 1);   // 定义(0,1)找第一个符合条件的带有i的连续子数组
    for (int i = 0; i < nums.length; i++) {
        pre += nums[i];
        if (map.containsKey(pre - k)) {
            count += map.get(pre - k);
        }
        map.put(pre, map.getOrDefault(pre, 0) + 1);
    }
    return count;
}

在这里插入图片描述

Top2:LeetCode 438找到字符串中所有字母异位词(differ优化滑动窗口)

LeetCode连接的方法一容易看懂,但是由于Arrays.equals(sCount, pCount)时间复杂度为p.length,所以不如方法二

题目描述:
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

示例 1:
输入: s = “cbaebabacd”, p = “abc”
输出: [0,6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的异位词。

示例 2:
输入: s = “abab”, p = “ab”
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的异位词。

题解链接:https://leetcode.cn/problems/find-all-anagrams-in-a-string/solution/zhao-dao-zi-fu-chuan-zhong-suo-you-zi-mu-xzin/

一、判断滑动窗口中每种字母的数量与字符串 p 中每种字母的数量是否相同时,只需要判断 differ 是否为零即可。【如果不好写循环条件,可以试着长度为4的数组分为2、2

数组定义++count[s.charAt(i) - 'a']

每次移动判断头和尾变化的differ即可,例:如果i位置为1,那么窗口右移之后正好变成0,这时differ就要–

if (count[s.charAt(i) - 'a'] == 1) --differ;   // 如果0位置 此时 是1,那么窗口右移之后正好变成0,就--differ
            else if (count[s.charAt(i) - 'a'] == 0) ++differ;   // 如果此时为0,(移动后变为1),++differ
            --count[s.charAt(i) - 'a'];

            if (count[s.charAt(i + pLen) - 'a'] == -1) {
                --differ;
            } else if (count[s.charAt(i + pLen) - 'a'] == 0) {
                ++differ;
            }
            ++count[s.charAt(i + pLen) - 'a'];

            if (differ == 0) ans.add(i + 1);
  • 时间复杂度:O(n+m+Σ)。其中Σ 为所有可能的字符数。我们需要 O(m)O(m) 来统计字符串 pp 中每种字母的数量;需要 O(m) 来初始化滑动窗口;需要 O(Σ) 来初始化differ;需要 O(n−m) 来滑动窗口并判断窗口内每种字母的数量是否与字符串 p 中每种字母的数量相同,每次判断需要 O(1) 。因为 s 和 p 仅包含小写字母,所以 Σ=26。
  • 空间复杂度:O(Σ)。只需要一个数组,用于存储滑动窗口和字符串 p 中每种字母数量的差。

可通过完整代码:

public List<Integer> findAnagrams2(String s, String p) {
    int sLen = s.length(), pLen = p.length();
    if (sLen < pLen) return new ArrayList<>();
    List<Integer> ans = new ArrayList<>();
    int[] count = new int[26];
    for (int i = 0; i < pLen; i++) {
        ++count[s.charAt(i) - 'a'];   // s
        --count[p.charAt(i) - 'a'];   // p
    }
    int differ = 0;
    for (int j = 0; j < 26; j++) {
        if (count[j] != 0) differ++;
    }
    if (differ == 0) ans.add(0);
    for (int i = 0; i < sLen - pLen; i++) {   // 每次移动判断头和尾变化的differ即可
        if (count[s.charAt(i) - 'a'] == 1) --differ;   // 如果0位置 此时 是1,那么窗口右移之后正好变成0,就--differ
        else if (count[s.charAt(i) - 'a'] == 0) ++differ;   // 如果此时为0,(移动后变为1),++differ
        --count[s.charAt(i) - 'a'];

        if (count[s.charAt(i + pLen) - 'a'] == -1) {
            --differ;
        } else if (count[s.charAt(i + pLen) - 'a'] == 0) {
            ++differ;
        }
        ++count[s.charAt(i + pLen) - 'a'];

        if (differ == 0) ans.add(i + 1);
    }
    return ans;
}

在这里插入图片描述

Top3:LeetCode 141环形链表I(快慢指针)、142II

LeetCode 141环形链表I(快慢指针)

题目描述:
给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例1:
在这里插入图片描述

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例2:
在这里插入图片描述

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例3:
在这里插入图片描述

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

一、为什么我们要规定初始时慢指针在位置 head,快指针在位置 head.next,而不是两个指针都在位置 head(即与「乌龟」和「兔子」中的叙述相同)?

观察下面的代码,我们使用的是 while 循环,循环条件先于循环体。由于循环条件一定是判断快慢指针是否重合,如果我们将两个指针初始都置于 head,那么 while 循环就不会执行。

  • 时间复杂度:O(N)。其中 N 是链表中的节点数。极限情况:当快慢在起点也是圈内的时候同时出发,最多移动N追上,此时慢指针走1圈,快指针正好走两圈。如果圈很小,会很快追上
  • 空间复杂度:O(1)。我们只使用了两个指针的额外空间。

可通过完整代码:

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

在这里插入图片描述

LeetCode 141环形链表II(公式,快慢指针)

题目描述:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

示例1:
在这里插入图片描述

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

示例2:
在这里插入图片描述

输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

示例3:
在这里插入图片描述

输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。

一、如果圈很小,可能快指针已经走了n圈了,虽然不知道n-1是多少圈,但是只要继续走肯定会在入圈处第一个点碰到

在这里插入图片描述

  • 时间复杂度:O(N)。其中 N 为链表中节点的数目。在最初判断快慢指针是否相遇时,slow 指针走过的距离不会超过链表的总长度;随后寻找入环点时,走过的距离也不会超过链表的总长度。因此,总的执行时间为 O(N)+O(N)=O(N)。
  • 空间复杂度:O(1)。我们只使用了 slow,fast,ptr 三个指针。

可通过完整代码:

public ListNode detectCycle(ListNode head) {
    if (head == null) return null;
    ListNode slow = head, fast = head;
    while (fast != null) {
        slow = slow.next;
        if (fast.next != null) fast = fast.next.next;
        else return null;
        if (fast == slow) {
            ListNode ptr = head;
            while (ptr != slow) {   // 此时圈可能很小,我也不知道n-1是多少圈,但是只要走,总会走完c+(n-1)(b+c),所以总会碰到
                ptr = ptr.next;
                slow = slow.next;
            }
            return ptr;
        }
    }
    return null;
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值