【算法训练营 · 二刷总结篇】 数组与字符串部分

文章目录

  • 概述
  • 数组部分
    • 核心知识点
    • 技巧方法
      • [✅ 技巧一:双指针法(★★★★★ 考察频率TOP1,数组的「王者技巧」,必考,占数组题50%+)](#✅ 技巧一:双指针法(★★★★★ 考察频率TOP1,数组的「王者技巧」,必考,占数组题50%+))
        • [✔ 核心定位](#✔ 核心定位)
        • [✔ 核心原理](#✔ 核心原理)
        • [✔ 两种核心类型(全覆盖双指针所有场景,必背,无例外)](#✔ 两种核心类型(全覆盖双指针所有场景,必背,无例外))
          • [▶ 类型1:快慢指针(同向双指针,最常用,占双指针题70%)](#▶ 类型1:快慢指针(同向双指针,最常用,占双指针题70%))
          • [▶ 类型2:左右指针(相向双指针,次常用,占双指针题30%)](#▶ 类型2:左右指针(相向双指针,次常用,占双指针题30%))
      • [✅ 技巧二:二分查找法(★★★★★ 考察频率TOP2,必考,占数组题30%+)](#✅ 技巧二:二分查找法(★★★★★ 考察频率TOP2,必考,占数组题30%+))
        • [✔ 核心定位](#✔ 核心定位)
        • [✔ 核心前提(缺一不可,必记)](#✔ 核心前提(缺一不可,必记))
        • [✔ 核心精髓(二刷最容易踩坑的点,必须根治)](#✔ 核心精髓(二刷最容易踩坑的点,必须根治))
        • [✔ 两种标准区间模板(二选一,推荐左闭右闭[写法更直观,适合面试])](#✔ 两种标准区间模板(二选一,推荐左闭右闭[写法更直观,适合面试]))
          • [▶ 模板1:左闭右闭区间 [left, right] (推荐,必背,面试首选)](#▶ 模板1:左闭右闭区间 [left, right] (推荐,必背,面试首选))
          • [▶ 模板2:左闭右开区间 [left, right)](#▶ 模板2:左闭右开区间 [left, right))
        • [✔ 高频考察题型(二刷重点刷这些)](#✔ 高频考察题型(二刷重点刷这些))
        • [✔ 避坑点](#✔ 避坑点)
      • [✅ 技巧三:滑动窗口法(★★★★ 考察频率TOP3,高频必考,占数组题15%+)](#✅ 技巧三:滑动窗口法(★★★★ 考察频率TOP3,高频必考,占数组题15%+))
        • [✔ 核心定位](#✔ 核心定位)
        • [✔ 核心前提](#✔ 核心前提)
        • [✔ 核心原理(形象理解)](#✔ 核心原理(形象理解))
        • [✔ 核心思路(四步走,固化成模板,必背)](#✔ 核心思路(四步走,固化成模板,必背))
        • [✔ 适用场景(一眼识别)](#✔ 适用场景(一眼识别))
        • [✔ 避坑点](#✔ 避坑点)
      • [✅ 技巧四:前缀和 & 差分数组(★★★★ 考察频率TOP4,高频必考,合称「数组预处理双神器」)](#✅ 技巧四:前缀和 & 差分数组(★★★★ 考察频率TOP4,高频必考,合称「数组预处理双神器」))
        • [✔ 子技巧4.1:前缀和(重点,考频更高)](#✔ 子技巧4.1:前缀和(重点,考频更高))
        • [✔ 子技巧4.2:差分数组(次重点,考频略低,但必考)](#✔ 子技巧4.2:差分数组(次重点,考频略低,但必考))
      • [✅ 技巧五:模拟法 & 暴力枚举(★★ 保底技巧,考察频率低,但必须会)](#✅ 技巧五:模拟法 & 暴力枚举(★★ 保底技巧,考察频率低,但必须会))
        • [✔ 模拟法](#✔ 模拟法)
        • [✔ 暴力枚举](#✔ 暴力枚举)
    • 通用代码模板
      • [✔ 模板1:快慢指针(同向,原地修改数组,必背,覆盖26/27/283/80)](#✔ 模板1:快慢指针(同向,原地修改数组,必背,覆盖26/27/283/80))
      • [✔ 模板2:左右指针(相向,有序数组,必背,覆盖977/15/344)](#✔ 模板2:左右指针(相向,有序数组,必背,覆盖977/15/344))
      • [✔ 模板3:二分查找(左闭右闭,推荐面试版,必背,覆盖704/35/33)](#✔ 模板3:二分查找(左闭右闭,推荐面试版,必背,覆盖704/35/33))
      • [✔ 模板4:滑动窗口(不定长,子数组最值,必背,覆盖209/3)](#✔ 模板4:滑动窗口(不定长,子数组最值,必背,覆盖209/3))
      • [✔ 模板5:前缀和 + 差分数组(预处理双神器,必背,覆盖560/1109)](#✔ 模板5:前缀和 + 差分数组(预处理双神器,必背,覆盖560/1109))
  • 字符串部分
    • 核心知识点
    • 技巧方法
      • [✅ 技巧一:双指针法(★★★★★ 考察频率TOP1,字符串「王者技巧」,必考,占比50%+)](#✅ 技巧一:双指针法(★★★★★ 考察频率TOP1,字符串「王者技巧」,必考,占比50%+))
        • [✔ 核心定位](#✔ 核心定位)
        • [✔ 核心原理](#✔ 核心原理)
        • [✔ 三种核心类型(全覆盖所有场景,无例外,Java版专属优化,必背)](#✔ 三种核心类型(全覆盖所有场景,无例外,Java版专属优化,必背))
          • [▶ 类型1:左右指针(相向双指针,最常用,占双指针题80%)](#▶ 类型1:左右指针(相向双指针,最常用,占双指针题80%))
          • [▶ 类型2:快慢指针(同向双指针,次常用,占双指针题20%)](#▶ 类型2:快慢指针(同向双指针,次常用,占双指针题20%))
          • [▶ 类型3:双指针匹配(交叉双指针,子序列专属)](#▶ 类型3:双指针匹配(交叉双指针,子序列专属))
      • [✅ 技巧二:滑动窗口法(★★★★★ 考察频率TOP2,必考,占比20%+)](#✅ 技巧二:滑动窗口法(★★★★★ 考察频率TOP2,必考,占比20%+))
        • [✔ 核心定位](#✔ 核心定位)
        • [✔ 核心前提](#✔ 核心前提)
        • [✔ 核心原理](#✔ 核心原理)
        • [✔ 核心优化(Java版专属,面试加分关键)](#✔ 核心优化(Java版专属,面试加分关键))
      • [✅ 技巧三:KMP字符串匹配算法(★★★★★ 考察频率TOP3,字符串「灵魂考点」,必考,后端大厂最爱问)](#✅ 技巧三:KMP字符串匹配算法(★★★★★ 考察频率TOP3,字符串「灵魂考点」,必考,后端大厂最爱问))
        • [✔ 核心定位](#✔ 核心定位)
        • [✔ 核心痛点](#✔ 核心痛点)
        • [✔ 核心原理(二刷必须吃透,面试会追问,不用死记,理解即可)](#✔ 核心原理(二刷必须吃透,面试会追问,不用死记,理解即可))
        • [✔ 适用场景](#✔ 适用场景)
      • [✅ 技巧四:哈希表/数组统计字符频次(★★★★ 高频,占比10%)](#✅ 技巧四:哈希表/数组统计字符频次(★★★★ 高频,占比10%))
        • [✔ 核心定位](#✔ 核心定位)
        • [✔ 核心思路](#✔ 核心思路)
        • [✔ 两种最优统计方式(Java版,必背,面试加分)](#✔ 两种最优统计方式(Java版,必背,面试加分))
      • [✅ 技巧五:中心扩展法(★★★★ 回文专属,高频必考,占比8%)](#✅ 技巧五:中心扩展法(★★★★ 回文专属,高频必考,占比8%))
        • [✔ 核心定位](#✔ 核心定位)
        • [✔ 核心原理](#✔ 核心原理)
      • [✅ 技巧六:模拟法 + 暴力匹配(★★ 保底技巧,考察频率低,但必须会)](#✅ 技巧六:模拟法 + 暴力匹配(★★ 保底技巧,考察频率低,但必须会))
        • [✔ 模拟法](#✔ 模拟法)
        • [✔ 暴力匹配](#✔ 暴力匹配)
    • 通用代码模板
      • [✔ 模板1:双指针-左右指针(反转字符串,必考,适配344/541/345)](#✔ 模板1:双指针-左右指针(反转字符串,必考,适配344/541/345))
      • [✔ 模板2:双指针-快慢指针(原地移除指定字符,适配27/剑指Offer05)](#✔ 模板2:双指针-快慢指针(原地移除指定字符,适配27/剑指Offer05))
      • [✔ 模板3:滑动窗口(无重复字符的最长子串,必考TOP1,适配3/76/567)](#✔ 模板3:滑动窗口(无重复字符的最长子串,必考TOP1,适配3/76/567))
      • [✔ 模板4:KMP算法完整模板(字符串匹配必考,适配28,Java完整版,可直接默写)](#✔ 模板4:KMP算法完整模板(字符串匹配必考,适配28,Java完整版,可直接默写))
      • [✔ 模板5:中心扩展法(回文子串专属,必考,适配5/647,最优解)](#✔ 模板5:中心扩展法(回文子串专属,必考,适配5/647,最优解))
      • [✔ 模板6:字符频次统计(最优解,适配242/383,Java版数组统计)](#✔ 模板6:字符频次统计(最优解,适配242/383,Java版数组统计))

概述

结合大厂面试考察频率,我们将10个模块分为 S级(必考)、A级(高频)、B级(低频) 三个梯队:

梯队 模块 考察频率 后端岗位核心价值
S级(必考) 数组、字符串 100% 基础数据处理,接口参数校验、文本解析高频场景
链表 95% JVM链表实现、分布式链表、缓存淘汰策略底层
二叉树 90% 数据库索引(B+树)、分布式系统一致性树结构
哈希表 90% 缓存(HashMap)、负载均衡、分布式存储映射
A级(高频) 栈与队列 75% 表达式计算、任务队列、线程池底层、RPC调用栈
动态规划 70% 资源分配、路径规划、缓存优化、限流算法
回溯算法 65% 排列组合、子集生成、配置项遍历、正则匹配
B级(低频) 贪心算法 40% 调度策略、最优路径(如Dijkstra优化)
图论 30% 网络拓扑、分布式路由、依赖分析(拓扑排序)

接下来我们就按照顺序进行总结,二刷的过程中我们一定要牢牢抓住三个关键:

  • 重要知识点
  • 技巧方法
  • 思考模板

数组部分

核心知识点

  1. 数组的本质:连续的内存空间存储相同类型元素,Java中数组的底层都是连续内存(Python的list是动态数组)。
  2. 数组的核心特性:随机访问效率极高 O(1) ,根据下标直接定位元素;增删元素效率极低 O(n),因为增删会导致后续元素整体平移。
  3. 数组的核心边界:下标从0开始 ,所有数组题的第一行代码必须先处理边界条件if nums == null || nums.length == 0(Java)/ if not nums(Python),这是面试代码的「加分项+必做项」,漏了直接扣分。
  4. 数组的核心优化原则:能原地修改就原地修改,减少额外空间复杂度(面试高频考察「空间优化」,比如要求O(1)空间解题)。
  5. 数组与链表的区别(后端面试必问):数组连续内存+随机访问快+增删慢;链表非连续+随机访问慢+增删快,结合场景回答即可。
  6. 有序数组的特性:有序是「双指针」「二分查找」的前提,遇到「有序数组」的题目,优先放弃暴力解法,直接匹配这两个技巧,效率从O(n²)→O(n)或O(logn)。
  7. 子数组/子序列的区别:后端面试高频考,必须分清!
    • 子数组:连续的元素段(比如数组[1,2,3]的子数组是[1,2]、[2,3]),解题优先「滑动窗口、前缀和」;
    • 子序列:不要求连续,元素相对顺序不变(比如[1,3]是子序列),数组中子序列题极少,一般归到字符串/动态规划。
  8. 数组的常见时间复杂度优化方向:暴力枚举O(n²) → 双指针/滑动窗口O(n) → 二分查找O(logn),这是数组题的「最优解路径」。
  9. 原地修改的核心技巧:利用数组自身的下标作为「标记」,不额外开辟空间(比如原地去重、原地删除元素)。

技巧方法

数组的所有题目,99%的高频题都能被以下6个技巧全覆盖,按「大厂考察频率从高到低」排序,优先级决定你的复习顺序,每个技巧都标注「适用场景+核心思路+避坑点」,这是二刷的核心精华,记牢这些,数组题就不会有「没思路」的情况。

优先级排序:双指针法 > 二分查找法 > 滑动窗口法 > 前缀和/差分数组 > 模拟法 > 暴力枚举(保底)

核心原则:能不用暴力就不用暴力,暴力是最后兜底的选择,面试写暴力解法基本拿不到满分

✅ 技巧一:双指针法(★★★★★ 考察频率TOP1,数组的「王者技巧」,必考,占数组题50%+)

✔ 核心定位

双指针是数组二刷最需要吃透、最能提分 的技巧,没有之一!大厂数组面试题,一半以上都是双指针的应用,只要掌握双指针,数组题就拿下了半壁江山

✔ 核心原理

用两个指针(下标)在数组中移动,用一次遍历替代两次嵌套遍历 ,将时间复杂度从暴力的O(n²)降到O(n),空间复杂度保持O(1),完美契合面试的「最优解」要求。

✔ 两种核心类型(全覆盖双指针所有场景,必背,无例外)
▶ 类型1:快慢指针(同向双指针,最常用,占双指针题70%)

✅ 适用场景:原地修改数组 的所有题目 → 原地去重、原地删除指定元素、移除重复项、筛选数组元素、移动零等。

✅ 核心思路:快指针探路,慢指针留存

  • 快指针 fast:遍历数组,逐个检查元素是否符合「保留条件」;
  • 慢指针 slow:指向「下一个要写入的位置」,只有当快指针找到符合条件的元素时,慢指针才移动,将元素写入。
    ✅ 核心原则:快慢指针始终同向移动,不回头,一次遍历完成所有操作
    ✅ 高频经典题:LeetCode 26(原地去重)、27(移除元素)、283(移动零)、80(删除有序数组的重复项II)
    ✅ 避坑点:① 循环条件是fast < nums.length,不是slow;② 慢指针的移动时机是「写入元素后」,不是之前。
▶ 类型2:左右指针(相向双指针,次常用,占双指针题30%)

✅ 适用场景:有序数组必用! → 两数之和(有序版)、三数之和、四数之和、反转数组、回文数组判断、二分查找的底层逻辑、有序数组的区间合并。

✅ 核心思路:左指针在头,右指针在尾,相向移动,根据条件收缩区间

  • 左指针 left:初始0,负责找「较小值」;
  • 右指针 right:初始nums.length-1,负责找「较大值」;
  • 核心逻辑:通过判断nums[left] + nums[right] 与 目标值的大小关系,决定移动左指针还是右指针,逐步缩小范围。
    ✅ 核心原则:数组必须有序!无序数组用左右指针无效,先排序再用
    ✅ 高频经典题:LeetCode 977(有序数组的平方)、15(三数之和)、16(最接近的三数之和)、344(反转字符串)
    ✅ 避坑点:① 无序数组一定要先排序;② 三数之和/四数之和要去重 (跳过重复元素),否则会出现重复答案;③ 循环条件是left < right,不是left <= right

✅ 技巧二:二分查找法(★★★★★ 考察频率TOP2,必考,占数组题30%+)

✔ 核心定位

二分查找是数组的「最优解技巧」,专门解决有序数组的查找问题 ,时间复杂度O(logn),效率碾压所有遍历解法,后端面试中只要题目出现「有序数组+查找」,面试官绝对期望你写二分解法,写遍历直接减分。

✔ 核心前提(缺一不可,必记)

1. 数组必须是有序的;2. 数组中无重复元素(有重复也能查,只是结果不唯一),这两个前提是二分查找的「命门」,没有就不能用。

✔ 核心精髓(二刷最容易踩坑的点,必须根治)

二分查找的所有错误,99%都源于「区间定义不清晰」 ,区间定义决定了「循环条件」和「指针移动规则」,二刷必须做到:选定一种区间定义,固化成模板,永远不换! 不要两种都写,容易混乱。

✔ 两种标准区间模板(二选一,推荐左闭右闭[写法更直观,适合面试])
▶ 模板1:左闭右闭区间 [left, right] (推荐,必背,面试首选)

✅ 核心定义:leftright都是有效下标,区间内的元素都要被查找,比如[0, nums.length-1]区间内有元素的条件是 left <= right

✅ 指针移动规则:

  • nums[mid] > target:目标值在左区间,right = mid - 1(mid一定不是答案,排除);
  • nums[mid] < target:目标值在右区间,left = mid + 1(mid一定不是答案,排除);
  • nums[mid] == target:找到目标值,返回mid。
    ✅ 循环终止条件:left > right(区间无元素,查找失败)。
▶ 模板2:左闭右开区间 [left, right)

✅ 核心定义:left是有效下标,right是无效下标,区间内的元素是[left, right-1],比如[0, nums.length)区间内有元素的条件是 left < right

✅ 指针移动规则:

  • nums[mid] > target:目标值在左区间,right = mid(mid是无效下标,不用减1);
  • nums[mid] < target:目标值在右区间,left = mid + 1
  • nums[mid] == target:找到目标值,返回mid。
    ✅ 循环终止条件:left == right(区间无元素,查找失败)。
✔ 高频考察题型(二刷重点刷这些)
  1. 基础查找:LeetCode 704(二分查找)、35(搜索插入位置);
  2. 变种查找(大厂最爱考):LeetCode 33(搜索旋转排序数组)、34(在排序数组中查找元素的第一个和最后一个位置)、153(寻找旋转排序数组中的最小值);
✔ 避坑点
  1. 计算mid时,用 mid = left + (right - left)/2 替代 mid = (left + right)/2,避免left+right数值溢出(面试高频追问,必须知道);
  2. 区间定义和循环条件、指针移动规则必须严格匹配,不能混用;
  3. 旋转数组的二分,核心是「判断哪一侧是有序的」,有序侧就能用二分。

✅ 技巧三:滑动窗口法(★★★★ 考察频率TOP3,高频必考,占数组题15%+)

✔ 核心定位

滑动窗口是数组「子数组问题的最优解」,专门解决连续子数组的最值问题 (最长/最短/和为目标值/满足条件的子数组),时间复杂度O(n),比暴力枚举的O(n²)高效太多,后端面试中「子数组」相关题目,滑动窗口是标准答案。

✔ 核心前提

处理的是连续的子数组,如果是子序列,滑动窗口无效!

注意: 滑动窗口相比于双指针的一个最大区别就在于不需要数组有序!

✔ 核心原理(形象理解)

用两个同向的快慢指针 (left=窗口左边界,right=窗口右边界)组成一个「窗口」,窗口内的元素是当前的子数组,通过「扩大右边界」和「收缩左边界」,在一次遍历中找到满足条件的最优子数组。窗口中一般需要使用数据结构维护子数组的数值和或者是元素次数等信息

✔ 核心思路(四步走,固化成模板,必背)
  1. 初始化:left=0,sum=0,result=最值(最长设为0,最短设为无穷大);
  2. 扩大窗口:遍历数组,right指针逐个右移,将nums[right]加入窗口(更新sum/计数);
  3. 收缩窗口:当窗口内的元素满足条件时,尝试收缩左指针,缩小窗口,更新最优解;
  4. 遍历结束:返回result。
✔ 适用场景(一眼识别)

看到题目中有「连续子数组」+「和/积/长度/满足某个条件」,直接用滑动窗口,比如:

✅ 高频经典题:LeetCode 209(长度最小的子数组)、3(无重复字符的最长子串)、76(最小覆盖子串)、567(字符串的排列)

✔ 避坑点
  1. 窗口收缩的条件要准确,比如「和>=target」时收缩,不要写成「和==target」;
  2. 收缩窗口时,要「持续收缩」直到条件不满足,不是只收缩一次;
  3. 注意窗口的边界,left永远<=right。

✅ 技巧四:前缀和 & 差分数组(★★★★ 考察频率TOP4,高频必考,合称「数组预处理双神器」)

这两个技巧是「一对互补的解题思路」,专门解决数组的区间问题 ,属于「预处理思想」:提前对数组做一次遍历,生成辅助数组,之后所有查询/修改操作都能在O(1)时间完成,时间换空间,效率拉满,后端面试中「区间求和、区间修改」的题目,这两个技巧是标准答案,必须吃透!

核心记忆:求区间和 → 前缀和;批量区间修改+单点查询 → 差分数组,二者互补,无冲突。

✔ 子技巧4.1:前缀和(重点,考频更高)

✅ 适用场景:快速求数组中任意区间 [i,j] 的元素和 ,比如「求子数组的和」「和为k的子数组」,暴力解法是O(n²),前缀和是O(n)预处理+O(1)查询。

✅ 核心定义:前缀和数组preSum,其中preSum[0] = nums[0]preSum[i] = nums[0] + nums[1] + ... + nums[i]

✅ 核心公式:区间[left, right]的和 = preSum[right] - preSum[left - 1]

✅ 高频经典题:LeetCode 560(和为K的子数组)、1248(统计优美子数组)

✔ 子技巧4.2:差分数组(次重点,考频略低,但必考)

✅ 适用场景:对数组的多个连续区间进行「加减修改」,最后求修改后的数组 ,比如「给区间[1,3]的元素加2,给区间[2,5]的元素减1,最后输出数组」,暴力解法是O(n*m),差分数组是O(n)预处理+O(m)修改+O(n)还原,效率碾压。

✅ 核心定义:差分数组diff,其中diff[0] = nums[0]diff[i] = nums[i] - nums[i-1] (i>=1)

✅ 核心公式:对区间[left, right]的元素加val → diff[left] += valdiff[right+1] -= val

✅ 还原数组:其本质就是对差分数组做一次前缀和,遍历diff数组,nums[i] = nums[i-1] + diff[i]

✅ 高频经典题:LeetCode 1109(航班预订统计)、1094(拼车)

✅ 避坑点:修改right+1时,要判断是否越界,越界则不用修改。

✅ 技巧五:模拟法 & 暴力枚举(★★ 保底技巧,考察频率低,但必须会)

✔ 模拟法

✅ 适用场景:数组的「螺旋遍历、对角线遍历、矩阵旋转」等需要「按固定规则遍历」的题目,比如LeetCode 54(螺旋矩阵)、59(螺旋矩阵II)、48(旋转图像)。

✅ 核心思路:按题目要求的规则,模拟遍历过程,用边界变量(top/bottom/left/right)控制遍历范围,逐步缩小边界。

✅ 避坑点:边界条件要清晰,比如遍历完一行后,要更新top边界,避免重复遍历。

✔ 暴力枚举

✅ 适用场景:实在没思路时的「兜底解法」,比如数组题的暴力解法是两层嵌套遍历,时间复杂度O(n²)。

✅ 核心原则:二刷阶段,暴力解法只作为「验证思路」的工具,绝对不要把暴力解法当作最终答案写在面试里,面试官会认为你没掌握最优解。

通用代码模板

二刷的最高境界是:看到题型 → 立刻匹配技巧 → 直接默写模板 → 微调细节即可AC,这些模板是我整理的「大厂面试最优版」,代码简洁、无冗余、边界条件完整,全部可以直接默写,覆盖数组99%的高频题。

✔ 模板1:快慢指针(同向,原地修改数组,必背,覆盖26/27/283/80)

java 复制代码
// Java版:快慢指针通用模板 - 原地去重/删除元素
public int removeDuplicates(int[] nums) {
    // 必写:边界条件
    if (nums == null || nums.length == 0) return 0;
    int slow = 0; // 慢指针:留存位置
    for (int fast = 0; fast < nums.length; fast++) { // 快指针:探路
        // 核心条件:快指针找到符合条件的元素
        if (nums[fast] != nums[slow]) {
            slow++;
            nums[slow] = nums[fast];
        }
    }
    return slow + 1; // 新数组长度
}

✔ 模板2:左右指针(相向,有序数组,必背,覆盖977/15/344)

java 复制代码
// Java版:左右指针通用模板 - 有序数组两数之和/反转数组/三数之和基础
public void reverse(int[] nums) {
    if (nums == null || nums.length == 0) return;
    int left = 0, right = nums.length - 1;
    while (left < right) {
        // 交换元素/判断条件/计算值
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
        left++;
        right--;
    }
}

✔ 模板3:二分查找(左闭右闭,推荐面试版,必背,覆盖704/35/33)

java 复制代码
// Java版:二分查找左闭右闭模板 - 基础查找
public int search(int[] nums, int target) {
    if (nums == null || nums.length == 0) return -1;
    int left = 0, right = nums.length - 1;
    while (left <= right) { // 区间有元素的条件
        int mid = left + (right - left) / 2; // 避免溢出
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    return -1; // 未找到
}

✔ 模板4:滑动窗口(不定长,子数组最值,必背,覆盖209/3)

java 复制代码
// Java版:滑动窗口通用模板 - 求长度最小的子数组(和>=target)
public int minSubArrayLen(int target, int[] nums) {
    if (nums == null || nums.length == 0) return 0;
    int left = 0, sum = 0, res = Integer.MAX_VALUE;
    for (int right = 0; right < nums.length; right++) {
        sum += nums[right]; // 扩大窗口
        // 满足条件,收缩窗口
        while (sum >= target) {
            res = Math.min(res, right - left + 1);
            sum -= nums[left];
            left++;
        }
    }
    return res == Integer.MAX_VALUE ? 0 : res;
}

✔ 模板5:前缀和 + 差分数组(预处理双神器,必背,覆盖560/1109)

java 复制代码
// Java版:前缀和模板
public int[] getPrefixSum(int[] nums) {
    int n = nums.length;
    int[] preSum = new int[n];
    preSum[0] = nums[0];
    for (int i = 1; i < n; i++) {
        preSum[i] = preSum[i - 1] + nums[i];
    }
    return preSum;
}
// Java版:差分数组模板
public int[] getDiffArray(int[] nums) {
    int n = nums.length;
    int[] diff = new int[n];
    diff[0] = nums[0];
    for (int i = 1; i < n; i++) {
        diff[i] = nums[i] - nums[i-1];
    }
    return diff;
}

字符串部分

核心关联:Java中字符串的底层本质是「char[] 字符数组」 ,所以数组的所有解题技巧都能无缝迁移到字符串,这是二刷的核心捷径,一定要吃透这个关联点!

核心知识点

  1. 字符串的底层本质 :Java中String类的底层存储是 private final char[] value ,字符串就是「包装后的字符数组」,这也是数组所有技巧能复用在字符串的核心原因(双指针、滑动窗口等)。

  2. String的不可变性(★★★★★ 后端面试必问TOP1)

    • 核心原因:char[] value数组被private + final修饰,数组地址不可变,数组内容也无法修改(private封装)。
    • 关键影响:Java中所有对String的修改(拼接、替换、截取、反转),都会创建新的String对象,原字符串不变。
    • 解题注意:做字符串「原地修改」类题目时,必须先把String转为 char[] 数组,操作完成后再转回String,这是Java字符串的「解题铁律」,比如反转字符串、删除指定字符,不转数组会有大量无效对象创建,代码也无法实现原地修改。
  3. Java中3个字符串类的区别(★★★★★ 必问TOP2,算法题必用)

    • String:不可变,适合只读、无需修改 的场景(比如题目输入、结果返回),算法题中作为入参和返回值
    • StringBuilder:可变字符序列,非线程安全 ,效率最高,算法题中所有字符串拼接、修改、拼接遍历结果的场景,优先用它(面试写这个绝对加分)。
    • StringBuffer:可变字符序列,线程安全 ,加了synchronized锁,效率低,算法题中完全不用 (面试写这个会被认为不懂效率优化)。
      ✔ 面试标准答案:单线程用StringBuilder,多线程用StringBuffer,只读用String
  4. 字符的ASCII码规律(★★★★★ 解题万能技巧,必考)

    字符串的所有字符操作,本质都是ASCII码的数值运算,这是字符串题的「解题捷径」,必须背熟以下规律,提笔就用

    • 小写字母 a-z:ASCII码范围 97-122c - 'a' 可以将小写字母转为 0-25 的数字(高频!比如统计字母频次)。
    • 大写字母 A-Z:ASCII码范围 65-90c - 'A' 可以将大写字母转为 0-25 的数字。
    • 大小写转换:小写字母 = 大写字母 + 32大写字母 = 小写字母 - 32
    • 数字字符 0-9:ASCII码范围 48-57c - '0' 可以将字符数字转为int型数字。
    • 空格字符:ASCII码是 32
      ✔ 核心技巧:判断字符类型(字母/数字/大小写)、统计字母频次、异位词判断,全靠ASCII码运算,效率极高
  5. 字符串的必写边界条件(★★★★★ 代码加分项,漏写直接扣分)

    和数组一致,所有字符串算法题的第一行代码,必须先处理边界条件,这是Java面试的「代码规范硬性要求」,无例外:

    java 复制代码
    if (s == null || s.length() == 0) {
        return ""; // 或对应逻辑,比如return 0/return false
    }

    高频边界场景:空串""、null、单字符字符串、全空格字符串。

  6. 子串 & 子序列 区别(★★★★ 高频考点,必考)

    这是字符串最核心的概念区分,题目中的关键词决定解题方法,记死这个规则,不会走弯路:

    • 子串 (Substring) :字符连续 ,且保持原顺序(比如 "abc" 的子串是 "ab""bc"),解题优先用「滑动窗口、双指针、KMP」;
    • 子序列 (Subsequence) :字符不要求连续 ,但必须保持原顺序(比如 "abc" 的子序列是 "ac"),解题优先用「双指针、动态规划」;
      ✔ 面试题中,字符串90%的题目是「子串问题」,10%是子序列问题。
  7. 字符串匹配的核心问题 :给定主串s、模式串p,判断p是否是s的子串,或找到ps中的所有匹配位置,最优解永远是KMP算法,暴力匹配会被面试官要求优化,必须掌握。

  8. 回文字符串的两种形态:字符串的回文是高频考点,分为「回文子串」(连续)和「回文子序列」(不连续),解题方法完全不同:回文子串用「中心扩展法/双指针」,回文子序列用「动态规划」。

  9. Java字符串的常用高效API(算法题必用,记熟省时间)

    算法题中不用记太多API,只记这些高频且高效的,所有API的下标都是左闭右开,和数组一致,避免下标错误:

    • char charAt(int index):获取指定下标字符,效率最高,替代转char数组的场景;
    • String substring(int start, int end):截取子串 [start, end),start必填,end默认到末尾;
    • boolean equals(String another):字符串内容比较(绝对不能用==);
    • toCharArray():转为char[]数组,原地修改必用
    • length():获取字符串长度(注意:数组是length属性,字符串是length()方法,面试常考区别)。

技巧方法

字符串的所有面试题,99%的高频题都能被以下6个技巧全覆盖,优先级严格按「大厂考察频率+后端面试权重」排序,你的复习顺序就按这个优先级来!

优先级排序:双指针法 > 滑动窗口法 > KMP字符串匹配法 > 哈希表/数组统计字符频次 > 中心扩展法(回文专属) > 模拟法/暴力法(保底)

核心原则:① 字符串是「字符数组」,数组的所有技巧无缝复用;② Java中字符串修改必转char[];③ 能不用暴力就不用暴力,暴力是最后兜底的选择,面试写暴力解法基本拿不到满分。

✅ 技巧一:双指针法(★★★★★ 考察频率TOP1,字符串「王者技巧」,必考,占比50%+)

✔ 核心定位

双指针是字符串二刷最需要吃透、最能提分 的技巧,没有之一!大厂字符串面试题,一半以上都是双指针的应用,掌握双指针,字符串题就拿下了半壁江山 ,且和数组的双指针逻辑完全一致,只是操作对象从int[]变成了char[]/String

✔ 核心原理

用两个指针(下标)在字符串中移动,一次遍历替代嵌套遍历 ,时间复杂度从暴力的O(n²)降到O(n),空间复杂度O(1)(原地操作),完美契合面试的「最优解」要求。

✔ 三种核心类型(全覆盖所有场景,无例外,Java版专属优化,必背)
▶ 类型1:左右指针(相向双指针,最常用,占双指针题80%)

✅ 适用场景:字符串反转、回文判断/验证、回文子串、字符对称匹配、有序字符串的合并 ,只要题目涉及「对称、反转、回文」,无脑用左右指针!

✅ 核心思路:左指针left初始=0(字符串头部),右指针right初始=s.length()-1(字符串尾部),相向移动,根据条件判断/交换字符 ,循环终止条件:left < right

✅ Java解题细节:因为String不可变,反转/交换字符必须先转char[]数组 ,操作完成后再用new String(char[])转回字符串。

✅ 高频经典真题:LeetCode 344(反转字符串)、541(反转字符串II)、125(验证回文串)、680(验证回文字符串Ⅱ)、345(反转字符串中的元音字母)

✅ 避坑点:① 循环条件是left < right,不是left <= right;② 转char数组后,记得转回String;③ 验证回文时,注意跳过非字母/数字字符(如空格、标点)。

▶ 类型2:快慢指针(同向双指针,次常用,占双指针题20%)

✅ 适用场景:字符串的原地删除/去重/筛选字符 ,比如「移除指定字符、删除重复字符、删除空格、保留指定字符」,和数组的快慢指针逻辑完全一致,是字符串「原地修改」的最优解。

✅ 核心思路:快指针探路,慢指针留存

  • 快指针fast:遍历字符串的每个字符,逐个检查是否符合「保留条件」;
  • 慢指针slow:指向「下一个要写入字符的位置」,只有当快指针找到符合条件的字符时,慢指针才移动,将字符写入。
    ✅ Java解题细节:① 转char[]数组进行原地修改;② 最终结果是截取[0, slow)的字符数组,转为字符串。
    ✅ 高频经典真题:LeetCode 27(移除元素→迁移到字符串)、459(重复的子字符串)、剑指Offer 05(替换空格)
    ✅ 避坑点:① 慢指针的移动时机是「写入字符后」;② 最终结果的长度是slow,不是原字符串长度。
▶ 类型3:双指针匹配(交叉双指针,子序列专属)

✅ 适用场景:判断一个字符串是否是另一个字符串的子序列 ,比如「s是否是t的子序列」,这是字符串独有的双指针场景,也是高频考点。

✅ 核心思路:指针i指向s的头部,指针j指向t的头部,遍历t:如果s[i]==t[j],则i右移;无论是否相等,j都右移。最终如果i遍历完s,则是子序列。

✅ 时间复杂度:O(n),n是t的长度,最优解。

✅ 高频经典真题:LeetCode 392(判断子序列)

✅ 技巧二:滑动窗口法(★★★★★ 考察频率TOP2,必考,占比20%+)

✔ 核心定位

滑动窗口是字符串「连续子串问题的最优解」,没有之一!字符串的99%的子串问题都是滑动窗口的应用场景,后端面试中只要题目出现「连续子串」+「最长/最短/无重复/包含某字符」,面试官绝对期望你写滑动窗口解法,写暴力直接减分。

✔ 核心前提

处理的是连续的子串,如果是子序列,滑动窗口无效!(字符串的核心区分点,记死)

✔ 核心原理

用两个同向的快慢指针 (left=窗口左边界,right=窗口右边界)组成一个「窗口」,窗口内的字符是当前的子串,通过「扩大右边界」和「收缩左边界」,在一次遍历中找到满足条件的最优子串。逻辑和数组的滑动窗口完全一致,只是操作对象是字符

✔ 核心优化(Java版专属,面试加分关键)

字符串的滑动窗口需要统计字符频次,Java中最优选择是:

  • 如果题目限定是「小写英文字母/大写英文字母」:用int[26] cnt数组统计,cnt[c - 'a']++,效率远高于HashMap,面试写这个绝对加分
  • 如果是任意字符:用HashMap<Character, Integer>统计频次。
    ✅ 高频经典真题:LeetCode 3(无重复字符的最长子串)、76(最小覆盖子串)、567(字符串的排列)、438(找到字符串中所有字母异位词)、904(水果成篮→字符版)
    ✅ 避坑点:① 窗口收缩的条件要准确,比如「包含所有目标字符」时收缩;② 收缩窗口时要「持续收缩」直到条件不满足;③ 字符频次统计时,记得在收缩窗口时「减频次」。

✅ 技巧三:KMP字符串匹配算法(★★★★★ 考察频率TOP3,字符串「灵魂考点」,必考,后端大厂最爱问)

✔ 核心定位

KMP是字符串的专属核心算法 ,也是面试的「分水岭考点」------能写出KMP的完整Java代码+讲清原理,面试官会直接加分!KMP专门解决字符串匹配问题 :给定主串s和模式串p,判断p是否是s的子串,或找到ps中所有出现的位置。

✔ 核心痛点

暴力匹配的时间复杂度是O(n*m),效率极低;KMP的时间复杂度是O(n+m)最优解,没有之一,面试中只要考字符串匹配,面试官一定会问「有没有更优的解法」,答案就是KMP。

✔ 核心原理(二刷必须吃透,面试会追问,不用死记,理解即可)
  1. KMP的核心:利用模式串的「最长相等前后缀」,避免主串指针的回溯,只需要移动模式串指针;
  2. 如何理解:当主串和模式串匹配到某一位发现不相等的时候,这说明前面已经匹配的部分主串和模式串是完全一样的。那么为了保证主串的匹配指针不发生回退,将主串中已经匹配的部分当作后缀,然后再到模式串中寻找到与之相对应的最长的公共前缀,然后将模式串中指针进行移动,再继续匹配即可。

  3. 最长相等前后缀:对于模式串的子串p[0..i],前缀是「不包含最后一个字符的所有头部子串」,后缀是「不包含第一个字符的所有尾部子串」,最长相等前后缀就是两者中长度最长的相等子串;
  4. 核心数组:next数组next[i]表示模式串p[0..i]的最长相等前后缀的长度,KMP的核心就是构建next数组 。在构建next数组的过程中同样也使用到了上面的理解思想。i指针指向后缀的末尾,j指针指向前缀的末尾(同时也代表当前已匹配的前缀长度)。当i、j所指元素相同的时候两个指针均向后移。而当不相同的时候,j指针并不是依次左移比较,而是寻找一个较小的部分,也就是找到[0,j - 1]这部分的最长公共前后缀(对应下图方框中的aba,我们前面提到过j指针除了指向当前的前缀末尾,还可以表示当前最长的公共前后缀长度,所以0到j - 1这部分和i的前面相对应部分是相同的),将这部分的前缀与j当作新的前缀(下图中最左边的ab),这部分的后缀与i当作新的后缀(下图中最右边的aa),移动j指针继续去比较这两个新的前后缀。而这个最长的公共前后缀就是next[j - 1]的值,也就是j指针要移动去的索引 。如此直到j为0或者两个指针所指元素相同。
✔ 适用场景

所有字符串匹配问题:判断子串、找匹配位置、重复子串判断等。

✅ 高频经典真题:LeetCode 28(找出字符串中第一个匹配项的下标)、459(重复的子字符串)、剑指Offer 28(字符串的匹配)

✅ 避坑点:① next数组的下标对应关系,避免越界;② 模式串匹配时,指针的移动规则要准确。

✅ 技巧四:哈希表/数组统计字符频次(★★★★ 高频,占比10%)

✔ 核心定位

这是字符串的「万能辅助技巧」,几乎所有字符串题都会用到,尤其是结合双指针、滑动窗口的场景,是解决「字符异位词、字符频次匹配、字符计数」类题目的最优解。

✔ 核心思路

统计字符串中每个字符出现的次数,通过频次的比较/判断来解题,Java中优先用数组统计,效率远高于HashMap

✔ 两种最优统计方式(Java版,必背,面试加分)
  1. 场景1:字符串仅包含 小写英文字母 a-z → 用int[26] count数组,count[c - 'a']++ 统计频次,count[c - 'a']-- 减少频次,效率O(1),最优选择!
  2. 场景2:字符串包含 大小写字母、数字、符号 → 用HashMap<Character, Integer>统计,map.put(c, map.getOrDefault(c, 0)+1)
    ✅ 高频经典真题:LeetCode 242(有效的字母异位词)、383(赎金信)、409(最长回文串)、567(字符串的排列)
    ✅ 避坑点:① 小写字母统计时,记得减'a',不是减0;② 统计完成后,记得重置数组/哈希表。

✅ 技巧五:中心扩展法(★★★★ 回文专属,高频必考,占比8%)

✔ 核心定位

这是字符串「回文子串问题的最优解」,专门解决「最长回文子串、统计回文子串个数」等题目,比动态规划更适合面试------代码更简洁、空间复杂度更低(O(1))、思路更直观,面试官更喜欢这种解法。

✔ 核心原理

回文字符串的核心特征:以某个字符为中心,向两边对称扩散,回文的中心分为两种:

  1. 奇数长度的回文 :中心是「单个字符」,比如 "aba",中心是 b
  2. 偶数长度的回文 :中心是「两个相邻的字符」,比如 "abba",中心是 bb

✅ 核心思路:遍历字符串的每个位置,分别作为「奇数中心」和「偶数中心」,向两边扩散,记录最大的回文子串长度。

✅ 高频经典真题:LeetCode 5(最长回文子串)、647(回文子串)

✅ 避坑点:① 必须同时处理奇数和偶数中心,否则会漏掉偶数长度的回文;② 扩散时注意边界条件:left >=0 && right < s.length()

✅ 技巧六:模拟法 + 暴力匹配(★★ 保底技巧,考察频率低,但必须会)

✔ 模拟法

✅ 适用场景:字符串的「按规则拼接、反转、替换、分割」等需要按题目要求模拟操作的题目,比如「反转单词顺序、字符串相加、Z字形变换」。

✅ 核心思路:按题目要求的规则,一步步模拟操作,用StringBuilder拼接结果(Java最优选择)。

✅ 真题:LeetCode 151(反转字符串中的单词)、415(字符串相加)、6(Z字形变换)

✔ 暴力匹配

✅ 适用场景:实在没思路时的「兜底解法」,比如字符串匹配的暴力解法(两层嵌套遍历),时间复杂度O(n*m)

✅ 核心原则:二刷阶段,暴力解法只作为「验证思路」的工具,绝对不要把暴力解法当作最终答案写在面试里,面试官会认为你没掌握最优解。

通用代码模板

所有模板的题目都是LeetCode高频原题,模板本身就是题目的最优解,无需修改,仅需微调变量名即可适配同类题。

✔ 模板1:双指针-左右指针(反转字符串,必考,适配344/541/345)

java 复制代码
// 经典题:344. 反转字符串 (原地修改,最优解)
public void reverseString(char[] s) {
    // 边界条件必写
    if (s == null || s.length <= 1) return;
    int left = 0;
    int right = s.length - 1;
    // 相向移动,交换字符
    while (left < right) {
        // 交换两个字符
        char temp = s[left];
        s[left] = s[right];
        s[right] = temp;
        left++;
        right--;
    }
}

// 扩展:反转字符串的指定区间 [start, end],通用模板
public void reverse(char[] s, int start, int end) {
    int left = start;
    int right = end;
    while (left < right) {
        char temp = s[left];
        s[left] = s[right];
        s[right] = temp;
        left++;
        right--;
    }
}

✔ 模板2:双指针-快慢指针(原地移除指定字符,适配27/剑指Offer05)

java 复制代码
// 经典题:移除字符串中的指定字符,返回新字符串 (原地修改,最优解)
public String removeChar(String s, char target) {
    if (s == null || s.length() == 0) return "";
    char[] arr = s.toCharArray();
    int slow = 0; // 慢指针:留存位置
    for (int fast = 0; fast < arr.length; fast++) { // 快指针:探路
        if (arr[fast] != target) {
            arr[slow] = arr[fast];
            slow++;
        }
    }
    // 截取[0, slow)的字符数组,转为字符串
    return new String(arr, 0, slow);
}

✔ 模板3:滑动窗口(无重复字符的最长子串,必考TOP1,适配3/76/567)

java 复制代码
// 经典题:3. 无重复字符的最长子串 (最优解,滑动窗口+数组统计)
public int lengthOfLongestSubstring(String s) {
    if (s == null || s.length() == 0) return 0;
    int[] cnt = new int[128]; // 覆盖所有ASCII字符
    int left = 0; // 窗口左边界
    int maxLen = 0; // 记录最大长度
    for (int right = 0; right < s.length(); right++) {
        char c = s.charAt(right);
        cnt[c]++;
        // 有重复字符,收缩左边界
        while (cnt[c] > 1) {
            char leftC = s.charAt(left);
            cnt[leftC]--;
            left++;
        }
        // 更新最大长度
        maxLen = Math.max(maxLen, right - left + 1);
    }
    return maxLen;
}

✔ 模板4:KMP算法完整模板(字符串匹配必考,适配28,Java完整版,可直接默写)

java 复制代码
// KMP核心:构建next数组
private int[] getNext(String p) {
    int m = p.length();
    int[] next = new int[m];
    next[0] = 0; // 第一个字符的最长相等前后缀为0
    int j = 0; // j表示最长相等前后缀的长度
    for (int i = 1; i < m; i++) {
        // 不相等时,回退j
        while (j > 0 && p.charAt(i) != p.charAt(j)) {
            j = next[j - 1];
        }
        // 相等时,j++
        if (p.charAt(i) == p.charAt(j)) {
            j++;
        }
        next[i] = j;
    }
    return next;
}

// KMP匹配:返回模式串p在主串s中第一个匹配的下标,无则返回-1
public int strStr(String s, String p) {
    if (p.length() == 0) return 0;
    int n = s.length();
    int m = p.length();
    int[] next = getNext(p);
    int j = 0; // 模式串指针
    for (int i = 0; i < n; i++) {
        while (j > 0 && s.charAt(i) != p.charAt(j)) {
            j = next[j - 1];
        }
        if (s.charAt(i) == p.charAt(j)) {
            j++;
        }
        // 匹配完成,返回起始下标
        if (j == m) {
            return i - m + 1;
        }
    }
    return -1;
}

✔ 模板5:中心扩展法(回文子串专属,必考,适配5/647,最优解)

java 复制代码
// 经典题:5. 最长回文子串 (中心扩展法,最优解,空间O(1))
private int maxLen = 0;
private int start = 0;
public String longestPalindrome(String s) {
    if (s == null || s.length() <= 1) return s;
    int n = s.length();
    // 遍历每个位置,作为奇数/偶数中心
    for (int i = 0; i < n; i++) {
        expand(s, i, i); // 奇数中心:单个字符
        expand(s, i, i + 1); // 偶数中心:两个字符
    }
    // 截取最长回文子串
    return s.substring(start, start + maxLen);
}
// 中心扩展核心方法
private void expand(String s, int left, int right) {
    // 满足回文条件,向两边扩散
    while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
        left--;
        right++;
    }
    // 计算当前回文长度
    int curLen = right - left - 1;
    // 更新最大长度和起始位置
    if (curLen > maxLen) {
        maxLen = curLen;
        start = left + 1;
    }
}

✔ 模板6:字符频次统计(最优解,适配242/383,Java版数组统计)

java 复制代码
// 经典题:242. 有效的字母异位词 (数组统计,效率最高)
public boolean isAnagram(String s, String t) {
    if (s.length() != t.length()) return false;
    int[] cnt = new int[26]; // 小写字母专用
    // 统计s的字符频次
    for (char c : s.toCharArray()) {
        cnt[c - 'a']++;
    }
    // 减少t的字符频次
    for (char c : t.toCharArray()) {
        cnt[c - 'a']--;
        // 提前剪枝,出现负数直接返回false
        if (cnt[c - 'a'] < 0) return false;
    }
    return true;
}
相关推荐
玖日大大2 小时前
随机森林算法原理及实战代码解析
算法·随机森林·机器学习
历程里程碑2 小时前
哈希1:两数之和:哈希表优化指南
java·开发语言·数据结构·c++·算法·哈希算法·散列表
程序员-King.2 小时前
day150—数组—二叉树的锯齿形层序遍历(LeetCode-103)
算法·leetcode·二叉树
被星1砸昏头2 小时前
C++中的状态模式实战
开发语言·c++·算法
sin_hielo2 小时前
leetcode 3314(位运算,lowbit)
数据结构·算法·leetcode
Remember_9932 小时前
【数据结构】深入理解排序算法:从基础原理到高级应用
java·开发语言·数据结构·算法·spring·leetcode·排序算法
bybitq2 小时前
Leetcode-124-二叉树最大路径和-Python
算法·leetcode·深度优先
鱼跃鹰飞2 小时前
Leetcode会员专享题:426.将二叉搜索树转换为排序的双向链表
数据结构·算法·leetcode·链表·深度优先
漫随流水2 小时前
leetcode回溯算法(39.组合总和)
数据结构·算法·leetcode·回溯算法