当前位置: 首页 > news >正文

第03篇:字符串入门

概述:为什么字符串是算法题里的高频主角

学完数组之后,字符串通常就是下一站。

原因并不复杂:

  • 很多题目的输入本质上就是一段文本
  • 字符串底层往往和数组处理思路高度相似
  • 双指针、哈希表、滑动窗口、KMP 等经典算法,很多都和字符串密切相关
  • 面试中非常喜欢考察字符串,因为它能同时检验遍历能力、边界处理能力和代码细节能力

这篇文章的目标不是单纯讲String的语法,而是帮助你建立字符串题最基础的 4 种意识:

  1. 字符串本质上也是一种可遍历的数据序列
  2. 字符统计类问题通常围绕“计数”展开
  3. 字符串反转类问题通常围绕“双指针”展开
  4. 字符匹配类问题通常围绕“比较、扫描、定位”展开

学完这篇,你应该能看懂大多数基础字符串题到底在考什么。

核心概念:字符串到底是什么

字符串可以理解为:由多个字符按顺序组成的一段文本数据

例如:

Strings="algorithm";

这里的s就是一个字符串,它由多个字符依次组成:

a l g o r i t h m

如果从算法角度看,字符串和数组非常像:

  • 都可以按位置访问元素
  • 都可以从头到尾遍历
  • 都经常会用到下标
  • 都很依赖边界判断

不同点在于,字符串处理的对象是字符,而不是普通整数。

在 Java 中,如果你想访问字符串中的某个字符,最常见的方式是:

Strings="hello";charch=s.charAt(1);// e

字符串长度则通过length()获取:

intn=s.length();

原理:字符串题为什么特别考验细节

数组题里,很多元素比较的是数值大小;而字符串题里,除了遍历,你还会频繁遇到这些问题:

  • 当前字符是不是字母
  • 是否和另一个字符相等
  • 大小写是否要统一
  • 空格和标点是否需要跳过
  • 比较的是字符、子串,还是整个字符串

所以字符串题经常看起来简单,但特别容易在细节上丢分。

下面这些错误,新手非常常见:

  • ==当作字符串内容比较
  • 忘记处理空字符串
  • 下标访问越界
  • 反转时左右指针没有收缩好
  • 子串匹配时边界条件写错

字符串题的难点,往往不在思路本身,而在于细节是否严谨。

基础操作:先把字符串遍历吃透

和数组一样,字符串题里最常见的第一步也是遍历。

1. 从前往后遍历字符串

publicstaticvoidprintChars(Strings){for(inti=0;i<s.length();i++){System.out.println(s.charAt(i));}}

这类写法非常常见,适用于:

  • 统计字符种类
  • 判断字符是否合法
  • 查找某个字符第一次出现的位置
  • 比较相邻字符关系

时间复杂度通常是:

O(n)

其中n表示字符串长度。

2. 转成字符数组再处理

有些题目需要频繁修改字符,这时会更适合先转成字符数组。

publicstaticvoidprintCharsByArray(Strings){char[]chars=s.toCharArray();for(charch:chars){System.out.println(ch);}}

为什么这样做?

因为在 Java 中,String是不可变的。
如果你频繁拼接或修改,直接操作String往往不如操作char[]StringBuilder更合适。

第一类高频题:字符统计

字符统计类问题是字符串入门里最常见的一种。

它的核心通常只有一句话:

遍历字符串,并记录每个字符出现了多少次。

示例 1:统计某个字符出现的次数

publicstaticintcountChar(Strings,chartarget){intcount=0;for(inti=0;i<s.length();i++){if(s.charAt(i)==target){count++;}}returncount;}

例如:

intans=countChar("banana",'a');// 3

这类题的关键很简单:

  1. 准备一个计数变量
  2. 遍历字符串
  3. 满足条件就累加

时间复杂度是:

O(n)

空间复杂度是:

O(1)

示例 2:统计每个字符出现的频率

如果题目要求统计所有字符出现次数,最常见会用哈希表。

importjava.util.HashMap;importjava.util.Map;publicstaticMap<Character,Integer>countFrequency(Strings){Map<Character,Integer>freq=newHashMap<>();for(inti=0;i<s.length();i++){charch=s.charAt(i);freq.put(ch,freq.getOrDefault(ch,0)+1);}returnfreq;}

这种题非常经典,后面很多字符串题都会建立在“先统计频率”这个动作之上,比如:

  • 判断两个字符串是否为字母异位词
  • 找到第一个不重复字符
  • 统计出现次数最多的字符

示例 3:只统计英文字母

有些题会要求你忽略数字、空格或标点,这时就要先做字符判断。

publicstaticintcountLetters(Strings){intcount=0;for(inti=0;i<s.length();i++){charch=s.charAt(i);if(Character.isLetter(ch)){count++;}}returncount;}

这类题提醒你一件事:

字符串题经常不只是“遍历”,而是“遍历 + 条件筛选”。

第二类高频题:字符串反转

