本文首发于我的个人博客:尾尾部落

1. KMP 算法

谈到字符串问题,不得不提的就是 KMP 算法,它是用来解决字符串查找的问题,可以在一个字符串(S)中查找一个子串(W)出现的位置。KMP 算法把字符匹配的时间复杂度缩小到 O(m+n) ,而空间复杂度也只有O(m)。因为“暴力搜索”的方法会反复回溯主串,导致效率低下,而KMP算法可以利用已经部分匹配这个有效信息,保持主串上的指针不回溯,通过修改子串的指针,让模式串尽量地移动到有效的位置。

具体算法细节请参考:

  • 字符串匹配的KMP算法
  • 从头到尾彻底理解KMP
  • 如何更好的理解和掌握 KMP 算法?
  • KMP 算法详细解析
  • 图解 KMP 算法
  • 汪都能听懂的KMP字符串匹配算法【双语字幕】
  • KMP字符串匹配算法1

1.1 BM 算法

BM算法也是一种精确字符串匹配算法,它采用从右向左比较的方法,同时应用到了两种启发式规则,即坏字符规则 和好后缀规则 ,来决定向右跳跃的距离。基本思路就是从右往左进行字符匹配,遇到不匹配的字符后从坏字符表和好后缀表找一个最大的右移值,将模式串右移继续匹配。 字符串匹配的KMP算法

2. 替换空格

剑指offer:替换空格 请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

public class Solution {public String replaceSpace(StringBuffer str) {StringBuffer res = new StringBuffer();int len = str.length() - 1;for(int i = len; i >= 0; i--){if(str.charAt(i) == ' ')res.append("02%");elseres.append(str.charAt(i));}return res.reverse().toString();}
}
复制代码

3. 最长公共前缀

Leetcode: 最长公共前缀 编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""。

首先对字符串数组进行排序,然后拿数组中的第一个和最后一个字符串进行比较,从第 0 位开始,如果相同,把它加入 res 中,不同则退出。最后返回 res

class Solution {public String longestCommonPrefix(String[] strs) {if(strs == null || strs.length == 0)return "";Arrays.sort(strs);char [] first = strs[0].toCharArray();char [] last = strs[strs.length - 1].toCharArray();StringBuffer res = new StringBuffer();int len = first.length < last.length ? first.length : last.length;int i = 0;while(i < len){if(first[i] == last[i]){res.append(first[i]);i++;}elsebreak;}return res.toString();}
}
复制代码

4. 最长回文串

LeetCode: 最长回文串 给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。在构造过程中,请注意区分大小写。比如 "Aa" 不能当做一个回文字符串。

统计字母出现的次数即可,双数才能构成回文。因为允许中间一个数单独出现,比如“abcba”,所以如果最后有字母落单,总长度可以加 1。

class Solution {public int longestPalindrome(String s) {HashSet<Character> hs = new HashSet<>();int len = s.length();int count = 0;if(len == 0)return 0;for(int i = 0; i<len; i++){if(hs.contains(s.charAt(i))){hs.remove(s.charAt(i));count++;}else{hs.add(s.charAt(i));}}return hs.isEmpty() ? count * 2 : count * 2 + 1;}
}
复制代码

4.1 验证回文串

Leetcode: 验证回文串 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 说明:本题中,我们将空字符串定义为有效的回文串。

两个指针比较头尾。要注意只考虑字母和数字字符,可以忽略字母的大小写。

class Solution {public boolean isPalindrome(String s) {if(s.length() == 0)return true;int l = 0, r = s.length() - 1;while(l < r){if(!Character.isLetterOrDigit(s.charAt(l))){l++;}else if(!Character.isLetterOrDigit(s.charAt(r))){r--;}else{if(Character.toLowerCase(s.charAt(l)) != Character.toLowerCase(s.charAt(r)))return false;l++;r--;} }return true;}
}
复制代码

4.2 最长回文子串

LeetCode: 最长回文子串 给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为1000。

