每日一题——最长无重复子数组

题目描述

给定一个长度为 n 的数组 arr,返回 arr 的最长无重复元素子数组的长度。无重复指的是所有数字都不相同。

子数组定义:子数组是连续的,比如 [1,3,5,7,9] 的子数组有 [1,3][3,5,7] 等,但 [1,3,7] 不是子数组。

数据范围

  • 数组长度 (0 \leq \text{arr.length} \leq 10^5)
  • 数组元素 (0 < \text{arr}[i] \leq 10^5)

示例

示例 1

输入
[2,3,4,5]
输出
4
说明
[2,3,4,5] 是最长子数组。

示例 2

输入
[2,2,3,4,3]
输出
3
说明
[2,3,4] 是最长子数组。

示例 3

输入
[9]
输出
1

示例 4

输入
[1,2,3,1,2,3,2,2]
输出
3
说明
最长子数组为 [1,2,3]

示例 5

输入
[2,2,3,4,8,99,3]
输出
5
说明
最长子数组为 [2,3,4,8,99]


解题思路

滑动窗口 + 哈希表

  1. 滑动窗口

    • 使用两个指针 leftright 表示当前窗口的左右边界。
    • right 指针向右移动,扩展窗口。
    • 如果遇到重复元素,left 指针向右移动,缩小窗口。
  2. 哈希表

    • 使用哈希表记录当前窗口中每个元素是否出现过。
    • 如果 arr[right] 已经存在于哈希表中,说明出现了重复元素,需要移动 left 指针。
  3. 更新最大长度

    • 每次窗口扩展时,更新最大无重复子数组的长度。

代码实现

#include <stdio.h>
#include <stdlib.h>

int maxLength(int* arr, int arrSize) {
    if (arrSize == 0) return 0; // 如果数组为空,直接返回0

    // 使用哈希表记录元素是否出现过
    int hash[100001] = {0}; // 哈希表大小根据题目数据范围设置
    int left = 0, right = 0; // 左右指针
    int max_len = 0; // 记录最长无重复子数组的长度

    while (right < arrSize) {
        if (hash[arr[right]] == 0) {
            // 如果当前元素没有出现过,加入哈希表
            hash[arr[right]] = 1;
            max_len = (right - left + 1 > max_len) ? (right - left + 1) : max_len;
            right++;
        } else {
            // 如果当前元素已经出现过,移动左指针直到没有重复元素
            while (left < right && hash[arr[right]] != 0) {
                hash[arr[left]] = 0;
                left++;
            }
        }
    }
    
    return max_len;
}

代码解析

1. 哈希表

  • 使用数组 hash 作为哈希表,记录当前窗口中每个元素是否出现过。
  • 数组大小为 100001,因为题目中数组元素的最大值为 (10^5)。

2. 滑动窗口

  • left 指针:表示窗口的左边界。
  • right 指针:表示窗口的右边界。
  • 窗口扩展:如果当前元素 arr[right] 没有出现过,扩展窗口。
  • 窗口收缩:如果当前元素 arr[right] 已经出现过,移动 left 指针直到没有重复元素。

3. 更新最大长度

  • 每次窗口扩展时,计算当前窗口的长度 right - left + 1,并更新 max_len

为什么while (left < right && hash[arr[right]] != 0)left不会一直增加到right

代码片段

while (left < right && hash[arr[right]] != 0) {
    hash[arr[left]] = 0;
    left++;
}

目的

这段代码的目的是:当右指针right指向的元素已经在当前子数组中出现过时,移动左指针left,直到当前子数组中不再包含重复的arr[right]为止。

  1. hash[arr[right]] != 0的条件

    • 这个条件确保了只有当arr[right]已经被包含在当前子数组中时,才会进入循环。
    • 一旦hash[arr[right]]被置为0(即arr[right]被移出子数组),循环就会停止。
  2. 循环的行为

    • 每次循环中,left向右移动一位,同时将hash[arr[left]]置为0,表示移除arr[left]
    • 这个过程会一直进行,直到hash[arr[right]]变为0,即arr[right]被移出子数组。
  3. 循环的终止条件

    • hash[arr[right]]变为0时,循环停止,此时arr[right]不再在子数组中。
    • 因此,left不会一直增加到right,而是会在移除重复元素后停止。

举例说明

假设数组为[2, 3, 4, 2]left = 0right = 3

  • 初始时,hash数组记录了[2, 3, 4]hash[2] = 1hash[3] = 1hash[4] = 1
  • right = 3时,arr[right] = 2,而hash[2] = 1,说明2已经在子数组中。
  • 进入循环:
    • left = 0hash[arr[left]] = hash[2] = 1,将其置为0,移除2
    • left++,此时left = 1
    • 再次检查hash[arr[right]] = hash[2],此时hash[2]已经被置为0,循环停止。

总结

  • left不会一直增加到right,因为循环的终止条件是hash[arr[right]] != 0
  • 一旦arr[right]被移出子数组,hash[arr[right]]会变为0,循环就会停止。
  • 这段代码的目的是确保在将arr[right]加入子数组之前,移除所有导致重复的元素。

复杂度分析

  • 时间复杂度:(O(n)),其中 (n) 是数组的长度。每个元素最多被访问两次(一次由 right 指针,一次由 left 指针)。
  • 空间复杂度:(O(1)),哈希表的大小固定为 (10^5),与输入规模无关。

总结

方法时间复杂度空间复杂度核心思想
滑动窗口 + 哈希表(O(n))(O(1))通过滑动窗口维护无重复子数组

标签:滑动窗口、哈希表、数组

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tt555555555555

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值