字符串反转是非常典型的入门题,但它背后对应的是非常重要的双指针思想。

示例 1:使用StringBuilder反转

如果只是快速得到反转结果,Java 中最直接的写法是:

publicstaticStringreverseString(Strings){returnnewStringBuilder(s).reverse().toString();}

这个写法非常简洁,适合工程开发或快速处理。

但如果你正在学算法,重点不应该只停在“会调用 API”,而是要理解:

如果不用现成方法,如何自己实现反转?

示例 2:双指针手写反转

publicstaticStringreverseByTwoPointers(Strings){char[]chars=s.toCharArray();intleft=0;intright=chars.length-1;while(left<right){chartemp=chars[left];chars[left]=chars[right];chars[right]=temp;left++;right--;}returnnewString(chars);}

这个过程的本质是:

  1. 左指针从头开始
  2. 右指针从尾开始
  3. 两边字符交换
  4. 指针不断向中间靠拢

例如字符串:

hello

交换过程大致是:

h e l l o o e l l h o l l e h

时间复杂度通常是:

O(n)

空间复杂度通常是:

O(n)

因为这里转成了字符数组。

反转类题的常见变形

后续你会遇到很多反转变形题:

  • 只反转单词顺序
  • 只反转元音字母
  • k个字符反转一次
  • 判断一个字符串是否为回文

虽然题目外观不同,但核心仍然是:

双指针 + 边界处理。

第三类高频题:字符串匹配

所谓字符串匹配,最基础的理解就是:

判断某个字符、某个子串,是否出现在另一个字符串中。

这是字符串题里极高频的一类。

示例 1:查找字符第一次出现的位置

publicstaticintfirstIndexOfChar(Strings,chartarget){for(inti=0;i<s.length();i++){if(s.charAt(i)==target){returni;}}return-1;}

这本质上就是线性扫描,复杂度通常为O(n)

示例 2:判断一个字符串是否包含某个子串

如果使用 Java 自带方法,可以直接这样写:

publicstaticbooleancontainsSubString(Strings,Stringtarget){returns.contains(target);}

但从算法学习角度,更重要的是知道最基础的暴力匹配思路。

示例 3:手写最基础的子串匹配

publicstaticintindexOfSubString(Stringtext,Stringpattern){intn=text.length();intm=pattern.length();for(inti=0;i<=n-m;i++){intj=0;while(j<m&&text.charAt(i+j)==pattern.charAt(j)){j++;}if(j==m){returni;}}return-1;}

这个写法的含义是:

  1. 枚举主串中的每一个可能起点
  2. 从该起点开始逐个字符比较
  3. 如果全部匹配成功,就返回起始位置

例如:

intindex=indexOfSubString("algorithm","go");// 2

这就是最原始的字符串匹配思想。
后面你学到 KMP 时,会发现 KMP 本质上也是在解决“匹配时如何少回退”这个问题。

一道综合基础题:判断回文字符串

字符串入门里有一类非常经典的综合题,就是回文判断。

所谓回文,就是正着读和反着读都一样,例如:

  • level
  • radar
  • abba

示例:判断字符串是否为回文

publicstaticbooleanisPalindrome(Strings){intleft=0;intright=s.length()-1;while(left<right){if(s.charAt(left)!=s.charAt(right)){returnfalse;}left++;right--;}returntrue;}

这道题非常重要,因为它同时考察了:

  • 字符串访问
  • 双指针移动
  • 边界判断
  • 提前返回

如果你能把这道题真正写熟,很多基础字符串题都会轻松很多。

字符串题的常见套路:先分类,再选方法

字符串题看起来很多,其实初学阶段大致可以先分成下面几类:

题型常见方法典型关键词
字符统计类遍历、计数、哈希表次数、频率、重复
反转类双指针、字符数组反转、回文、交换
匹配类扫描、比较、定位包含、查找、子串
过滤处理类条件判断、跳过无效字符空格、标点、大小写
构造类StringBuilder拼接、生成新字符串

字符串题不要上来就硬写,先判断题型,再决定用遍历、双指针、哈希表还是构造。

易错点:新手做字符串题最容易踩的坑

1. 用==比较字符串内容

在 Java 中:

  • ==比较的是引用地址
  • equals()比较的是字符串内容

例如:

Stringa="abc";Stringb=newString("abc");System.out.println(a.equals(b));// true

如果你要比较内容,优先考虑equals()

2. 忘记处理空字符串

例如:

Strings="";

很多人写代码时默认字符串一定有内容,结果一访问charAt(0)就出错。

3.charAt(i)越界

字符串下标范围是:

0 ~ s.length() - 1

任何访问超出这个范围,都会抛异常。

4. 反转时忘记移动指针

双指针题里,如果忘了写:

left++;right--;

程序就可能陷入死循环。

5. 子串匹配时边界写错

例如暴力匹配中,外层循环常见写法应该是:

i<=n-m

如果写错,就可能漏掉最后一个可能起点,或者直接越界。

6. 频繁用+拼接长字符串