以某个元素为中心,分别计算偶数长度的回文最大长度和奇数长度的回文最大长度。

class Solution {private int index, len;public String longestPalindrome(String s) {if(s.length() < 2)return s;for(int i = 0; i < s.length()-1; i++){PalindromeHelper(s, i, i);PalindromeHelper(s, i, i+1);}return s.substring(index, index+len);}public void PalindromeHelper(String s, int l, int r){while(l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)){l--;r++;}if(len < r - l - 1){index = l + 1;len = r - l - 1;}}
}
复制代码

4.3 最长回文子序列

LeetCode: 最长回文子序列 给定一个字符串s,找到其中最长的回文子序列。可以假设s的最大长度为1000。 最长回文子序列和上一题最长回文子串的区别是,子串是字符串中连续的一个序列,而子序列是字符串中保持相对位置的字符序列,例如,"bbbb"可以使字符串"bbbab"的子序列但不是子串。

动态规划: dp[i][j] = dp[i+1][j-1] + 2 if s.charAt(i) == s.charAt(j) otherwise, dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1])

class Solution {public int longestPalindromeSubseq(String s) {int len = s.length();int [][] dp = new int[len][len];for(int i = len - 1; i>=0; i--){dp[i][i] = 1;for(int j = i+1; j < len; j++){if(s.charAt(i) == s.charAt(j))dp[i][j] = dp[i+1][j-1] + 2;elsedp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);}}return dp[0][len-1];}
}
复制代码

5. 字符串的排列

Leetcode: 字符串的排列 给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。 换句话说,第一个字符串的排列之一是第二个字符串的子串。

我们不用真的去算出s1的全排列,只要统计字符出现的次数即可。可以使用一个哈希表配上双指针来做。

class Solution {public boolean checkInclusion(String s1, String s2) {int l1 = s1.length();int l2 = s2.length();int [] count = new int [128];if(l1 > l2)return false;for(int i = 0; i<l1; i++){count[s1.charAt(i) - 'a']++;count[s2.charAt(i) - 'a']--;}if(allZero(count))return true;for(int i = l1; i<l2; i++){count[s2.charAt(i) - 'a']--;count[s2.charAt(i-l1) - 'a']++;if(allZero(count))return true;}return false;}public boolean allZero(int [] count){int l = count.length;for(int i = 0; i < l; i++){if(count[i] != 0)return false;}return true;}
}
复制代码

6. 打印字符串的全排列

剑指offer:字符串的排列 输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

把问题拆解成简单的步骤: 第一步求所有可能出现在第一个位置的字符(即把第一个字符和后面的所有字符交换[相同字符不交换]); 第二步固定第一个字符,求后面所有字符的排列。这时候又可以把后面的所有字符拆成两部分(第一个字符以及剩下的所有字符),依此类推。这样,我们就可以用递归的方法来解决。

public class Solution {ArrayList<String> res = new ArrayList<String>();public ArrayList<String> Permutation(String str) {if(str == null)return res;PermutationHelper(str.toCharArray(), 0);Collections.sort(res);return res;}public void PermutationHelper(char[] str, int i){if(i == str.length - 1){res.add(String.valueOf(str));}else{for(int j = i; j < str.length; j++){if(j!=i && str[i] == str[j])continue;swap(str, i, j);PermutationHelper(str, i+1);swap(str, i, j);}}}public void swap(char[] str, int i, int j) {char temp = str[i];str[i] = str[j];str[j] = temp;}
}
复制代码

7. 第一个只出现一次的字符

剑指offer: 第一个只出现一次的字符 在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1.

先在hash表中统计各字母出现次数,第二次扫描直接访问hash表获得次数。也可以用数组代替hash表。

