小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
原文位于 github仓库-正在起步阶段的前端知识库,其中记录了一名前端初学者的学习日记🤔&学习之路点点滴滴的记录(练手demo🧑💻,必会知识点🧐)
欢迎大家来贡献更多“前端er必会知识点”🧑🎓/分享更多有意义的demo❤️!(请给这个年幼的小仓库更丰富的内容吧🥺🥺🥺)
本篇来说一下字符串
字符串在算法面试中,单独考察的机会并不多,同样倾向于和一些经典算法(后面会讲的)结合来体现区分度。本节我们先解决只需要数据结构知识做基础就可以解决的字符串问题。
两个字符串相关的基本算法技能
- 反转字符串
- 判断是否为回文字符串
这两个技能偶尔也会单独命题,但整体来看在综合性题目中的考察频率较高,需要大家着重熟悉、反复练习和记忆,确保真正做题时万无一失。(链表的题里面这二位也是经典得不行勒!)
综上,本篇基本内容为:
- 反转字符串
- 回文字符串
反转字符串
直接调用API即可(还有其他的方法,比较体现技巧)
一些公司一面为了试水,有时会单独考这个操作(之前就看过一篇面经里有提到这个)
let str = "bytedance";
let res = str.split("").reverse().join("");// "ecnadetyb"
这里的核心主要是数组的API reverse()
来做题~
344. 反转字符串
- 双指针
var reverseString = function(s) {
let i = 0, j = s.length - 1;
while(i < j){
[s[i], s[j]] = [s[j], s[i]];// 解构赋值快速交换两个数的位置~
i++;
j--;
}
// return s;这里的字符串没有被改变,依旧是原来的顺序,不理解的可以在下一题试一试,看看还能不能这么反转
// 题目中每组[s[i],s[j]]的顺组改变了,最终返回一个反转好的字符数组
};
541. 反转字符串 II
与344的反转字符串容易搞混的点在于:
344中测试代码接收的结果为[s[s.length-1],...s[1],s[0]]——是一个字符数组
而本题要求返回字符串,需要对数组&字符串的转换很熟悉!
var reverseStr = function(s, k) {
let loop = Math.ceil(s.length / (2*k));// 需要进行对撞双指针遍历的轮数loop
let arr = s.split("");// 将字符串拆成数组,方便最后返回结果arr.join(""),不然交换元素不能改变字符串本身
for(let m = 0; m < loop; m++){
// 每轮双指针的位置如下:(也是本题的关键)
let i = m*2*k;
let j = (s.length - i) < k ? s.length - 1 : i + k - 1;// 若剩余字符<k 则j指针位于字符串的末尾(将剩余字符全部反转)
while(i < j){
[arr[i], arr[j]] = [arr[j], arr[i]];
i++;
j--;
}
}
return arr.join("");
};
回文字符串
判断一个字符串是否回文是必备技能!
回文字符串例子
let str = "gooddoog"
- 【1】使用额外的空间存一个新字符串,看看与最开始的是否是同一个
最简单的想法,但是空间复杂度为 O(N)
let newStr = str.split('').reverse().join('');
return newStr === str;
- 【重要】【2】利用字符串以中间为轴的对称性,使用双指针
for(let i = 0; i < str.length; i++){
// 遍历字符串的前半部分,如果与后半部分相同,则满足对称性
if(str[i] !== str[str.length - i]){
return false;
}
}
return true;
125. 验证回文串
这题一个比较重要的点是要做数据预处理,把空格、逗号、冒号啥的都去除XD
s = "I:am:bill, llib ma I";// 这个字符串回文的必要条件是剔除掉空格、特殊符号们
- 使用正则表达式进行数据预处理
这种方法一劳永逸,一次性去除特殊字符
let newS = s.toLowerCase().match(/[a-z0-9]+/g);
console.log(newS);// ['i','am', 'bill', 'llib', 'ma', ]
// 使用match方法在字符串内索引指定值/找到一个或多个正则表达式的匹配
// 查找从大写A到小写z的字符,并返回匹配值
- 写一个判断遍历到的字母是否位于 a-z / 0-9 之间 的函数 进行数据预处理
这种方法要求进行遍历,并在遍历过程中进行调用函数,判断对应字符是否符合要求
另外注意:在JS中进行字符串的比较大小实际上就是比较它们的ASCII码值的大小~如下:
建议搭配双指针使用
function isValid(str){
return str >= 'a' && str <= 'z' || str >= '0' && str <= '9';
}
// 如果遍历到的字母
将数据预处理好了之后,有三种经典的解决方法
是的,就是reverse、 双指针、栈三种方法
- 【1】正则匹配 + reverse法
reverse法非常适合与正则表达式搭配对题目进行秒杀
var isPalindrome = function(s) {
let valid = s.toLowerCase().match(/[a-z0-9]+/g);// valid为进行正则匹配后筛选出来的数组
if(!valid){
return true;
}
let str = valid.join("");// 数据预处理(正则匹配)后得到的字符串
let comp = str.split("").reverse().join("");// 将字符串翻转
return comp === str;
};
- 【2】特殊字符处理函数 + 双指针
var isPalindrome = function(s) {
var str = s.toLowerCase();
// 定义好一头一尾的双指针
let i = 0;
let j = str.length - 1;
while(i < j){
// i/j不符合下面的isValid就推动左/右指针 并结束本轮迭代
if(!isValid(str[i])){
i++;
continue;
}
if(!isValid(str[j])){
j--;
continue;
}
if(str[i] !== str[j]){
return false;
}
i++;
j--;
}
return true;
};
var isValid = function(str){
return (str >= 'a' && str <= 'z') || (str >= '0' && str <= '9');
}
- 【3】正则匹配 + 栈
var isPalindrome = function(s) {
let valid = s.toLowerCase().match(/[a-z0-9]+/g);
if(!valid){
return true;
}
let str = valid.join("");// 正则匹配过后获得的字符串
let stack = [];
let mid = str.length >> 1;// 设置中间的位置,入栈至str[mid - 1]再遍历后续内容 并与栈中内容一一比对
for(let i = 0; i < mid; i++){
stack.push(str[i]);
}
// 入栈完毕,接下来进行比对
// 额外注意:如果字符串长度为奇数,应该跳过中间的字符进行比对
if(str.length % 2){
mid++;
}
for(let i = mid; i < str.length; i++){
let comp = stack.pop();// 将元素一一出栈
if(comp !== str[i]){
return false;
}
}
return true;
};
680. 验证回文字符串 Ⅱ
经典衍生问题
本质思想依旧是利用回文字符串的对称性——自然而然地想到了双指针~
要记住!
- 对称性
- 双指针
可以解决大部分回文字符串相关问题!
另外本体需要我们头脑清晰地创建一个工具方法——isPalindrome(s, i, j) 判断字符串s从i到j是否局部回文!
var validPalindrome = function(s) {
let i = 0;
let j = s.length - 1;
while(i < j){
if(s[i] !== s[j]){
return isPalindrome(s, i + 1, j) || isPalindrome(s, i, j - 1);
}
i++;
j--;
}
return true;
};
// 判断从i到j的s是否回文!跳过某一项之后局部回文,则整体回文!
// eg: cuppucu
// 先判断 uppucu 发现不回文
// 再判断 cuppuc 发现回文
var isPalindrome = function(s, i, j){
while(i < j){
if(s[i] !== s[j]){
return false;
}
i++;
j--;
}
return true;
}