数组

大家好,我是老马。

今天我们一起来学习一下数组这种数据结构。

主要知识

数组需要拆分下面几个部分:

  1. 理论介绍

  2. 源码分析

  3. 数据结构实现?

  4. 题目练习(按照算法思想分类)

  5. 梳理对应的 sdk 包

  6. 应用实战

因为这个是 leetcode 系列,所以重点是 4、5(对4再一次总结)。

为了照顾没有基础的小伙伴,会简单介绍一下1的基础理论。

简单介绍1,重点为4。其他不是本系列的重点。

Sliding Window 滑动窗口

滑动窗口(Sliding Window)是一种常用于数组、字符串等线性数据结构中的算法技巧,特别适用于解决子区间子串相关的问题,比如「最长子串」、「最大和子数组」、「包含某种元素的最小子数组」等。


🧠 核心思想

滑动窗口的本质就是两个指针形成一个窗口,然后在这个窗口内进行操作:

  • 左指针表示窗口的起始位置
  • 右指针表示窗口的结束位置(通常是开放的,即不包括右边界)

通过不断移动左右指针,动态维护这个窗口的状态,从而达到:

  • 减少不必要的重复计算
  • 降低时间复杂度

📦 常见类型

1. 固定长度窗口

适用于要求窗口长度固定的问题。

例题:求一个数组中所有长度为 k 的子数组的最大和。

public int maxSum(int[] nums, int k) {
    int sum = 0;
    for (int i = 0; i < k; i++) sum += nums[i];
    int max = sum;

    for (int i = k; i < nums.length; i++) {
        sum += nums[i] - nums[i - k];
        max = Math.max(max, sum);
    }

    return max;
}

2. 动态长度窗口(也叫可变窗口)

适用于窗口长度不确定,但要满足某种条件(比如:包含所有字符、和不超过某个值等)。

例题:最小覆盖子串(LeetCode 76)

public String minWindow(String s, String t) {
    int[] need = new int[128];
    for (char c : t.toCharArray()) need[c]++;
    
    int left = 0, right = 0, start = 0, minLen = Integer.MAX_VALUE, count = t.length();

    while (right < s.length()) {
        if (need[s.charAt(right++)]-- > 0) count--;

        while (count == 0) {
            if (right - left < minLen) {
                minLen = right - left;
                start = left;
            }

            if (++need[s.charAt(left++)] > 0) count++;
        }
    }

    return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen);
}

🧩 适用场景

滑动窗口特别适合这些题型:

题型 举例
固定长度的子数组 最大/最小和、平均值
动态长度子数组 最小长度满足条件的区间
子串问题 最长无重复字符子串、最小覆盖子串
数量统计类 至多/至少包含 K 个不同字符的子串

⏱️ 时间复杂度分析

滑动窗口的优点之一就是:

  • 每个元素最多进出窗口一次 ⇒ 整体复杂度为 O(n),比暴力解法的 O(n²) 更高效。

🛠️ 实战技巧

  1. 窗口中需要统计什么内容?

    • 比如:元素个数、元素频率、和等
  2. 什么时候扩展窗口(移动右指针)?

    • 一般是为了让窗口「满足条件」
  3. 什么时候收缩窗口(移动左指针)?

    • 一般是为了让窗口「不再满足条件」后恢复到合法状态
  4. 如何判断窗口是否满足条件?

    • 常用的变量如:validcountmatch

滑动窗口看起来和双指针有些类似,二者的区别与联系

滑动窗口(Sliding Window)和双指针(Two Pointers)确实非常相似,很多人初学时容易混淆。

但它们其实是一对“重叠又有区别”的技巧,可以说:

滑动窗口 是双指针的一种特化


🧭 二者的联系

类别 滑动窗口 & 双指针
✅ 本质 都是用两个指针遍历数组(或字符串)
✅ 目的 减少暴力遍历的冗余计算,提升效率
✅ 复杂度 通常是 O(n),而非 O(n²)
✅ 使用结构 线性结构:数组、字符串、链表等

🔍 核心区别对比

对比项 滑动窗口 双指针
📌 关注点 窗口内的内容(通常是连续子数组/子串) 两个位置的关系(可以连续,也可以不连续)
📐 应用场景 统计窗口内的信息(子数组和、字符频率、是否满足条件) 排序数组中的搜索、配对、快慢指针类问题
📊 窗口长度 通常变化的(动态窗口)、有时固定 不强调窗口,强调两个点
↔️ 移动方式 一般是 right 先动,再根据条件 left 两个指针可以一起动,也可以独立移动
💡 常配合结构 频率表、哈希表、前缀和等 不一定,有时只是比较值、移动位置

🧠 总结口诀

滑动窗口:用来统计连续区间的状态(子串、子数组等)
双指针:用来比较两个位置的值或找某种“配对”关系

✅ 简单区分建议:

你要做的事
子数组/子串统计、满足某种限制、最大最小长度等 滑动窗口
两端收缩查找、元素比较、链表操作等 双指针

🧪 如果你还在纠结,可以记住这三点:

  1. 滑动窗口一般处理连续的一段区间
  2. 双指针处理两个独立位置的值/关系
  3. 滑动窗口可以看作是“受控的”双指针

给出滑动窗口的经典题目,一简单,2中等,1困难

难度 题目编号 & 名称 链接 类型
简单 643. 子数组最大平均数 I 力扣链接 固定长度滑窗
中等 3. 无重复字符的最长子串 力扣链接 动态窗口 + 去重
中等 438. 找到字符串中所有字母异位词 力扣链接 动态窗口 + 频率统计
困难 76. 最小覆盖子串 力扣链接 动态窗口 + 最小长度

数组基本知识+技巧

分类 代表技巧
遍历类 遍历、双指针、滑窗
前缀/差分类 前缀和、差分、后缀数组
查找类 二分、哈希、离散化
空间/状态优化 状压、滚动数组、差分压缩
排序/归并类 排序、归并逆序对、树状数组、线段树
子数组/序列 动规、中心扩展、单调栈
矩阵类 二维差分、DFS/BFS、旋转模拟
特殊技巧 快速幂、滚动哈希、模拟题技巧

🎯 常见遍历技巧 + 力扣题目对照表

技巧 力扣题目 说明
基础遍历 27.移除元素 直接遍历+判断
快慢指针 26. 删除重复项 原地去重
左右夹逼 167.两数之和 II 排序+夹逼
滑动窗口 209. 长度最小子数组 动态控制窗口
子数组枚举 560. 和为K的子数组 前缀和优化
子序列枚举 491. 递增子序列 回溯
倒序遍历 198. 打家劫舍 动态规划
规则模拟 54. 螺旋矩阵 控制方向循环遍历