import java.util.HashMap;
public class Solution {public int FirstNotRepeatingChar(String str) {int len = str.length();if(len == 0)return -1;HashMap<Character, Integer> map = new HashMap<>();for(int i = 0; i < len; i++){if(map.containsKey(str.charAt(i))){int value = map.get(str.charAt(i));map.put(str.charAt(i), value+1);}else{map.put(str.charAt(i), 1);}}for(int i = 0; i < len; i++){if(map.get(str.charAt(i)) == 1)return i;}return -1;}
}
复制代码

8. 翻转单词顺序列

剑指offer: 翻转单词顺序列 LeetCode: 翻转字符串里的单词

借助trim()和 split()就很容易搞定

public class Solution {public String reverseWords(String s) {if(s.trim().length() == 0)return s.trim();String [] temp = s.trim().split(" +");String res = "";for(int i = temp.length - 1; i > 0; i--){res += temp[i] + " ";}return res + temp[0];}
}
复制代码

9. 旋转字符串

Leetcode: 旋转字符串 给定两个字符串, A 和 B。 A 的旋转操作就是将 A 最左边的字符移动到最右边。 例如, 若 A = 'abcde',在移动一次之后结果就是'bcdea' 。如果在若干次旋转操作之后,A 能变成B,那么返回True。

一行代码搞定

class Solution {public boolean rotateString(String A, String B) {return A.length() == B.length() && (A+A).contains(B);}
}
复制代码

9.1 左旋转字符串

剑指offer: 左旋转字符串 汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

在第 n 个字符后面将切一刀,将字符串分为两部分,再重新并接起来即可。注意字符串长度为 0 的情况。

public class Solution {public String LeftRotateString(String str,int n) {int len = str.length();if(len == 0)return "";n = n % len;String s1 = str.substring(n, len);String s2 = str.substring(0, n);return s1+s2;}
}
复制代码

9.2 反转字符串

LeetCode: 反转字符串 编写一个函数,其作用是将输入的字符串反转过来。

class Solution {public String reverseString(String s) {if(s.length() < 2)return s;int l = 0, r = s.length() - 1;char [] strs = s.toCharArray(); while(l < r){char temp = strs[l];strs[l] = strs[r];strs[r] = temp;l++;r--;}return new String(strs);}
}
复制代码

10. 把字符串转换成整数

剑指offer: 把字符串转换成整数 将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。

public class Solution {public int StrToInt(String str) {if(str.length() == 0)return 0;int flag = 0;if(str.charAt(0) == '+')flag = 1;else if(str.charAt(0) == '-')flag = 2;int start = flag > 0 ? 1 : 0;long res = 0;while(start < str.length()){if(str.charAt(start) > '9' || str.charAt(start) < '0')return 0;res = res * 10 + (str.charAt(start) - '0');start ++;}return flag == 2 ? -(int)res : (int)res;}
}
复制代码

11. 正则表达式匹配

剑指offer:正则表达式匹配 请实现一个函数用来匹配包括’.’和’*’的正则表达式。模式中的字符’.’表示任意一个字符,而’*’表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串”aaa”与模式”a.a”和”ab*ac*a”匹配,但是与”aa.a”和”ab*a”均不匹配

动态规划: 这里我们采用dp[i+1][j+1]代表s[0..i]匹配p[0..j]的结果,结果自然是采用布尔值True/False来表示。 首先,对边界进行赋值,显然dp[0][0] = true,两个空字符串的匹配结果自然为True; 接着,我们对dp[0][j+1]进行赋值,因为 i=0 是空串,如果一个空串和一个匹配串想要匹配成功,那么只有可能是p.charAt(j) == '*' && dp[0][j-1] 之后,就可以愉快地使用动态规划递推方程了。

