剑指offer 学习笔记
第三章 字符串
3.1字符串基础知识
字符串由任意长度(长度可能为0)的字符组成,Java中用定义的类型String来表示字符串。
函数 | 函数功能 |
---|---|
charAt | 返回指定下标处的字符 |
compareTo | 按照字典顺序比较两个字符串 |
equals | 判断两个字符串的长度和内容是否相同 |
indexOf | 返回字符串中某个字符或字符串首次出现的下标位置 |
lastIndexOf | 返回字符串中某个字符或字符串最后出现的下标位置 |
length | 返回字符串的长度 |
split | 将字符串安装指定的分隔符进行分割 |
substring | 根据下标截取字符串 |
toLowerCase/toUpperCase | 将字符串中所有大写(或小写)字母改写为小写(或大写)的字母 |
Java中String类型所表达的字符串是无法改变的,修改的内容在返回值的字符串中,原来的字符串保持不变。
3.2双指针
可以用两个指针来定位一个子字符串,其中一个指针指向字符串的第一个字符,另一个指针指向字符串的最后一个字符,两个指针之间所包含的就是一个子字符串。
面试题14 字符串中的变位词
输入字符串s1和s2,如何判断字符串s2中是否包含字符串s1的某个变位词?假设两个字符串中只包含英文小写字母。
思路:首先用一个数组模拟哈希表遍历s1字符串中的字符,统计出现的字符及其出现次数,然后用一个双指针指向s2中的子字符串,
public boolean checkInclusion(String s1,String s2){
if(s1.length()>s2.length()){
return false;}
int[]count=new int[26];#因为题目说只包含小写英文字符
for(i=0;i<s1.length();i++){
count[s1.charAt(i)-'a']++;
#创建一个数组模拟哈希表 把s1字符串对应下标表示的字符在数组中的位置设置为1
count[s2.charAt(i)-'a']--;
#这里是提前把s2字符串中前s1.length长度的字符检查一下是否为变位词
}
if(areAllZero(counts)){
return true;#这里另写一个检查数组中的数字是否全为0的函数,如果是说明包含s1的变位词 返回true
}
for(i=s1.length();i<s2.length();i++){
count[s2.charAt(i)-'a']++;
#这里使用两个指针来对s2中子字符串进行检查
#i指针指向子字符串的最后一个字符 i-s1.length()指针指向子字符串的第一个字符,子字符串的长度就是s1的长度,从s2头开始向右遍历,每次移动这两个指针时都相当于在原来的字符串的最右边添加一个新的字符,并且从原来子字符串中删除最左边的字符,每当在子字符串中添加一个字符时,就把哈希表中对应位置-1,同样,每当在子字符串中删除一个字符时,就把哈希表对应位置加1
count[s2.charAt(i-s1.length())-'a']--;
if(areAllZero(int [] (counts)){
return true;}
}
return false;}
private boolean areAllZero(int [] counts){
for(int count:counts){
if(count!=0){
return false;}}
return true;}
时间复杂度O(m+n)
空间复杂度O(1)
面试题15 字符串中的所有变位词
力扣
输入字符串s1和s2,如何找出字符串s2的所有变位词在字符串s1中的起始下标?假设两个字符串中只包含英文小写字母。
思路:和上一道题基本一样,就是多了一个整数列表用来记录指向变位词子字符串开头的指针的下标。
public List<Integer> findAnagrams(String s1,String s2){
List<Integer> indices=new LinkedList<>();
int[]counts=new int[26];
if(s1.length()<s2.length()){
return indices;}
for(int i=0;i<s2.length();i++){
counts[s2.charAt(i)-'a']++;
counts[s1.charAt(i)-'a']--;
}
if(areAllZero(counts)){
indices.add(0);}
for(int i=s2.lengthz();i<s1.length();i++){
counts[s1.charAt(i)-'a']--;
counts[s1.charAt(i-s1.length())-'a']++;
if(areAllZero(counts)){
incides.add(i-s2.length()+1);}
}
return indices;
}
时间复杂度O(n)
空间复杂度O(1)
面试题16 不含重复字符的最长子字符串
力扣
输入一个字符串,求该字符串中不含重复字符的最长子字符串的长度。
思路:用一个哈希表统计子字符串中字符出现的次数,由于不含重复字符,因此这个哈希表中应该都不大于1
public int lengthOfLongestSubstring(String s){
if(s.length()==0){
return 0;}
int[]counts=new int[256];
int longest=1;
int i=0;
int j=0;
#j是第一个指针,i是最后一个指针
for(i=0;i<s.length();i++){
counts[s.charAt(i)-'a']++;
while(hasGreaterThan1(counts)){
#如果子字符串中包含重复字符,那么向右移动第一个指针,删除子字符串中最左边的字符
counts[s.charAt(j)-'a']--;
j++;
}
longest=Math.max(i-j+1,longest);
}
return longest;}
private boolean hasGreaterThan1(int[] counts){
for(int count:counts){
if(count>1){
return true;}}
return false;}
上述代码需要多次遍历整个哈希表
换一个思路,定义一个变量用来储存哈希表中大于1的数字的个数,即子字符串中重复字符的个数
public int lengthOfLongestSubstring(String s){
if(s.length()==0){
return 0;}
int i=0;
int j=-1;
int longest=1;
int countDup=0;
int[]counts=new int[256];
for(i=0;i<s.length();i++){
counts[s.charAt(i)-'a']++;
if(s.charAt(i)==2){
countDup++;}
while(countDup>0){
j++;
counts[s.charAt(j)-'a']--;
if(count[s.charAt(j)]==1){
countDup--;}}
longest=Math.max(longest,i-j);
}
return longest;}
面试题17 包含所有字符的最短字符串
力扣
输入两个字符串s和t,请找出字符串s中包含字符串t的所有字符的最短子字符串。
思路:这个题的解法有些绕
首先定义一个真正的哈希表用来存储t中存在的字符,键为字符,值为在t中出现的次数
然后用一个整型变量存储这个哈希表的size也就是目前在t中出现而尚未在s中出现的字符的个数
首先先遍历一遍字符串t,将其所有的字符和出现次数记录在哈希表中,然后设置两个指针,一个指向子字符串开始的位置,一个指向子字符串结束的位置。
当且仅当结束位置的指针的下标小于字符串s的长度时 或者 当出现在t中的字符全都出现在s中了且遍历到s的末尾时,循环成立。
外循环中,分两种大的情况,分别是当count大于0说明此时当前区间的字符串尚未包含t中所有的字符,这时候需要将右边的指针向右移动。
首先检查目前所指向的s的字符是否出现在t中也就是是否记录在了哈希表中,如果没有出现则忽略不计,如果出现了,那么将他在哈希表中的值-1.同时每进行一次-1的操作,检查一下此时哈希表中这个字符的值是否为0,如果为0了就将count变量-1,说明出现在t中而未出现在s中的字符又少了一个。之后如果count依然大于0继续将右指针向右移动,直到不满足此条件。
第二个条件,如果count非大于0,那么说明此时两个指针所指向的区间的字符串中已经包含了t中所有的字符,那么由于题目需要求最短的,那就想办法移动左指针看能不能将重复的字符去除。
首先记录当下的两个指针的位置和长度,接着和之前一样,判断这个字符是否出现在哈希表中,如果没出现就跳过操作,直接向右移动指针,如果出现,那么将其在哈希表中的对应值加1,如果此时哈希表中对应字符的值变为1,那么说明这个字符不重复,删除字符会让count加1。就又跳回到上面count大于0的情况了,如果后面没有满足条件的区间也不用担心,因为在第二个if中开始已经用minstart和minend记录下了第一次符合条件的区间。
public String minWindow (String s,String t){
HashMap<Character,Integer> charToCount=new HashMap<>();
for(char ch:t.toCharArray()){
charToCount.put(ch,charToCount.getOrDefault(ch,0)+1);
}
int count=charToCount.size();
int start=0;
int end=0;
int minstart=0;
int minend=0;
int minlength=Integer.MAX_VALUE;
while(end<s.length()||(count==0&&end==s.length())){
if(count>0){
char endchar=s.charAt(end);
if(charToCount.containsKey(endchar)){
charToCount.put(endchar,charToCount.get(endchar)-1);
if(charToCount.get(endchar)==0){
count--;
}
}
end++;}
else{
if(end-start<minlength){
minlength=end=start;
minstart=start;
minend=end;}
char startch=s.charAt(start);
if(charToCount.containsKey(startch)){
charToCount.put(startch,charToCount.get(startch)+1);
if(charToCount.get(startch)==1){
count++;}
}
start++;}}
return minLength<Integer.MAX_VALUE?s.substring(minStart,minend):"";}
3.3 回文字符串
回文是一类特殊的字符串。不管是从头到尾读取一个回文还是颠倒过来从尾到头读取一个回文,得到的内容是一样的。
面试题18 有效的回文
给定一个字符串,请判断它是不是回文。假设只需要考虑字母和数字字符,并忽略大小写。
思路:思路比较简单,就是设置首尾指针判断,但要注意里面的一些细节比如将通过下标取字符串s中的单个字符进行比较,还有就是对单个字符是否为数字或字母进行判断,最后就是将字符统一转换为小写字母。
public boolean isPallindrome(String s){
int i=0;
int j=s.length()-1;
while(i<j){
char ch1=s.charAt(i);
char ch2=s.charAt(j);
if(!Character.isLetterOrDigit(ch1)){
i++;}
else if(!Character.isLetterOrDigit(ch2)){
j--;}
else{
ch1=Character.toLowerCase(ch1);
ch2=Character.toLowerCase(ch2);
if(ch1!=ch2){
return false;}
i++;
j--;}}
return true;}
时间复杂度O(n)
面试题19 最多删除一个字符得到回文
力扣
给定一个字符串,请判断如果最多从字符串中删除一个字符能不能得到一个回文字符串。
思路:仍然是设置首尾两个指针逐一判断,当遇到相同就继续,遇到不同就分别判断删除这两个的其中一个剩下的是否能得到回文
public boolean validPallindrome(String s){
int start=0;
int end=s.length()-1;
for(start=0;start<s.length()/2;start++){
if(s.charAt(start)!=s.charAt(end)){
return false;}
}
return start==s.length()/2||isPallindrome(s,start,end-1)||isPallindrome(s,start+1,end);}
private boolean isPallindrome(String s,int start,int end){
while(start<end){
if(s.charAt(start)!=s.charAt(end)){
break;}
start++;
end--;
}
return start>=end;}
面试题20 回文字符串的个数
力扣
给定一个字符串,请问该字符串中有多少个回文连续子字符串
思路:这道题代码很简短,但思路有点绕 可以从字符串的中心向两边延申,如果存在一个长度为m的字符串,向两边延申一个位置之后如果还相同,那么就是长度为m+2的字符串。此外要判断回文字符串的长度,如果时奇数,还是偶数
public int countSubstring(String s){
if(s.length()==0||s==null){
return 0;}
int count=0;
for(int i=0;i<s.length();i++){
count+=countPallindrome(s,i,i);
count+=countPallindrome(s,i,i+1);}
return count;}
private int countPallindrome(String s,int start,int end){
int count=0;
while(start>=0&&end<=s.length()&&s.chartAt(start)==s.charAt(end)){
count++;
start++;
end--;}
return count;}