Unit 5 字符串
Q1:判断两个字符串是否是变形词
Q2:求字符串中各数字之和
Q3:去掉字符串中连续出现K个0的子串
Q4:判断两个str是否互为旋转词
Q5:将整数字符串转成整数值(如果不是整数字符串则返回0)
Q6:将字符串str中连续出现的指定字符串from替换成字符串to
Q7:得到字符串的统计字符串
Q8:判断字符串中是否所有字符都只出现过一次
Q9:在有序且含空的数组中查找字符串(的位置)
Q10:使字符串中单词逆序
Q11:将字符串的前size部分移到后面去
Q12:求字符串数组中两个字符串的最小距离
Q13:给定字符串str,判断其是不是整体有效的括号字符串
Q14:第一个只出现一次的字符
Q15:0左边必有1的二进制字符串数量
Q16:拼接str[]中字符串产生字典顺序最小的字符串
Q17:找到字符串中的最长无重复子串
Q18:求最小覆盖子串
Q19:回文最小分割数
Q20:前缀树的实现
Q21:替换字符串中空格
Q22:正则表达式匹配
Q23:表示数值的字符串
Q24:打印出字符串的所有排列
Q25:字符流中第一个不重复的字符
unit 5 Q1:判断两个字符串是否是变形词
变形词的定义:如果str1和str2出现的字符种类一样,每个字符出现的次数也一样,则称str1和str2互为变形词。
例:str1=“123”,str2=“231”,str1和str2互为变形词
time:2019/09/01
思路:
设置一个哈希表hashtable,(k,v),key记录str1中出现过的字符,value是str1中该字符出现的次数
1.str1和str2出现空字符串,返回false;str1和str2长度不同,返回false。
1.遍历str1,根据str1建立hashtable。规则:hashtable中有当前字符,则其value做++操作,再put回去;没有当前字符则 put(当前字符,1)
2.再遍历str2,对str2中字符依次判断是否在hashtable中。
如果hashtable中没有该字符,或者该字符对应的value为0,则说明出错了,返回false;
否则对该hashtable[cur]- -。
3.成功出str2的遍历,说明考验成功是变形词,返回true
PS:
Java中HashTable对象的key和value都是object类型的,想要找到指定key对应的value并赋给int型变量,可做如下操作:
// Object类型 --> String类型 --> int类型
int value=Integer.parseInt(String.valueOf(hashtable.get(key)));
HashTable的更新:
hashtable.put(key,value);
PPS:
附Java Hashtable类的文档:
代码:U5Q1_Anagram.java
unit 5 Q2:求字符串中各数字之和。
例:str1=“A1CD2E33”,和为1+2+33=36;
str2=“A-1B–2C–D6E”,和为(-1)+2+6=7。
time:2019/09/02
思路:
字符串中的每个字符无非是三种,字母/数字/负号。
1.设置sum为累加和,初值为0;
num为当前的数,初值为0;
cur为当前字符
posi为num前面的符号,初值为true,代表正。
2.逐字符遍历字符串。
(1).如果当前字符是数字,则置num为(num*10+cur)
(2).else,则当前字符不是字母就是负号。
无论上述哪种都代表着一个数字结束了,要根据posi判断符号,将num加入或减入sum。重置num
- (a).如果当前字符是负号:
- i.当前字符不是第一个且前一个字符也是负号:对posi取反(posi=!posi)
- ii.else:当前字符是第一个或是前一个不是负号,当前字符即为第一个负号:置posi为false
- (b).当前字符不是负号,那它就是字母。置posi为true
3.遍历完字符串后,如果最后一个字符是数字,那最后一个带posi的num还没被加入sum中。
因此在遍历字符串的for循环外,根据posi判断符号,将最后一个num加入或减入sum。
4.返回sum,即为字符串中各数字的和
PS:
//char类型 --> int类型
int cur=Character.getNumericValue(strChar[i]);
代码:U5Q2_SubstrSum.java
unit 5 Q3:去掉字符串中连续出现K个0的子串
例:str=”A00B”,k=2,结果为"AB";
str=”A0000B000”,K=3, 结果为”A0000B”
time:2019/09/03
思路:
1.设置start用于指向每一个0组合的第一个下标,初值为-1
设置count记录每一个0组合当前为止的位数,初值为0
2.遍历str。
(1).如果当前字符是0:如果start等于-1代表当前0是0组合的第一个,更新start为当前下标i;否则当前的0前面还有0,start不变。不论如何都要count++。
(2).如果当前字符不是0:只有count==k满足时,要删除从start到当前i-1的所有位置的0。其他情况(前面的是0组合但不是k个 / 前面一个不是0)都不需要做操作。
不管做不做删除操作,都要置start为初值-1、count为初值0,迎接下一个0组合。
3.遍历完之后,如果最后一个字符是0,得再来一次判断是否k个,如果k个就删除的操作。
PS:null的ascii码为0。想要删哪个元素,就把char[]数组中该元素置为0。再用String.valueOf(char[])函数化char类型为string类型,中间的null就会被跳过,达到删除的目的。
//char类型 --> String类型
String newStr = String.valueOf(charStr);
代码:U5Q3_RemoveK0Substr.java
unit 5 Q4:判断两个str是否互为旋转词
要求时间复杂度为O(N)
旋转词的定义:把字符串str1前面任意长度挪到后面,形成字符str2,则称str1和str2互为旋转词
time:2019/09/09
思路:
1.先判断str1和str2长度是否相同,如果不同直接返回false;
2.置首尾相连的str3为str1+str1,判断str2是否在str3中:
如果在str2在str3中,是旋转词,返回true;
不在则不是旋转词,返回false。
3.容易想到的BF算法解决字符串匹配问题的时间复杂度为O(
N
2
N^2
N2),用KMP算法解决字符串匹配问题的时间复杂度为O(N)。
isContain_KMP(String str1,String str2)就是KMP算法的实现(2019/09/09还未实现)。
unit 5 Q5:将整数字符串转成整数值(如果不是整数字符串则返回0)
例:
str=“123”,返回123.
str=“023”,返回0。(不符合习惯)
str=“A13”,返回0。(不是整数字符串)
str="-012",返回0。(不符合习惯)
思路:
首先要判断是否字符串是否是合法的整数字符串。先判断开头,再遍历后面。以下三类情况,开头不合法。
boolean isValid(char[] charStr):
1.如果开头是0且长度大于1,不合法,返回false;
2.如果开头是-,且 长度仅为1或者第二个字符是0,不合法,返回false;
3.如果开头不是(0到9)也不是-,那就是其他字符(如字母),返回false。
从第二个开始遍历,出现任何不是(0到9),都不合法,返回false
再转换。主要要判断是否超过了32位整数的最大最小值,超过了会溢出。
int strToInt(String str):
-
先用isValid判断是否合法,不合法直接返回0。
-
置isPos:boolean型,判断是否为正。
置maxShang:32位Int最大值除以10的商。 maxYushu:32位Int最小值除以10的余数。
置minShang_absolute:32位Int最小值除以10的商的绝对值。 minYushu_absolute:32位Int最小值除以10的余数的绝对值。
(之所以分最大最小值,是因为32位Int的最大最小值不一样,为方便我都用绝对值计算,最后再添加符号)
置res:总转换成int型的结果。 cur:当前字符转换成int型。 -
for循环遍历字符串的char数组,正数从i=0开始,负数从i=1开始。
(1).取当前字符,用Character.getNumericValue()函数将char型转换成int型
(2).如果是正数,两种情况会导致溢出:
--------(i).旧res已大于maxShang,res乘以10之后加多少都比32位Int的最大值大
--------(ii).旧res等于maxShang,但cur大于maxYushu,(res * 10+cur)肯定大于最大值
如果是负数,依然两种情况导致溢出(道理同正数):
--------(i).res>minShang_absolute
--------(ii).(res==minShang_absolute&&cur>minYushu_absolute)
一旦溢出,返回0。
不溢出做res=res*10+cur -
出循环后根据isPos决定res的符号,返回res
代码:
public class Solution {
public int StrToInt(String str) {
if(str==null||str.equals(""))
return 0;//空,转不了
char[] charString=str.toCharArray();
//先看合不合法
if(!isValid(charString))
return 0;
//true开头是符号 false开头是数字
boolean isSymbolFirst=(charString[0]<'0'||charString[0]>'9')?true:false;
boolean isPosi=true;
if(isSymbolFirst)
isPosi=(charString[0]!='-')?true:false;//true正数 false负数
//不能计算出res直接比,因为res不能比MAX_VALUE大。只能未雨绸缪先比
int maxShang=Integer.MAX_VALUE/10;
int maxYushu=Integer.MAX_VALUE%10;
int minShang=-1*(Integer.MIN_VALUE/10);
int minYushu=-1*(Integer.MIN_VALUE%10);
int res=0;//int型转换结果
int cur=0;//int型当前字符
//开头有符号,从第二个开始
for(int i=isSymbolFirst?1:0;i<charString.length;++i){
cur=Character.getNumericValue(charString[i]);
//res已大于maxShang,res乘以10之后加多少都比32位Int的最大值大
//旧res等于maxShang,但cur大于maxYushu,(res*10+cur)肯定大于最大值
if(isPosi){
if(res>maxShang||(res==maxShang&&cur>maxYushu))//正数
return 0;
}else
if(res>minShang||(res==minShang&&cur>minYushu))//负数
return 0;
res=res*10+cur;
}
res=isPosi?res:(-1*res);
return res;
}
private boolean isValid(char[] charStr){
//不合法的四种情况:
//1.开头为0,长度大于1(例:01)
if(charStr[0]=='0'&&charStr.length>1)
return false;
//开头为-,长度等于0或者第二个字符是0(例:-,-0)
if(charStr[0]=='-'&&(charStr.length==1||charStr[1]=='0'))
return false;
//开头既不为0-9也不为-也不为+(例:a)
if((charStr[0]<'0'||charStr[0]>'9')&&(charStr[0]!='-')&&(charStr[0]!='+'))
return false;
//4.从第二个开始遍历。出现0-9以外的字符不合法
for(int i=1;i<charStr.length;i++){
if(charStr[i]<'0'||charStr[i]>'9')
return false;
}
return true;
}
}
unit 5 Q6:将字符串str中连续出现的指定字符串from替换成字符串to
例:str=“123abc”,from=“abc”,to=“4567”,返回"1234567"。
str=“123”,from=“abc”,to=“456”,返回"123"。
str=“123abcabc”,from=“abc”,to=“X”,返回"123X"。
time:2019/09/11
思路:
总体思路为两步,第一步将str中所有的from的字符换成0
第二步,遍历换0后的str,将所有连续的0都换成to
Q:为啥不能直接将from换成to?
A:如果from连续出现,这样就会连着两个to。还是全换成0稳妥。
1.void clear(char[] charStr,int end,int len):
- 用一个while循环,在charStr中从下标end开始向前数len个字符,将它们全部置为0.
2.String replace(String str,String from,String to) {
总函数:将连续的0置换成to。
- 先遍历str,找到其中含有的from部分,调用clear( )函数,将str中的所有from都清为0。
- for循环遍历str,不为0的就直接加到res(String型)后面;
如果为0则再加个while小循环,找到str[i+1]不是0为止。
此时从进while到出while循环的两个i之间为一段连续的0,(0当然不加进res)将to加到res后面;
出while循环res便加好了,返回res。
代码:U5Q6_ReplaceSpeStr.java
unit 5 Q7:得到字符串的统计字符串
例:“aaabbadddffc"的统计字符串为"a_3_b_2_a_1_d_3_f_2_c_1”
time:2019/09/12
思路:
按照自己的想法来即可。
采用for循环套while循环的遍历方式
每一轮for循环都出一次连续子串的统计,for中的while循环是为了向后找到子串的最后一个
1.置count计数器统计连续的个数,初值为0;置res(String型)得到结果字符串
2.开始for循环之后,res加上当前字符和_
(1).用while循环向后遍历,count统计连续的个数,直到子串的最后一个
(2).res加上当前count,count清零。
(3).如果当前字符不是最后一个,则再加个_
3.返回res
代码:U5Q7_getCountStr.java
unit 5 Q8:判断字符串中是否所有字符都只出现过一次
time:2019/09/17
要求1:时间复杂度O(N)
要求2:额外空间复杂度O(1),时间复杂度尽可能低
思路:
要求1:设置HashMap存所有字符即可,空间换时间。
要求2:先排序,再判断。
题目即转化为寻找额外空间复杂度为O(1),时间复杂度尽可能低的排序算法。选择堆排序。
(2019/09/17)先跳过,写到堆排序时再补
代码:U5Q8_isUnique.java
unit 5 Q9:在有序且含空的数组中查找字符串(的位置)
time:2019/09/18
思路:
折半查找的思路,多一些需要应对null的部分
1.置low为0,high为(len-1),mid为(low+high)/2
2.
(1)如果mid对应字符不为空且等于str:先记录当前mid为res,同时再high=mid-1向左走试图找到最左(如果找到了即更新res,没找到则位置为当前res)
(2)否则如果mid对应字符不为空且大于str:当前mid太大了,向左走,即high=mid-1
(3).否则如果mid对应字符不为空且小于str:当前mid太小了,向右找,即low=mid+1
(4).否则:当前mid对应字符为空,要向左找到第一个不为空的。如果左边全为空那再找右边的
- (a).用一个while循环找到左半边的第一个不为null的,下标为i
- (b).如果i对应字符比str大或者i<low(即左半边都是null),去右边找,low=mid+1
- [c).否则如果i对应字符等于str,置res为i,继续向左找high=i-1(参考2.(1))
- (d).否则i对应字符比str小,向左走,high=i-1
最后的res即是str在strs[]中的位置
代码:U5Q9_findIndex.java
unit 5 Q10:使字符串中单词逆序
剑指offer 58_1 牛客链接
例:若有字符串"dog loves pig",反转后为"pig loves dog"
若有字符串"I’m a student.",反转后为"student. a I’m"
要求:时间复杂度为O(N),额外空间复杂度O(1)
date:2019/09/20
思路:
1.先写好部分反转的reversePart函数
2.整体思路为,先整体反转使"dog loves pig"反转为"gip sevol god",再对反转后的字符串划分单词,对每个单词反转,得到"pig loves dog"
3.具体实现:
void reversePart(char[] charStr,int start,int end):
一个while循环,用temp暂存。每一轮都头尾互换,start++,end–。
String rotateEachWord(String str):
先调用一次reversePart做全部反转;
再两层while循环,第一层i控制,第二层j。第一层while循环每一轮都会找出一个单词,i指向单词首,j指向单词尾,
找单词操作用第二层while循环完成,找到后再调用一次reversePart对单词反转。结束第一轮循环时将j+1赋给i。
重点在于实现的效率和准确。
代码:
//date:2020/02/21实现
public class Solution {
public String ReverseSentence(String str) {
String res="";
if(str==null||str.equals(""))
return res;
char[] charStr=str.toCharArray();
int len=charStr.length;
//先将字符串全部倒置
ReversePart(charStr,0,len-1);
//找到一个个单词,将单词倒置过来
int i=0,j;//i指向单词开头,j指向单词结尾
while(i<len){
j=i;
while(j+1<len&&charStr[j+1]!=' ')
j++;//找到单词结尾的位置j
ReversePart(charStr,i,j);//把找到的单词反转
i=j+2;//更新i
}
//把结果数组charStr转回string类型
for(int k=0;k<len;k++)
res+=charStr[k];
return res;
}
private void ReversePart(char[] str,int start,int end){
while(start<=end){
char temp=str[start];
str[start]=str[end];
str[end]=temp;
start++;
end--;
}
}
}
unit 5 Q11:将字符串的前size部分移到后面去
例:若有字符串"ABCDE",size=3,则调整后为"DEABC"
要求:时间复杂度为O(N),额外空间复杂度O(1)
date:2019/09/20
思路:先将"ABCDE"–>“CBADE”–>“CBAED”–>“DEABC”
两部分先分别反转,再一起反转即可。
理解并记住这套路。
//date:2020/02/21
public class Solution {
public String LeftRotateString(String str,int n) {
String res="";
if(str==null||str.equals(""))
return res;
char[] charStr=str.toCharArray();
int len=charStr.length;
reversePart(charStr,0,n-1);
reversePart(charStr,n,len-1);
reversePart(charStr,0,len-1);
for(int i=0;i<len;i++)
res+=charStr[i];
return res;
}
private void reversePart(char[] str,int start,int end){
while(start<=end){
char temp=str[start];
str[start]=str[end];
str[end]=temp;
start++;
end--;
}
}
}
代码:U5Q11_rotatePart.java
unit 5 Q12:求字符串数组中两个字符串的最小距离
例:strs=[“1”,“3”,“3”,“3”,“2”,“3”,“1”],str1=“1”,str2=“2”,返回2
date:2019/09/21
思路:
设置index1存str1对应的下标,设置index2存str2对应的下标,index1、index2初值均为-1。设置minD记录最小距离,初值为Integer.MAX_VALUE。
for循环遍历strs[],遇到str1或str2即更新index1或index2。每轮遍历如果|index1-index2|小于当前minD则更新minD
注意,如果index1和index2有一个为-1,说明有一个还没遇到过,不更新minD
代码:U5Q12_minDistance.java
unit 5 Q13:给定字符串str,判断其是不是整体有效的括号字符串
有效字符串定义与举例:
str="()",返回true;str="(()())",返回true;str="(())",返回true。
str="())",返回false;str="()(",返回false;str="()a()",返回false。
date:2019/09/22
思路:
这道题不难,考察的是有没有把所有情况都考虑到
有三种情况不是符合上述定义的:
1.遍历中:不是 “(” 或者 “)” 的
2.遍历中:")" 比 “(” 多
3.全部遍历完:"(" 和 “)” 不一样多
代码:U5Q13_isBracketValid.java
unit 5 Q14:第一个只出现一次的字符
剑指offer 50 牛客链接
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).
date:2020/02/18
思路:
设置一个hashmap<Character,Integer>记录字符串中每个字符和其出现次数
遍历两次,第一次更新hashmap;第二次找到第一个只出现一次的字符,返回它的下标。
代码:
public int FirstNotRepeatingChar(String str) {
if(str==null||str.equals(""))
return -1;
HashMap<Character,Integer> hashmap=new HashMap<Character,Integer>();
char[] charStr=str.toCharArray();
//遍历两遍。第一遍更新hashmap<字符,次数>\
for(int i=0;i<charStr.length;i++){
if(!hashmap.containsKey(charStr[i]))
hashmap.put(charStr[i],1);
else{
int times=hashmap.get(charStr[i]);
hashmap.put(charStr[i],times+1);
}
}
//再遍历一遍。找到第一个只出现一次的字符
for(int i=0;i<charStr.length;i++){
if(hashmap.get(charStr[i])==1)
return i;
}
return -1;
}
unit 5 Q15:0左边必有1的二进制字符串数量
给一整数N,要求(每个"0"的左边必是"1"的、长度为N的字符串)的个数
N=1,长度为1的字符串只有"1"符合要求,返回1
N=2,长度为2的字符串只有"10"和"11"符合要求,返回2
N=3,长度为3的字符串只有"101"、"110"和"111"符合要求,返回3
date:2019/09/23
思路:
递归的思想。时间复杂度O(2^N)
设P(i)是i个字符下满足该条件的个数,则易得P(1)=1,P(2)=2;
从字符串末尾开始,倒着考虑。
1.str[N…N],1个字符。str[N]只能是1,个数为P(1)=1;
2.str[N-1…N],2个字符。str[N-1]只能是1,个数P(2)=2;
3.str[N-2…N],3个字符。str[N-2]只能1:
如果str[N-1]=1,相当于str[N-1…N]的情况,该情况下个数为P(2);
如果str[N-1]=0,则str[N]只能是1,相当于str[N…N]的情况,该情况下个数为P(1);
所以P(3)=P(2)+P(1)
4.str[N-3…N],4个字符。str[N-3]只能是1:
如果str[N-2]=1,相当于str[N-2…N]的情况,该情况下个数为P(3);
如果str[N-2]=0,则str[N-1]只能是1,相当于str[N-1…N]的情况,该情况下个数为P(2);
所以P(4)=P(3)+P(2)
.
.
.
综上所述可得递推公式:
str[1…N],N个字符。个数P(N)=P(N-1)+P(N-2)
类斐波那契数列,递归实现即可。
代码:U5Q15_getNum10.java
unit 5 Q16:拼接str[]中字符串产生字典顺序最小的字符串
strs=[“abc”,“de”],返回"abcde"
strs=[“b”,“ba”],返回"bab"
date:2019/09/24
思路:
对str[]按照字典顺序s的大小进行排序,再依次拼起来即可。
贪心算法思路,知道容易证明难。
实现时需要改写快速排序,比较大小的规则为:
若str1和str2比较,如果(str1+str2)<(str2+str1),则str1在前;反之str2在前。
时间复杂度即为排序的复杂度,用快速排序即为O(N*logN)
//str1.compareTo(str2):按字典顺序比较两字符串;等于返回0,大于返回>0的int值,小于返回<0的int值
//例子:
String str1 = "Strings";
String str2 = "Strings";
String str3 = "Strings123";
System.out.println(str1.compareTo(str2));
System.out.println(str2.compareTo(str3));
System.out.println(str3.compareTo(str1));
结果:
>>> 0
>>> -3
>>> 3
代码:U5Q16_minStitchingStr.java
unit 5 Q17:找到字符串中的最长无重复子串
剑指offer 48 Leetcode 3 难度:中等
要求时间复杂度为O(N)
例:str=“abcd”,返回4
str=“aabcb”,最长无重复子串为"abc",返回3
date:2019/09/26
思路:
设置hashtable(str[i],i),存遍历到的每个值str[i]最近一次出现的位置
设置start指向以i结束的最长无重复子串的开始位置,初值为0;
dup指向i之前最近一次出现str[i]的位置(由hashtable.get()获得),初值为1;
len存当前以i结束的最长无重复子串,初值为0;maxLen记录所有字串中的最大值,初值为Integer.MIN_VALUE。
1.遍历字符数组,取str[i]最近一次出现的位置于dup中,如果先前没出现过,置duo为-1;
2.如果dup大于等于start,则以i结束的最长子串只能从(dup+1)开始,置start为dup+1;否则重复点在当前子串开始位置之前,start不用变。
3.当前字串为str[start…i],则长度len为(i-start+1);
如果len比maxLen大,更新maxLen;更新hashtable中str[i]的value为最新的i;hashtable.put(charStr[i],i);
4.出while循环返回maxLen即可。
代码:
//2020/02/17
public int lengthOfLongestSubstring(String s) {
if(s==null||s.equals(""))
return 0;
int maxLen=-1;
//用HashMap记录每一个值上次出现的位置
HashMap<Character,Integer> hashMap=new HashMap<Character,Integer>();
int start=0,dup=0;//start:当前子字符串开始位置 //dup暂存str[i]上次出现的位置
char[] str=s.toCharArray();
//遍历
for(int i=0;i<str.length;i++){
if(hashMap.containsKey(str[i]))
dup=hashMap.get(str[i]);//从hashMap中找str[i]上次出现的位置
else
dup=-1;
//如果当前str[i]上一次出现的位置dup在start之前,则更新当前start为(dup+1)
if(dup>=start)
start=dup+1;
maxLen=Math.max(maxLen,i-start+1);
hashMap.put(str[i],i);//更新hashMap中str[i]的位置
}
return maxLen;
}
unit 5 Q18:求最小覆盖子串
Leetcode第76题,难度:hard
例:
str1=“ADOBECODEBANC”,
若str2=“ABCC”,则str1包含str2的最小覆盖子串是"CODEBANC"
若str2=“ABC”,则str1包含str2的最小覆盖字串是"BANC"
要求:时间复杂度O(N)
date:2019/09/27
思路:
滑动窗口经典题,也可以返回最小覆盖字串的长度。
想象一个left为头right为尾的窗口str1[left…right],在str1上从左向右滑动;
先左边不动、右边拉长,延申至可以覆盖str2全部元素后,固定右边,让左边移动收缩,收缩至不能覆盖为止。
再固定左边、右边拉长…如此反复至right走到最头为止。
滑动窗口就像一个不断向前蠕动、时缩时伸的蚯蚓,在这个过程中找寻匹配且最小的覆盖字串。
实现:
1.变量设置:
Hashtable类型的needs[str2中字符,该字符对应的个数],需求hashtable,经遍历str2初始化后不再改变。
Hashtable类型的window[当前窗口中字符,该字符对应个数],当前滑动窗口的hashtable,随着滑动窗口的滑动而变化。
left和right分别指向滑动窗口的头和尾,初值为0;
minLeft、minRight记录最小字串的头尾,minLen记录最小字串长度。如果当前长度小于minLen,更新minLeft,minRight,minLen
matched表示window窗口和needs中有几个字符已成功匹配,初值为0.(matched == needs.size())说明window覆盖了str2所有字符。
2.两层while循环,第一层while循环固定left,right右移;第二层while循环固定right,left右移。
3.第一层while循环:
在str1上遍历到每个值(即str1[right]),如果都将其加至window里更新其value即对应的个数,
当window中str1[right]对应个数 等于 needs中str1[right]对应个数时,则str2中一个字符已经完全覆盖,matched++;
如果str2中所有字符均已覆盖(即matched == needs.size())则进第二层while循环(即right不动更新left)。
第二层while循环出来后的滑动窗口str1[left,right]已经不满足了,right++继续寻找;
4.第二层while循环:
matched==needs.size()且left<=right时循环:
能进循环说明当前window完全覆盖,如果当前window比minLen短,则更新minLeft,minRight,minLen;
每个left自增前,如果当前window中str1[left]对应个数 等于 needs中str1[right]对应个数 时,matched–;
(因为当前刚好个数相等,left自增后window就会少一个成功覆盖的元素,不能完全覆盖)
更新window中str1[left]对应个数;left++
5.两层while循环出来后的str[left…right]即是最小覆盖字串,整理下返回即可。
// hashtable.getOrDefault()函数
// 如果能找到返回key对应的value,如果没找到则 hashtable.put(key,defaultValue)
int getValue=hashtable.getOrDefault(key,defaultValue);
代码:U5Q18_minWindowSubstr.java
unit 5 Q19:回文最小分割数
给定str,返回把str全部切成回文字串的最小分割数。
例:
str=“ABA”,最少需要切0次,返回0。
str=“ACDCDCDAD”,最少需要切三次(“A”,“CDCDC”,“DAD”),返回2。
date:2019/09/28
思路:
经典的动态规划题
实现:
1.int型 动态规划数组dp[i],表示str[i…len-1]最少还需切割几次,才能全切成回文子串。
boolean型 动态规划数组isP[i][j],如果str[i…j]是回文串,则isP[i][j]为true
2.两层for循环:第一层for循环i从右向左遍历,每一次遍历出一个dpi
第二层for循环j从左向右遍历,枚举j,
根据公式dp[i]=Min{dp[j+1]+1},(i<=j<=len-1且isP[i][j] == True)求得dp[i]
3.第二层for循环详解:
Q1:dp[i]=Min{dp[j+1]+1},(i<=j<=len-1且isP[i][j]==True)公式?
A:第二层for循环实际上将str[i…len-1]分为str[i…j]和str[j+1,len-1]两截。
如果前半截是回文则把前半截自己作为一个部分不切,把后半截做最好的切割(后半截的最小分割数为dp[j+1])
则该j情况下的最小分割数为{dp[j+1]+1}。枚举j,取{dp[j+1]+1}的最小者即为dp[i]。
如果前半截不是回文?题目要求子串要回文,不符,下一个j。
Q2:如何快速判断Q1中的str[i…j]是否回文?
A2:
str[i…j]是回文分为三种情况:
1.字符数为1(i=j,也是str[i]==str[j])
2.字符数为2:两字符相等(即str[i]==str[j])
3.字符串大于2:str[i+1,j-1]是回文,且首尾字符相等(即str[i]==str[j])
一个动态规划的问题。
两层循环,i从右向左遍历,j从左向右遍历。
要算p[i][j]时,p[i+1][j-1]肯定已经算过(i从右向左,先i+1再i;j从左向右,先j-1再j),这样就能顺便求出所有的p[i][j]。
代码:U5Q19_minPalindromeCut.java
unit 5 Q20:前缀树的实现
Leetcode 208 难度:中等
头条做过面试题
date:2019.09.29
代码:U5Q20_prefixTree.java
unit 5 Q21:替换字符串中空格
We are happy --> We%20are%20happy
剑指offer 5
date:2019/11/6
思路:
String.replaceAll(“要被替换的部分”,“替换成的部分”)
public String replaceBlank(String str){
if(str==null||str.equals(""))
return "";
return str.replaceAll(" ","%20");
}
奇奇怪怪
#### unit 5 Q22:正则表达式匹配
剑指offer 19 Leetcode 10 难度:困难 牛客链接
date:2020/1/28
请实现一个函数用来匹配包括’ . ‘和’ * ’ 的正则表达式。模式中的字符’.’ 表示任意一个字符,而’ * '表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。
例如,字符串"aaa"与模式"a.a"和"ab * ac * a"匹配,但是与"aa.a"和"ab*a"均不匹配。
public class Solution {
public boolean match(char[] str, char[] pattern){
if(str==null||pattern==null)
return false;
return matchCore(str,0,pattern,0);
}
public boolean matchCore(char[] str,int i,char[] pattern,int j){
//同时到头,匹配成功
if(i==str.length && j==pattern.length)
return true;
//模式串先到头,匹配失败
if(j==pattern.length && i!=str.length)
return false;
//模式串第二个是*,做两步操作
if(j+1<pattern.length && pattern[j+1]=='*'){
//模式第二个是*的情况下,pattern[j]和str[i]相匹配 或者pattern[j]是*
if(i<str.length && (str[i]==pattern[j]||pattern[j]=='.')){
//try1:模式后移两位
boolean try1=matchCore(str,i,pattern,j+2);
//try2:试着匹配str的下一位
boolean try2=matchCore(str,i+1,pattern,j);
return try1||try2;
}else{
//pattern[j]和str[i]不匹配,模式后移两个,*直接跳过
return matchCore(str,i,pattern,j+2);
}
}
//模式串第二个不是*,止用比较第一个
//本层匹配成功进下一层
if(i<str.length&&j<pattern.length&&(str[i]==pattern[j]||pattern[j]=='.'))
return matchCore(str,i+1,pattern,j+1);
//不成功return false
return false;
}
}
unit 5 Q23:表示数值的字符串
剑指offer 20 牛客链接
date:2020/02/08
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。
public class Solution {
public boolean isNumeric(char[] str) {
String string = String.valueOf(str);
return string.matches("[\\+-]?[0-9]*(\\.[0-9]*)?([eE][\\+-]?[0-9]+)?");
}
}
一些解释:
[\+ -]? :括号里面加减都有可能。?表明[]中是可选的
[0-9]* :0-9会匹配零或多次。
unit 5 Q24:打印出字符串的所有排列
剑指offer 38 牛客链接
date:2020/02/14
思路:
每次都向后多固定一个位置。将后面的与固定住的依次互换,这样所有情况都会走到。
代码:
ArrayList<String> res=new ArrayList<String>();
public ArrayList<String> Permutation(String str) {
if(str==null||str.equals(""))
return res;
helper(str.toCharArray(),0);
Collections.sort(res);
return res;
}
private void helper(char[] charStr,int i){
if(i==charStr.length-1){
String str=String.valueOf(charStr);
if(!res.contains(str))
res.add(str);//添加过的再不能加了
return;
}
//开始互换
for(int j=i;j<charStr.length;j++){
swap(charStr,i,j);//对charStr换一次
helper(charStr,i+1);//进下一层charStr
swap(charStr,i,j);//刚换过的再换回来,以免影响下次
}
}
private void swap(char[] ch,int i,int j){
char temp=ch[i];
ch[i]=ch[j];
ch[j]=temp;
}
unit 5 Q25:字符流中第一个不重复的字符
剑指offer 50_2 牛客链接
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
date:2020/02/19
思路:
用Hashmap存每个字符出现的次数,list存当前的字符,不太难
HashMap<Character,Integer> hashmap=new HashMap<Character,Integer>();//存出现次数
ArrayList<Character> list=new ArrayList<Character>();//被插入的结果
//Insert one char from stringstream
public void Insert(char ch)
{
//更新hashmap中各字符出现的次数
if(hashmap.containsKey(ch))
hashmap.put(ch,hashmap.get(ch)+1);
else
hashmap.put(ch,1);
list.add(ch);
}
//return the first appearence once char in current stringstream
public char FirstAppearingOnce()
{
for(int i=0;i<list.size();i++){
char cur=list.get(i);
if(hashmap.get(cur)==1)
return cur;
}
return '#';//出了for循环,说明没找到
}