public boolean isMatch(String s, String p) {if (s == null || p == null) {return false;}boolean[][] dp = new boolean[s.length()+1][p.length()+1];dp[0][0] = true;for (int j = 0; i < p.length(); j++) {if (p.charAt(j) == '*' && dp[0][j-1]) {dp[0][j+1] = true;}}for (int i = 0 ; i < s.length(); i++) {for (int j = 0; j < p.length(); j++) {if (p.charAt(j) == '.') {dp[i+1][j+1] = dp[i][j];}if (p.charAt(j) == s.charAt(i)) {dp[i+1][j+1] = dp[i][j];}if (p.charAt(j) == '*') {if (p.charAt(j-1) != s.charAt(i) && p.charAt(j-1) != '.') {dp[i+1][j+1] = dp[i+1][j-1];} else {dp[i+1][j+1] = (dp[i+1][j] || dp[i][j+1] || dp[i+1][j-1]);}}}}return dp[s.length()][p.length()];
}
复制代码

12. 表示数值的字符串

剑指offer: 表示数值的字符串 请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串”+100″,”5e2″,”-123″,”3.1416″和”-1E-16″都表示数值。 但是”12e”,”1a3.14″,”1.2.3″,”+-5″和”12e+4.3″都不是。

设置三个标志符分别记录“+/-”、“e/E”和“.”是否出现过。

public class Solution {public boolean isNumeric(char[] str) {int len = str.length;boolean sign = false, decimal = false, hasE = false;for(int i = 0; i < len; i++){if(str[i] == '+' || str[i] == '-'){if(!sign && i > 0 && str[i-1] != 'e' && str[i-1] != 'E')return false;if(sign && str[i-1] != 'e' && str[i-1] != 'E')return false;sign = true;}else if(str[i] == 'e' || str[i] == 'E'){if(i == len - 1)return false;if(hasE)return false;hasE = true;}else if(str[i] == '.'){if(hasE || decimal)return false;decimal = true;}else if(str[i] < '0' || str[i] > '9')return false;}return true;}
}
复制代码

13. 字符流中第一个不重复的字符

剑指offer: 字符流中第一个不重复的字符 请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符”go”时,第一个只出现一次的字符是”g”。当从该字符流中读出前六个字符“google”时,第一个只出现一次的字符是”l”。

用一个哈希表来存储每个字符及其出现的次数,另外用一个字符串 s 来保存字符流中字符的顺序。

import java.util.HashMap;
public class Solution {HashMap<Character, Integer> map = new HashMap<Character, Integer>();StringBuffer s = new StringBuffer();//Insert one char from stringstreampublic void Insert(char ch){s.append(ch);if(map.containsKey(ch)){map.put(ch, map.get(ch)+1);}else{map.put(ch, 1);}}//return the first appearence once char in current stringstreampublic char FirstAppearingOnce(){for(int i = 0; i < s.length(); i++){if(map.get(s.charAt(i)) == 1)return s.charAt(i);}return '#';}
}
复制代码

