字符串
基础知识
字符串由任意长度(长度可能为0)的字符组成,是编程语言中表示文本的数据类型。Java中用定义的类型
String来表示字符串。
常用函数
序号 函数 函数功能 1 charAt 返回指定下标处的字符 2 compareTo 按照字典顺序比较两个字符串 3 equals 判断两个字符串的长度和内容是否相同 4 indexOf 返回字符串中某个字符或字符串首次出现的下标位置 5 lastIndexOf 返回字符串中某个字符或子字符串最后出现的下标位置 6 length 返回字符串长度 7 split 将字符串按照指定的分隔符进行分割 8 substring 根据下标截取字符串 9 toLowerCase/toUpperCase 将字符中所有的字母改成小写或大写
双指针
前一节说的双指针用于数组,那么同样的字符串也可以转成数组来使用双指针。
例题解答
1、字符串中的变位词
题目
输入字符串s1和s2,如何判断字符串s2中是否包含字符串s1的某个变位词?如果字符串s2中包含字符串s1的
某个变位词,则字符串s1至少有一个变位词是字符串s2的子字符串。假设两个字符串中只包含英文小写字母。例
如,字符串s1为"ac",字符串s2为"dgcaf",由于字符串s2中包含字符串s1的变位词"ca",因此输出为true.如
果字符串s1为"ab",字符串s2为"dgcaf",则输出为false。
分析
变位词实际上是字符串的所有排列,可以使用暴力解法,先获取s1的所有变位词,然后再遍历是否在s1中
被包含。
还可以再进行优化使用双指针和哈希表,变位词与字母及字母出现的次数有关,那么统计字符串中包含字母
及每个字母出现的次数,存入哈希表中。因为题目中字母都是英文小写,共26个,所以可以使用一个数组模拟,
数组下标0对应字母'a',它的值对应字母'a'出现的次数,数组下标1对应字母'b',它的值对应字母'b'出现
的次数,一次类推。
根据上面分析后,先扫描s1,每扫描到一个字符,就找到它在哈希表中的位置,并把它对应的值加1。然后
考虑s2是否包含字符串s1的变位词,每次移动需要判断s1长度的s2子字符串是否是s1的变位词。代码如下:
示例代码
public static boolean checkInclusion ( String s1, String s2) {
if ( s1. length ( ) > s2. length ( ) ) {
return false ;
}
int [ ] counts = new int [ 26 ] ;
for ( int i = 0 ; i < s1. length ( ) ; i ++ ) {
counts[ s1. charAt ( i) - 'a' ] ++ ;
counts[ s2. charAt ( i) - 'a' ] -- ;
}
if ( areAllZero ( counts) ) {
return true ;
}
for ( int i = s1. length ( ) ; i < s2. length ( ) ; i ++ ) {
counts[ s2. charAt ( i) - 'a' ] -- ;
counts[ s2. charAt ( i - s1. length ( ) ) - 'a' ] ++ ;
if ( areAllZero ( counts) ) {
return true ;
}
}
return false ;
}
private static boolean areAllZero ( int [ ] counts) {
for ( int count : counts) {
if ( count != 0 ) {
return false ;
}
}
return true ;
}
2、字符串中的所有变位词
题目
输入字符串s1和s2,如何找出字符串s2的所有变位词在字符串s1中的起始下标?假设两个字符串中只包含英文小写字母,例如,字符串s1为
"cbadabacg",字符串s2为"abc",字符串s2的两个变位词"cba","bac"是字符串s1中的子字符串,输出它们在字符串s1的起始下标0和5。
分析
和上题类似,可以用双指针和哈希表解决
示例代码
public static List < Integer > findAnagrams ( String s1, String s2) {
List < Integer > result = new ArrayList < > ( ) ;
int s1Length = s1. length ( ) ;
int s2Length = s2. length ( ) ;
if ( s1Length < s2Length) {
return result;
}
int [ ] counts = new int [ 26 ] ;
for ( int i = 0 ; i < s2Length; i ++ ) {
counts[ s2. charAt ( i) - 'a' ] ++ ;
counts[ s1. charAt ( i) - 'a' ] -- ;
}
if ( areAllZero ( counts) ) {
result. add ( 0 ) ;
}
for ( int i = s2Length; i < s1Length; i ++ ) {
counts[ s1. charAt ( i) - 'a' ] -- ;
counts[ s1. charAt ( i - s2Length) - 'a' ] ++ ;
if ( areAllZero ( counts) ) {
result. add ( i - s2Length + 1 ) ;
}
}
return result;
}
private static boolean areAllZero ( int [ ] counts) {
for ( int count : counts) {
if ( count != 0 ) {
return false ;
}
}
return true ;
}
3、不含重复字符的最长子字符串
题目
输入一个字符串,求该字符串中不含重复字符的最长子字符串的长度。例如,输入字符串"babcca",其最长的不含重复字符的子字符串是"abc",
长度为3。
分析
可以使用一个哈希表存储字符出现的次数,使用双指针,每次向后移动快指针则字符串增加一位,当有字符出现大于1次时,向后移动慢指针字符串减少一位,每次赋值给结果快指针与满指针的差值最大值即可获得结果。
示例代码
public static int lengthOfLongestSubstring ( String s) {
int result = 0 ;
int length = s. length ( ) ;
if ( length == 0 ) {
return result;
}
int [ ] counts = new int [ 256 ] ;
int countTemp = 0 ;
int low = - 1 ;
for ( int i = 0 ; i < length; i ++ ) {
counts[ s. charAt ( i) ] ++ ;
if ( counts[ s. charAt ( i) ] == 2 ) {
countTemp ++ ;
}
while ( countTemp > 0 ) {
low ++ ;
counts[ s. charAt ( low) ] -- ;
if ( counts[ s. charAt ( low) ] == 1 ) {
countTemp -- ;
}
}
result = Math . max ( i - low, result) ;
}
return result;
}
4、包含所有字符的最短字符串
题目
输入两个字符串s和t,请找出字符串s中包含t的所有字符的最短字符串。例如,输入的字符串s为"ADDBANCAD",字符串t为"ABC",则字符串s中包含
字符'A'、'B'、’C'的最短子字符串是"BANC"。如果不存在符合条件的子字符串,则返回空字符串""。如果存在多个符合条件的子字符串,则返回任意一个。
分析
一个字符串s中包含另一个字符串t的所有字符,那么字符串t的所有字符在字符串s中都出现,并且同一个字符在字符串s中出现的次数不少于在字符串t
中出现的次数。由此可以使用哈希表存储字符和字符次数来解决,先扫描字符串t中字符并存入到哈希表中,然后扫描s字符串,使用双指针方式进行,
一个指针指向s的第一个字符,一个指针指向s的最后一个字符,然后判断进行指针移动计算结果。
示例代码
public static String minWindow ( String s, String t) {
Map < 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 , end = 0 , minStart = 0 , minEnd = 0 ;
int minLength = Integer . MAX_VALUE ;
int sLength = s. length ( ) ;
while ( end < sLength || ( count == 0 && end == sLength) ) {
if ( count > 0 ) {
char endCh = s. charAt ( end) ;
if ( charToCount. containsKey ( endCh) ) {
charToCount. put ( endCh, charToCount. get ( endCh) - 1 ) ;
if ( charToCount. get ( endCh) == 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) : "" ;
}
回文字符串
回文字符串是一类特殊的字符串,不管是从头到尾读还是从尾到头读,读取的字符串结果都一样。
5、有效的回文
题目
给定一个字符串,请判断它是不是回文。假设只需要考虑字母和数字字符,并忽略大小写。例如,"Was it a cat I saw?"是一个回文字符串,而
"race a car"不是一个回文字符串。
分析
判断一个字符串是不是回文,常用的方法是双指针相反方向移动。直到两个指针相遇,或有不相同的字符时终止。
示例代码
public static boolean isPalindrome ( 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 ;
}
6、最多删除一个字符得到回文
题目
给定一个字符串,请判断如果最多从字符串中删除一个字符能不能得到一个回文字符串。例如,如果输入字符串"abca",由于删除字符'b'或者字符'c'
就能得到一个回文字符串,因此输出为true。
分析
由于事先不知道删除哪个字符,则可以两边字符都进行尝试,解题思路和上题类似。
示例代码
public static boolean validPalindrome ( String s) {
int start = 0 ;
int end = s. length ( ) - 1 ;
for ( ; start < s. length ( ) / 2 ; start ++ , end -- ) {
if ( s. charAt ( start) != s. charAt ( end) ) {
break ;
}
}
return start == s. length ( ) / 2 || isPalindrome ( s, start, end - 1 ) || isPalindrome ( s, start + 1 , end) ;
}
private static boolean isPalindrome ( String s, int start , int end) {
while ( start < end) {
if ( s. charAt ( start) != s. charAt ( end) ) {
break ;
}
start ++ ;
end -- ;
}
return start >= end;
}
7、回文字符串的个数
题目
给定一个字符串,请问该字符串中有多少回文连续子字符串?例如,字符串"abc"有3个回文子字符串,分别为"a"、"b"、"c";而字符串"aaa"有6
个回文子字符串,分别为"a","a","a","aa","aa"和"aaa"。
分析
可以从字符串中间向两边移动指针来解决,从中间位置开始,往左一个,往右一个,如果相同则,会获得一个回文字符串。
示例代码
public static int countSubstrings ( String s) {
if ( s == null || s. length ( ) == 0 ) {
return 0 ;
}
int result = 0 ;
for ( int i = 0 ; i < s. length ( ) ; i ++ ) {
result += countPalindrome ( s, i, i) ;
result += countPalindrome ( s, i, i + 1 ) ;
}
return result;
}
private static int countPalindrome ( String s, int start, int end) {
int count = 0 ;
while ( start >= 0 && end < s. length ( ) && s. charAt ( start) == s. charAt ( end) ) {
count ++ ;
start -- ;
end ++ ;
}
return count;
}
总结
熟练使用字符串的函数,双指针思想,字符的性质,能很好解决一些问题