leetcode解题报告字符串篇

56 阅读4分钟

字母异位词分组

  1. 首先遍历字符串数组
  2. 在遍历的过程中对每一个字符串进行排序
  3. 排序完成之后将字符串添加进map中, 它的键是排序完成的字符串,它的值是一个数组

所以我们可以写出下面的程序:

vector<vector<string>> groupAnagrams(vector<string>& strs) {
    unordered_map<string, vector<string>> hash;

    for (auto str : strs) {
        string key = str;
        sort(key.begin(), key.end());
        hash[key].push_back(str);
    }
    vector<vector<string>> ans;

    for (auto item : hash) {
        ans.push_back(item.second);
    }
    return ans;

}

这道题的关键就是我们需要知道这个异位词在排序之后都是一样的,其次查找相同的词可以通过hash函数的方法来做。

反转字符串中的单词

这个题目的题意是需要我们按照单词作为单位来做分隔,那么如何按照单词级别的单位进行分割呢,其实当我们遍历到空格的时候可以停下来,这样就是一个单词了。

其实这是一个模版,这个模版如下:

string str;
for (int i = 0; i < str.size(); i++) {
    while (j < s.size() && str[i] == ' ') j++; //跳过空格
    while (i < s.size() && str[i] != ' ') i++; //找到单词
}

有了这个模版我们就可以切分每一个单词了,一旦切分单词了,那我们剩下来的事情就比较好弄了。

  1. 我们将分割之后的每个单词给反转,单词的区间是[i, j]
  2. 反转之后我们将新的单词给拷贝一下,定义一个k, 因为可能出现单词位置的改变,比如" hello,world "
  3. 然后将k之后的字符串全部删除,还是因为出现字符串位置的改变
  4. 然后将整个字符串反转一下
string reverseWords(string s) {
    int k = 0;
    for (int i = 0; i < s.size(); i++) {
        while (i < s.size() && s[i] == ' ') i++;
        if (i == s.size()) break;
        int j = i;
        while (j < s.size() && s[j] != ' ') j++;
        reverse(s.begin() + i, s.begin() + j);
        //每次拿完之后, 填充空格
        if (k) s[k++] = ' ';
        while (i < j) s[k++] = s[i++];
    }
    s.erase(s.begin() + k, s.end());
    reverse(s.begin(), s.end());
    return s;
}

比较版本号

这个比较版本号的题目和上面的题做法类似,都是分割字符串,这道的题目的解法如下:

int i, j;

while (i < vesion1.size() || j < version2.size()) {
    while (x < version1.size() && version1[i] == '.') x++;
    while (y < version2.size() && version2[j] == '.') y++;
}

通过上面的模版我们分离了字符串,然后就是对字符串进行比较,我们比较的手法是将字符串转化成为对应的数字:

//i是初始位置
//转化字符串的区间是[i, x-j]
int a = i == x ? 0 : atoi(version1.substr(i, x - i).c_str());
int b = j == y ? 0 : atoi(version2.substr(j, y - j).c_str());

然后判断是否相同:

if (a > b) return 1;
if (a < b) return -1;
i = x + 1;
j = y + 1;

独特的电子邮件地址

这道题目依然是解决对应的截取对应的字符串,只不过这次使用到的是两个库函数。分别是find函数和对应的insert函数。

str.find('@')

然后直接给出对应的代码:

int numUniqueEmails(vector<string>& emails) {
    unordered_set<string> h;

    for (auto email : emails) {
        int at = email.find('@');
        string name;
        for (auto c : email.substr(0, at)) {
            if (c == '+') break;
            else if (c != '.') name += c;
        }

        string domain = email.substr(at + 1);
        h.insert(name + '@' + domain);
    }
     return h.size();
}

实现 Trie (前缀树)

下面这道题目也是一个比较经典的数据结构叫做trie树,这个trie树主要是用来存储字符串或者说是单词的。 然后这个trie树是下面这样的:

a -> b -> c
|
d
|
c

a是所有树的根节点,当我们遍历这棵树的时候会得到两个单词,分别是abc和对应的adc,当然如果b如果是一个单词的结尾的话,我们我们也可以说ab也是一个单词。

如果用算法来描述这个数据结构的话,代码如下:

struct Node {
    bool is_end; //表示一个单词是不是以当前字母结尾
    Node* son[26]; //表示一个树的子节点
    Node() {
     is_end = false;
     for (int i = 0; i < 26; i++) {
      son[i] = NULL;
     }
    }
}* root;

当有了上面这个结构的时候我们就可以进行构造树了。主要是插入和查询。

void insert(string word) {
     auto p = root;
    for (auto c : word) {
        int u = c - 'a';
        if (p->son[u] == NULL) p->son[u] = new Node();
        p = p->son[u];
    }
    p->is_end = true;
}

bool search(string word) {
 auto p = root;
    for (auto c : word) {
        int u = c - 'a';
        if (p->son[u] == NULL) return false;
        p = p->son[u];
    }
    return p->is_end;
}