[算法总结] 13 道题搞定 BAT 面试——字符串相关推荐

  1. 如何学好 Linux、C++,并搞定 BAT 面试 作者/分享人:天千

    学好Linux运维需要做到以下几点 1.多做实验 实验环境完全可以通过VMware来模拟,模拟私有网络,模拟多台机器,要搞懂VMware提供的集中网络模式的工作原理(桥接网络.宿主机网络.NAT等), ...

  2. 一小时搞定计算机网络面试

    一小时搞定计算机网络面试 一.计算机网络体系结构参考模型: 七层协议的作用: 1.物理层:主要定义物理设备标准,如网线的接口类型.光纤的接口类型.各种传输介 质的传输速率等.它的主要作用是传输比特流( ...

  3. 搞定BAT Java面试题

    下面就Java常见的面试题做一个简单的总结,一句话掌握这些面试题,搞定BAT不是梦. 基本概念 操作系统中 heap 和 stack 的区别: 什么是基于注解的切面实现: 什么是 对象/关系 映射集成 ...

  4. 声音甜美的美女工程师已就位-帮你搞定React面试的疑难杂症 React面试优化教程

    声音甜美的美女工程师已就位-帮你搞定React面试的疑难杂症 React面试优化教程 React早已经是一线大厂前端的必备技术了,那么在前端跳槽过程中能够帮助同学们的加分的就是在面试这个环节了.Rea ...

  5. 13天搞定java_[Java基础] 魔乐科技教你13天搞定JAVA系列高端教程 视频教程 教学视频...

    资源介绍 课程目录:                            <魔乐科技教你13天搞定JAVA>第八天-01异常的捕获及处理.rar        58.04 MB < ...

  6. 程序员笔试面试最爱考察的算法,到底怎么搞定?

    作者 | 黄小斜 责编 | 屠敏 本文思维导图 什么是算法 上回我们有一篇文章,讲述了作为一个新人程序员,如何学习数据结构这门课程,其实呢,数据结构和算法是息息相关的,为什么这么说呢,因为数据结构本身 ...

  7. 搞定剑桥面试数学题番外篇2:使用多线程并发“加强版”

    0. 概览 我们在之前三篇博文中已经介绍了如何用多种语言(ruby.swift.c.x64 汇编和 ARM64 汇编)实现一道"超超超难"的剑桥数学面试题: · 有趣的小实验:四种 ...

  8. 恭喜不能发财,搞定大厂面试才行:动态规划问题的思路解析

    祝愿各位同学在新一年里身体健康,阖家欢乐.根据过往几十年的观察和经验发现,单单恭喜无法令人发财,我们只有成功找到好的工作,赢得一份称心的事业,发财才有机会,能够进入好的企业自然是实现自身财富提升的合理 ...

  9. 谈谈java面向对象之抽象,手把手带你搞定java面试之面向对象

    计算机语言晦涩难懂,打算利用通俗易懂的文字带领大家学习java基础.如果文中有什么错误的地方,欢迎大家在评论区指正,免得我误人子弟. Question:当面试JAVA开发岗位的时候,面试官最爱问的问题 ...

最新文章

  1. CCNP-19 IS-IS试验2(BSCI)
  2. 飞鹤乳业CIO:移动化让企业品牌和消费者紧密连接
  3. Centos7安装mysql社区版
  4. spring事务的传播属性
  5. 欢迎大家讨论一个关于界面显示的问题!!
  6. SAX EntityResolver 的作用
  7. [转载] 使用Python+OpenCV实现在视频中某对象后添加图像
  8. cass怎么多级放坡_cass土方计算考虑放坡
  9. 不是复制硅谷,而是与硅谷建立人脉
  10. MSDN帮助文档中文
  11. 有道云笔记怎么保存html,有道云笔记怎么保存网页?有道云笔记保存路径是什么...
  12. linux head
  13. python证件照_python opencv实现证件照换底的方法
  14. 2022高教杯数学建模E思路 超详细文字内容 数模E题
  15. 全球最强的女孩保养秘方大全
  16. 手势识别:使用EfficientNet模型迁移、VGG16模型迁移
  17. 如何编写Python爬虫
  18. 常见英语面试问答_40个常见的工作面试问答
  19. sentinel滑动时间窗口算法学习
  20. 芯片低功耗设计的两种常用EDA流程

热门文章

  1. BugkuCTF-WEB题web16备份是个
  2. 迷你世界显示未连接服务器成功,迷你世界登录未成功是什么意思 | 手游网游页游攻略大全...
  3. 和氟西汀类似的备注_撒狗粮:可爱又霸气的给男朋友的微信备注
  4. java bean状态_无状态和有状态企业Java Bean
  5. android 移除fragment,Android Viewpager+Fragment取消预加载及Fragment方法的学习
  6. 定时线程_SpringBoot定时任务,@Async多线程异步执行
  7. openglshader实现虚拟场景_opengl – 如何使用GLSL着色器将径向模糊应用于整个场景?...
  8. 《软件项目管理(第二版)》第 3 章——项目计划 重点部分总结
  9. html文档基本结构由哪三对,第3章 网页制作及HTML语言基本结构简介.ppt
  10. mysql key_len_浅谈mysql explain中key_len的计算方法