Unit 5 字符串

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类的文档:

Hashtable API 1
Hashtable API 2
代码: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):

  1. 先用isValid判断是否合法,不合法直接返回0。

  2. 置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型。

  3. 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

  4. 出循环后根据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循环,说明没找到
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值