在循环中不断使用字符串相加,性能通常不理想。
如果需要反复构造字符串,更推荐使用StringBuilder

publicstaticStringbuildString(intn){StringBuildersb=newStringBuilder();for(inti=0;i<n;i++){sb.append(i);}returnsb.toString();}

7. 没有先想清楚“题目比较的到底是什么”

字符串题里常见的比较对象有:

  • 字符
  • 整个字符串
  • 子串
  • 忽略大小写后的结果
  • 去掉非字母数字后的结果

如果不先想清楚,后面代码很容易越写越乱。

复杂度总结:字符串基础操作要有直觉

下面这些复杂度,建议尽快形成条件反射:

操作常见时间复杂度说明
按下标访问字符O(1)可以直接通过位置读取
遍历整个字符串O(n)逐个字符处理
统计字符次数O(n)一次扫描即可
反转字符串O(n)每个字符最多处理一次
暴力子串匹配O(n * m)主串和模式串逐步比较

这里的:

  • n表示主串长度
  • m表示模式串长度

总结:字符串入门,关键不是 API,而是套路意识

很多人学字符串时,会把注意力放在各种 Java 字符串方法上。
这些方法当然重要,但如果只停留在“背 API”,算法能力很难真正提升。

这篇文章你应该清楚的结论:

  1. 字符串本质上也是一种可遍历的数据序列
  2. 字符统计类问题,核心是“遍历 + 计数”
  3. 反转类问题,核心往往是“双指针”
  4. 匹配类问题,核心是“扫描 + 比较 + 边界”
  5. 很多字符串题的难点,不在思路,而在细节处理

字符串题表面看是在处理文字,实际上考的是遍历、比较、边界和套路识别能力。

http://www.cnnetsun.cn/news/2855599.html

相关文章:

  • Kaspersky Free(免费杀毒软件)
  • Python 单元测试与 Mock 体系全解
  • 【3.1Java基础】Java运算符常见错误排查:10个高频编译运行错误一网打尽
  • 还在用老版本jQuery?手把手教你复现CVE-2020-11022/11023这个XSS漏洞(附完整PoC)
  • 别再死记公式!用Python模拟带你直观理解停止等待与回退N帧协议的信道利用率
  • 考研摆烂后如何一周突击复试?北邮网安复试准备全流程(含密码学、408速成法)
  • 新手避坑指南:用大疆NAZA-LITE飞控组装F450无人机,从焊接电调到GPS校准的完整流程
  • ARM9微控制器LPC292x硬件设计实战:从数据手册到可靠电路
  • 从一次线上数据泄露事故复盘:我们是如何用签名和脱敏堵住越权漏洞的
  • 工业数据上云的‘翻译官’:实测KepOPC DA2UA如何桥接Windows OPC DA与跨平台应用
  • 别再傻傻分不清!用猫狗猪分类的例子,一次搞懂论文里的OA、mAcc、Instance和Class Accuracy
  • 动态群组密钥管理协议:原理、实现与优化
  • 不只是玩具:用金牛座脑波模块+ESP32,打造一个低成本的居家专注力监测‘小黑盒’
  • 告别盲目搜索:手把手教你用Keil MDK调试RT-Thread的RT_ASSERT死机问题
  • Arma3任务制作者必看:如何用SQF的ForEach和WaitUntil,让AI小队执行复杂巡逻逻辑
  • 语音RAG实战:构建端到端音频理解与原声回答系统
  • 告别IP依赖:在Vivado中直接调用MMCME2_ADV原语生成自定义时钟(以Zynq-7000为例)
  • 从零配置到上线:手把手带你用华为AC+AP搭建一个可用的企业Wi-Fi(含CAPWAP隧道详解)
  • 别让DRC吓到你!Cadence SPB17.4原理图检查的‘白名单’与‘黑名单’设置心得
  • 别再套模板了!我用这3个真实案例拆解GIS/遥感专业保研个人陈述怎么写(附避坑指南)
  • 别再用暴力搜索了!用动态规划5分钟搞定‘蚂蚁移动’这类网格路径问题(附C++代码)
  • 上市公司财报AI解析流水线:本地化、可验证、零API依赖
  • 用C++队列模拟流感传播:从NOI真题到游戏地图感染算法实战
  • AI简历优化:三重信号编码法突破ATS筛选
  • 别再只看GPS信号格了!手把手教你读懂手机/车载导航里的DOP值(精度衰减因子)
  • 别再死磕TII投稿了!我用LaTeX搞定IEEE论文格式的血泪经验(附模板下载与避坑清单)
  • OpenLayers测距踩坑记:从EPSG:4326坐标偏差到Vue中内存泄漏的排查与修复
  • GeoServer权限进阶:不用账号密码,用AuthKey插件实现API密钥式鉴权(2.25.2 Docker版)
  • 模板驱动型文档自动化:结构化内容生成的核心原理与实践
  • 你的Vue/React老项目可能中招了!排查并修复jQuery 3.5.0以下版本的XSS隐患