字符串一直是面试考察的重点,其破解之道的前提是对相关的api非常熟悉,然后在此基础上运用算法思想解题。本专题就从零开始,记录自己的字符串处理刷题路径,同时记录下相关api,以便对字符串处理游刃有余。
相关知识
1. String常用api
str.length() //求字符串str长度
str.charAt(index) //index处的字符
str.contains(anotherStr) //判断str中是否包含anotherStr
str.startsWith(searchWord) //判断str是否以searchword开头
Character.isDigit(char c) //可以判断c是不是数字
Character.isLetterOrDigit(char c)//判断c是否为数字或者字母
Character.toLowerCase(char c) //将c转换为小写(如果有的话,否则字符不变,如'_')
str.substring(start,end) //其中[start,end),参考下面3.
str.substring(start) //截取start及其以后的所有字符,参考下面3.
str.replace(char oldchar,char newchar) //将字符串中的oldchar替换为newchar
curstr.equals(targetstr) //比较当前curstr和targetstr是否相同
Objects.equals(curstr,targetstr) //同上比较字符串,不过可以避免curstr为空
2. StringBuilder常用方法
//StringBuilder res转String
sb.toString()
sb.substring()
sb.charAt(int index) //检索字符
sb.append(String str) //追加字符串
sb.append(char c) //追加字符
sb.append(int a) //追加a的字符形式
sb.deletecharAt(int index) //删除指定位置上的字符
sb.reverse() //对字符串进行翻转
3. String字符串截取 String.substring
String.substring(start,end) //其中[start,end)
String.substring(start) //截取start及其以后的所有字符
4. 字符串字典序比较 str.compareTo(a)
5. 字符串与数字间相互转换
字符串转数字
String str="123";
Integer num1=new Integer(str);
int num2= Integer.parseInt(str);
Integer num3=Integer.valueOf(str);
数字转字符串
Integer.toString(n)
6. char[]、String转换
//char[]转String
String.valueOf(char[])
//String转char[]
char[] a=s.toCharArray()
7. 正则表达式 s.matches(“”)
参考 正则表达式30分钟入门教程 (deerchao.cn)
8. 字符串分割 String.split()
参考java split方法怎么用
需要转义的字符如|.+*^?[/{}()$
,转义方法为字符前面加上\\
。反斜杠\
在字符串中也是需要转义的,字符串中想要使用一个反斜杠需要这么写\\
。
注意:当split的字符在字符串两侧时,会划分出来空字符串""
。
9. 格式保留问题 String.format()
参考JAVA字符串格式化——String.format()的使用
相关题目
1455.检查单词是否为句中其他单词的前缀
可以采用String的api startWith(searchWord)或者直接暴力枚举。
class Solution {
public int isPrefixOfWord(String sentence, String searchWord) {
String[] words=sentence.split(" ");
for(int i=0;i<words.length;i++){
String word=words[i];
int n=word.length();
int m=searchWord.length();
if(m>n) continue;
boolean flag=true;
for(int j=0;j<m&&flag;j++){
if(word.charAt(j)!=searchWord.charAt(j)) flag=false;
}
if(flag) return i+1;
}
return -1;
}
}
1408.数组中的字符串匹配
暴力枚举就好了
class Solution {
public List<String> stringMatching(String[] words) {
ArrayList<String> ans=new ArrayList<>();
int len=words.length;
for(int i=0;i<len;i++){
for(int j=0;j<len;j++){
if(i!=j&&words[j].contains(words[i])){
ans.add(words[i]);
//满足一个子字符串的要求就可以break
break;
}
}
}
return ans;
}
}
179.最大数
关键在于sort中compareTo
的运用,通过字典序的传递性,a在b前&&b在c前
则最终a在c
前,从而达到整体数字最大。
class Solution {
public String largestNumber(int[] nums) {
int len=nums.length;
String[] s=new String[len];
for(int i=0;i<len;i++){
s[i]=nums[i]+"";
}
Arrays.sort(s,(a,b)->(b+a).compareTo(a+b));
String res=String.join("",s);
return res.charAt(0)=='0'?"0":res; // 0能放在第一个就说明后面必然全是0
}
}
761.特殊的二进制序列
参考宫水三叶的题解,s
可以被可恰好划分为多个特殊的子串item
,当且仅当某个子串满足1和0个数相等就可以,因为原始序列s
可以保证第二个性质。第二个性质也表明,特殊子串item
必然为1...0
的模式。
因此可将本题构造分两步进行:1.对每个item
进行重排,使其本身调整为字典序最大;2.对不同item
之间进行重排,使最终s
整体字典序最大。对于1,我们可以对 item
中的 1...0
中的非边缘部分进行调整(递归处理子串部分),同时用list
将该子串进行收集。对于2,我们利用对list
进行重排,重排参考179.最大数,最终组合出结果。
class Solution {
public String makeLargestSpecial(String s) {
int len=s.length();
return dfs(s,0,len-1);
}
public String dfs(String s,int start,int end){
if(start>end) return "";
ArrayList<String> list=new ArrayList<>();
int count=0;
int split=start;
for(int i=start;i<=end;i++){
if(s.charAt(i)=='1') count++;
else count--;
if(count==0){
//步骤一,item本身重排
list.add("1"+dfs(s,split+1,i-1)+"0");
split=i+1;
}
}
//步骤二,item整体重排
//对ArrayList调用Collections.sort()
Collections.sort(list,(a,b)->(b+a).compareTo(a+b));
StringBuilder sb=new StringBuilder();
for(String ss:list){
sb.append(ss);
}
return sb.toString();
}
}
165.比较版本号
参考了加法模板,对两个字符串中的数字进行遍历比较,不够的补0.
class Solution {
public int compareVersion(String version1, String version2) {
String[]v1=version1.split("\\.");
String[]v2=version2.split("\\.");
int len1=v1.length;
int len2=v2.length;
int i=0;
int j=0;
while(i<len1||j<len2){
int a=i<len1?Integer.valueOf(v1[i]):0;
int b=j<len2?Integer.valueOf(v2[j]):0;
if(a==b){
i++;
j++;
continue;
}
return Integer.compare(a,b);
}
return 0;
}
}
809.情感丰富的文字
参考了比较模板,这题的思路主要是两条字符串都从前往后遍历,将没法扩展的情况直接return false
。至于能否拓展,主要还是看两者的字符以及对应的数量,比较好的实现方式是,直接把每个字符串中相同字符的数量也统计出来,从而判断拓展性。
class Solution {
public int expressiveWords(String s, String[] words) {
if(s.length()==0||words.length==0) return 0;
int cnt=0;
for(String w:words){
if(isStrechy(s,w)) cnt++;
}
return cnt;
}
//判断b能否拓展成a
public boolean isStrechy(String a,String b){
int len1=a.length();
int len2=b.length();
int i=0;
int j=0;
while(i<len1&&j<len2){
char c1=a.charAt(i);
char c2=b.charAt(j);
int cnt1=0;
int cnt2=0;
while(i<len1&&a.charAt(i)==c1){
i++;
cnt1++;
}
while(j<len2&&b.charAt(j)==c2){
j++;
cnt2++;
}
if(c1!=c2||cnt1<cnt2||cnt1!=cnt2&&cnt1<3) return false;
}
//当一个字符串中出现另一个字符串中没有出现的字符时,也没法扩张
return i==len1&&j==len2;
}
}
640.求解方程
总的来说就是要模拟移项的过程,将算式转换为factor*x=value
的形式求解,其中主要是对x的系数factor
以及value
的求解,充分运用str.split()
。
class Solution {
public String solveEquation(String equation) {
String[] eq=equation.split("=");
String[] eql=eq[0].replace("-","+-").split("\\+");
String[] eqr=eq[1].replace("-","+-").split("\\+");
int factor=0;
int value=0;
for(String s:eql){
if("x".equals(s)){
factor+=1;
}else if("-x".equals(s)){
factor+=-1;
}else if(s.contains("x")){
//正负数都加上
factor+=Integer.parseInt(s.substring(0,s.length()-1));
}else if(!s.equals("")){
value-=Integer.parseInt(s);
}
}
for(String s:eqr){
if("x".equals(s)){
factor-=1;
}else if("-x".equals(s)){
factor-=-1;
}else if(s.contains("x")){
//正负数都加上
factor-=Integer.parseInt(s.substring(0,s.length()-1));
}else if(!s.equals("")){
value+=Integer.parseInt(s);
}
}
if(factor==0){
if(value==0) return "Infinite solutions";
else return "No solution";
}else{
return "x="+value/factor;
}
}
}
1417.重新格式化字符串
分别统计出现的字母和数字,差值<=1的情况下,从数量较大的那种字符开始放起。
class Solution {
public String reformat(String s) {
Deque<Character> cq=new ArrayDeque<>(), numq=new ArrayDeque<>();
int len=s.length();
for(int i=0;i<len;i++){
char c=s.charAt(i);
if(c>='0'&&c<='9') numq.offer(c);
else cq.offer(c);
}
int cqSize=cq.size();
int numqSize=numq.size();
if(Math.abs(cqSize-numqSize)>1) return "";
StringBuilder sb=new StringBuilder();
if(cqSize>=numqSize){
for(int i=0;i<len;i++){
if(i%2==0) sb.append(cq.poll());
else sb.append(numq.poll());
}
}else{
for(int i=0;i<len;i++){
if(i%2==0) sb.append(numq.poll());
else sb.append(cq.poll());
}
}
return sb.toString();
}
}
828.统计子串中的唯一字符
模拟+乘法原理应用题,原问题为求所有子数组的唯一字符数量和,其可等价为求每个 s[i]
对答案的贡献,即每个 s[i]
可作为多少个子数组的唯一元素,这是乘法原理运用的地方。
参考宫水三叶的题解,其中两数组l
、r
统计不同字符出现的左右边界(包含出现的相同字符)方法值得学习。cnts
数组记录最近的左右边界,能够在O(1)
的时间内找到相同的值出现的最近边界。
class Solution {
public int uniqueLetterString(String s) {
int len=s.length();
//s中每个字母左右两边出现相同字符的位置
int[]l=new int[len];
int[]r=new int[len];
//cnts用于统计从左到右出现某个字符的最近左边界,默认-1
int[] cnts=new int[26];
Arrays.fill(cnts,-1);
for(int i=0;i<len;i++){
int gap=s.charAt(i)-'A';
l[i]=cnts[gap];
cnts[gap]=i;
}
//cnts用于统计从右到左出现某个字符的最近右边界,默认len
Arrays.fill(cnts,len);
for(int i=len-1;i>=0;i--){
int gap=s.charAt(i)-'A';
r[i]=cnts[gap];
cnts[gap]=i;
}
int ans=0;
for(int i=0;i<len;i++){
ans+=(i-l[i])*(r[i]-i);
}
return ans;
}
}
1694.重新格式化电话号码
以4个字符作为分割标准。
class Solution {
public String reformatNumber(String number) {
StringBuilder sb=new StringBuilder();
for(char c:number.toCharArray()){
if(Character.isDigit(c)){
sb.append(c);
}
}
String s=sb.toString();
int len=s.length();
int cur=0;
StringBuilder res=new StringBuilder();
//剩余大于4个,放入3个
while(len-cur>4){
res.append(s.substring(cur,cur+3));
res.append('-');
cur+=3;
}
//等于4个的时候,每2个放入
if(len-cur==4){
res.append(s.substring(cur,cur+2));
res.append('-');
cur+=2;
res.append(s.substring(cur,cur+2));
cur+=2;
}
//小于4个,直接放
if(len-cur<4){
res.append(s.substring(cur,len));
}
return res.toString();
}
}
1784.检查二进制字符串字段
统计有间隔的连续1,就是看第一个1以及后面是否会有0分割的1
class Solution:
def checkOnesSegment(self, s: str) -> bool:
#统计间隔的连续1
gen=(s[i]=='1' and (i==0 or s[i-1]=='0') for i in range(len(s)))
return sum(gen)<2
811.子域名访问计数
采用Python的字典做哈希,统计每个后缀出现的次数,其中用域名中的"."做分割值得学习,需要取当前点后domain[i+1:]
当后缀。
class Solution:
def subdomainVisits(self, cpdomains: List[str]) -> List[str]:
# 用字典存储各个域名出现的次数
# 从前往后找各个后缀的域名
dic = defaultdict(int)
for s in cpdomains:
temp= s.split(' ')
time,domain=int(temp[0]),temp[1]
# 将当前完整的域名加入
dic[domain]+=time
for i in range(len(domain)):
if domain[i]=='.':
dic[domain[i+1:]]+=time
return [str(dic[c])+" "+c for c in dic.keys()]