String - 字符串(一)
本章主要介绍字符串相关题目。
处理字符串操作相关问题时,常见的做法是从字符串尾部开始编辑,从后往前逆向操作。这么做的原因是因为字符串的尾部往往有足够空间,可以直接修改而不用担心覆盖字符串前面的数据。
strStr
Example
If source = “source” and target = “target”, return -1.
If source = “abcdabcdefg” and target = “bcd”, return 1.
题解
对于字符串查找问题,可使用双重 for 循环解决,效率更高的则为 KMP 算法。双重 for 循环的使用较有讲究,因为这里需要考虑目标字符串比源字符串短的可能。对目标字符串的循环肯定是必要的,所以可以优化的地方就在于如何访问源字符串了。简单直观的解法是利用源字符串的长度作为 for 循环的截止索引,这种方法需要处理源字符串中剩余长度不足以匹配目标字符串的情况,而更为高效的方案则为仅遍历源字符串中有可能和目标字符串匹配的部分索引。
int strStr(char* haystack, char* needle) {
if (haystack == NULL || needle == NULL) return -1;
const int len_h = strlen(haystack);
const int len_n = strlen(needle);
for (int i = 0; i < len_h - len_n + 1; i++) {
int j = 0;
for (; j < len_n; j++) {
if (haystack[i+j] != needle[j]) {
break;
}
}
if (j == len_n) return i;
}
return -1;
}
Two Strings Are Anagrams
Write a method anagram(s,t) to decide if two strings are anagrams or not.
Example
Given s=“abcd”, t=“dcab”, return true.
题解1 - hashmap 统计字频
判断两个字符串是否互为变位词,若区分大小写,考虑空白字符时,直接来理解可以认为两个字符串的拥有各不同字符的数量相同。对于比较字符数量的问题常用的方法为遍历两个字符串,统计其中各字符出现的频次,若不等则返回false. 有很多简单字符串类面试题都是此题的变形题。
class Solution {
public:
/**
* @param s: The first string
* @param b: The second string
* @return true or false
*/
bool anagram(string s, string t) {
if (s.empty() || t.empty()) {
return false;
}
if (s.size() != t.size()) {
return false;
}
int letterCount[256] = {0};
for (int i = 0; i != s.size(); ++i) {
++letterCount[s[i]];
--letterCount[t[i]];
}
for (int i = 0; i != t.size(); ++i) {
if (letterCount[t[i]] != 0) {
return false;
}
}
return true;
}
};
源码分析
- 两个字符串长度不等时必不可能为变位词(需要注意题目条件灵活处理)。
- 初始化含有256个字符的计数器数组。
- 对字符串 s 自增,字符串 t 递减,再次遍历判断letterCount数组的值,小于0时返回false.
题解2 - 排序字符串
另一直接的解法是对字符串先排序,若排序后的字符串内容相同,则其互为变位词。题解1中使用 hashmap 的方法对于比较两个字符串是否互为变位词十分有效,但是在比较多个字符串时,使用 hashmap 的方法复杂度则较高。
class Solution {
public:
/**
* @param s: The first string
* @param b: The second string
* @return true or false
*/
bool anagram(string s, string t) {
if (s.empty() || t.empty()) {
return false;
}
if (s.size() != t.size()) {
return false;
}
sort(s.begin(), s.end());
sort(t.begin(), t.end());
if (s == t) {
return true;
} else {
return false;
}
}
};
源码分析
- 对字符串 s 和 t 分别排序,而后比较是否含相同内容。对字符串排序时可以采用先统计字频再组装成排序后的字符串,效率更高一点。
Compare Strings
Compare two strings A and B, determine whether A contains all of the characters in B.
The characters in string A and B are all Upper Case letters.
Example
For A = “ABCD”, B = “ABC”, return true.
For A = “ABCD” B = “AABC”, return false.
题解
题目意思是问B中的所有字符是否都在A中,而不是单个字符。比如B="AABC"包含两个「A」,而A="ABCD"只包含一个「A」,故返回false. 做题时注意题意,必要时可向面试官确认。
既然不是类似 strstr 那样的匹配,直接使用两重循环就不太合适了。题目中另外给的条件则是A和B都是全大写单词,理解题意后容易想到的方案就是先遍历 A 和 B 统计各字符出现的频次,然后比较频次大小即可。嗯,祭出万能的哈希表。
class Solution {
public:
/**
* @param A: A string includes Upper Case letters
* @param B: A string includes Upper Case letter
* @return: if string A contains all of the characters in B return true
* else return false
*/
bool compareStrings(string A, string B) {
if (A.size() < B.size()) {
return false;
}
const int AlphabetNum = 26;
int letterCount[AlphabetNum] = {0};
for (int i = 0; i != A.size(); ++i) {
++letterCount[A[i] - 'A'];
}
for (int i = 0; i != B.size(); ++i) {
--letterCount[B[i] - 'A'];
if (letterCount[B[i] - 'A'] < 0) {
return false;
}
}
return true;
}
};
源码解析
- 异常处理,B 的长度大于 A 时必定返回false, 包含了空串的特殊情况。
- 使用额外的辅助空间,统计各字符的频次。
Anagrams
Given an array of strings, return all groups of strings that are anagrams.
Example
Given [“lint”, “intl”, “inlt”, “code”], return [“lint”, “inlt”, “intl”].
Given [“ab”, “ba”, “cd”, “dc”, “e”], return [“ab”, “ba”, “cd”, “dc”].
Note
All inputs will be in lower-case