LeetCode(五)字符串处理专题

本文详细探讨了LeetCode中涉及字符串处理的多个问题,包括Count and Say序列、字母异位词分组、字符串单词翻转、版本号比较、最长回文子串、整数英文表示、Z字形排列、无重复字符子串、循环小数表示、前缀树实现、回文分割和最短回文串构建等,涵盖了字符串操作的各种常见算法和技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LeetCode 38. Count and Say

报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下:

1.     1
2.     11
3.     21
4.     1211
5.     111221

1 被读作 “one 1” (“一个一”) , 即 11。
11 被读作 “two 1s” (“两个一”), 即 21。
21 被读作 “one 2”, “one 1” (“一个二” , “一个一”) , 即 1211。

给定一个正整数 n(1 ≤ n ≤ 30),输出报数序列的第 n 项。

注意:整数顺序将表示为一个字符串。

示例 1:

输入: 1
输出: “1”

示例 2:

输入: 4
输出: “1211”

class Solution {
public:
    string countAndSay(int n) {
        string s = "1";
        for(int i = 1; i < n; i++)
        {
            string ns = "";//当前字符串
            for(int j = 0; j < s.size();)
            {
                int u = j;
                while(u < s.size() && s[u] == s[j]) u ++;
                ns += to_string(u - j);//j是起始 u是重复的下标
                ns += s[j];
                j = u;//进行下一次操作
            }
            s = ns;
        }
        return s;        
    }
};

LeetCode 49. Group Anagrams

给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。

示例:

输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”],
输出:
[
[“ate”,“eat”,“tea”],
[“nat”,“tan”],
[“bat”]
]

说明:
所有输入均为小写字母。
不考虑答案输出的顺序。

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string, vector<string>> hash;//把字符串映射到一个字符串集合
        for(auto s : strs)
        {
            string rs = s;
            sort(s.begin(), s.end());
            hash[s].push_back(rs);//把所有字符串归类 统计相同个数
        }
        
        vector<vector<string>> res;
        for(auto &group : hash)
        {
            res.push_back(group.second); //group.second是hash的第二维字符串集合
        }
        return res;
    }
};

LeetCode 151. Reverse Words in a String

给定一个字符串,逐个翻转字符串中的每个单词。

示例 1:

输入: “the sky is blue”
输出: “blue is sky the”

示例 2:

输入: " hello world! "
输出: “world! hello”
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。

示例 3:

输入: “a good example”
输出: “example good a”
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

说明:
无空格字符构成一个单词。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

class Solution {
public:
    /*
    分两步操作:
    1.将字符串中的每个单词逆序,样例输入变为: "eht yks si eulb";
    2.将整个字符串逆序,样例输入变为:"blue is sky the";
    */
    string reverseWords(string s) {
        int i = 0,k = 0;
        s += " ";
        while(k < s.size() && s[k] == ' ') k++;//过滤开头的空格
        while(k < s.size())
        {
            if(s[k] != ' ')
                s[i++] = s[k++];
            else //过滤单词间的空格
            {
                s[i++] = ' ';//给一个空格
                while(k < s.size() && s[k] == ' ') k ++;//过滤                
            }
        }
        if(!i) 
        {
            s = "";            
            return s;
        }
        s.erase(s.begin() + i - 1, s.end()); //去掉结尾空格(因为)
        for(int i = 0; i <s.size(); i++)
        {
            int j = i;
            while(j < s.size() && s[j] != ' ') j ++;
            reverse(s.begin() + i, s.begin() + j);//翻转每个单词
            i = j; //操作下一个单词
        }
        reverse(s.begin(), s.end());        
        return s;
    }
};

LeetCode 165. Compare Version Numbers

比较两个版本号 version1 和 version2。
如果 version1 > version2 返回 1,如果 version1 < version2 返回 -1, 除此之外返回 0。

你可以假设版本字符串非空,并且只包含数字和 . 字符。

. 字符不代表小数点,而是用于分隔数字序列。

例如,2.5 不是“两个半”,也不是“差一半到三”,而是第二版中的第五个小版本。

你可以假设版本号的每一级的默认修订版号为 0。例如,版本号 3.4 的第一级(大版本)和第二级(小版本)修订号分别为 3 和 4。其第三级和第四级修订号均为 0。

示例 1:

输入: version1 = “0.1”, version2 = “1.1”
输出: -1

示例 2:

输入: version1 = “1.0.1”, version2 = “1”
输出: 1

示例 3:

输入: version1 = “7.5.2.4”, version2 = “7.5.3”
输出: -1

示例 4:

输入:version1 = “1.01”, version2 = “1.001”
输出:0
解释:忽略前导零,“01” 和 “001” 表示相同的数字 “1”。

示例 5:

输入:version1 = “1.0”, version2 = “1.0.0”
输出:0
解释:version1 没有第三级修订号,这意味着它的第三级修订号默认为 “0”。

提示:
版本字符串由以点 (.) 分隔的数字字符串组成。这个数字字符串可能有前导零。
版本字符串不以点开始或结束,并且其中不会有两个连续的点。

class Solution {
public:
    
    vector<int> get_num(string version)
    {
        vector<int> res;
        for(int i = 0; i < version.size(); i++)
        {
            int j = i, s= 0;
            while(j < version.size() && version[j] != '.')
            {
                s = s * 10 + version[j] - '0';
                j ++;
            }
            i = j;//操作下一个 .
            res.push_back(s);
        }
        while(res.size() && res.back() == 0) res.pop_back();//去除末尾无效 1.0 和 1
        return res;
    }
    
    int compareVersion(string version1, string version2) {
        auto n1 = get_num(version1);
        auto n2 = get_num(version2);
        if(n1 < n2) return -1;
        if(n1 > n2) return 1;
        else return 0;
        
    }
};

LeetCode 5. Longest Palindromic Substring

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。

示例 2:

输入: “cbbd”
输出: “bb”

class Solution {
public:
    string longestPalindrome(string s) {
        int len = 0;
        string ans;
        for(int k = 0; k < s.size(); k ++)//枚举中心点 往两边扩展
        {
            int i = k, j = k + 1;//偶数情况
            while(i >= 0 && j < s.size() && s[i] == s[j]) i--, j++;
            if(j - i - 1 > len)
            {
                len = j - i - 1;
                ans = s.substr(i + 1, len);
            }
            
            i = k - 1, j = k + 1;//奇数情况
            while(i >= 0 && j < s.size() && s[i] == s[j]) i--, j++;
            if(j - i - 1 > len)
            {
                len = j - i - 1;
                ans = s.substr(i + 1, len);
            }
        }
        return ans;
    }
};

LeetCode 273. Integer to English Words

将非负整数转换为其对应的英文表示。可以保证给定输入小于 231 - 1 。

示例 1:

输入: 123
输出: “One Hundred Twenty Three”

示例 2:

输入: 12345
输出: “Twelve Thousand Three Hundred Forty Five”

示例 3:

输入: 1234567
输出: “One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven”

示例 4:

输入: 1234567891
输出: “One Billion Two Hundred Thirty Four Million Five Hundred Sixty Seven Thousand Eight Hundred Ninety One”

class Solution {
public:
    int hundred = 100, thousand = 1000, million = 1000000, billion = 1000000000;
    unordered_map<int, string> numbers;
    string numberToWords(int num) {
        char number20[][30] = {"Zero", "One", "Two", "Three", "Four", "Five",
                               "Six", "Seven", "Eight", "Nine", "Ten", "Eleven",
                               "Twelve", "Thirteen", "Fourteen", "Fifteen",
                               "Sixteen", "Seventeen", "Eighteen", "Nineteen"};
        
        char number2[][30] = {"Twenty", "Thirty", "Forty", "Fifty", "Sixty",
                             "Seventy", "Eighty", "Ninety"};
        for(int i = 0; i < 20; i++) numbers[i] = number20[i];//映射0-19
        for(int i = 20, j = 0; i < 100; i += 10, j++) numbers[i] = number2[j];//映射二十三十这些
        numbers[hundred] = "Hundred", numbers[thousand] = "Thousand";
        numbers[million] = "Million", numbers[billion] = "Billion";
        string res;
        for(int k = 1000000000; k >= 100; k /= 1000)
        {
            if(num >= k)
            {
                res += ' ' + get3(num / k) + ' ' + numbers[k];
                num %= k;
            }
        }
        if(num) res += ' ' + get3(num);
        if(res.empty()) res = ' ' + numbers[0];
        return res.substr(1); //去除开头空格
    }
    
    string get3(int num)
    {
        string res;
        if(num >= hundred)// >=
        {
            res += ' ' + numbers[num / hundred] + ' ' + numbers[hundred];
            num %= hundred;
        }
        if(num)
        {
            if(num < 20) res += ' ' + numbers[num];
            else if(num % 10 == 0) res += ' ' +numbers[num];
            else res += ' ' + numbers[num / 10 * 10] + ' ' + numbers[num % 10];
        }
        return res.substr(1); //去除开头空格
    }
};

LeetCode 6. ZigZag Conversion

将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 “LEETCODEISHIRING” 行数为 3 时,排列如下:

L   C   I   R
E T O E S I I G
E   D   H   N

之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“LCIRETOESIIGEDHN”。

请你实现这个将字符串进行指定行数变换的函数:

string convert(string s, int numRows);

示例 1:

输入: s = “LEETCODEISHIRING”, numRows = 3
输出: “LCIRETOESIIGEDHN”

示例 2:

输入: s = “LEETCODEISHIRING”, numRows = 4
输出: “LDREOEIIECIHNTSG”
解释:

L     D     R
E   O E   I I
E C   I H   N
T     S     G
class Solution {
public:
    /*
    对于行数是 n的情况:
    对于第一行和最后一行,是公差为 2(n−1)的等差数列,首项是 0 和 n−1;
    对于第 i行(0<i<n−1),是两个公差为 2(n−1) 的等差数列交替排列,首项分别是 i 和 2n−i−2;
    所以我们可以从上到下,依次打印每行的字符。
    */
    string convert(string s, int numRows) {
        string res;
        if(numRows == 1) return s;
        for(int j = 0; j < numRows; j++) //枚举每一行
        {
            if(j == 0 || j == numRows - 1) //第一行或者最后一行
            {
                for(int i = j; i < s.size(); i += 2 * (numRows - 1))
                    res += s[i];
            }
            else //中间行
            {
                for(int k = j, i = numRows * 2 - 1 - j - 1;
                   i < s.size() || k < s.size();
                   i += 2 * (numRows - 1), k += 2 * (numRows - 1))
                {
                    if(k < s.size()) res += s[k];//交替输出
                    if(i < s.size()) res += s[i];
                }
            }
        }
        return res;
    }
};

LeetCode 3. Longest Substring Without Repeating Characters

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

示例 2:

输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。

示例 3:

输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

class Solution {
public:
    /*
    两个指针 i,j,表示当前扫描到的子串是 [i,j]。unordered_map<char,int> hash,表示 [i,j]中每个字符出现的次数。
    线性扫描时,每次循环的流程如下:
    1.指针 j向后移一位, 同时将哈希表中 s[j]的计数加一: hash[s[j]]++;
    2.假设 j移动前的区间 [i,j] 中没有重复字符,则 j 移动后,只有 s[j] 可能出现2次。因此我们不断向后移动 i,直至区间 [i,j]中 s[j] 的个数等于1为止;
    */
    int lengthOfLongestSubstring(string s) {
        unordered_map<char, int> hash;
        int res = 0;
        for(int i = 0, j = 0; j < s.size(); j++)
        {
            if(++ hash[s[j]] > 1) // s[j]出现2次, 需要缩小空间,让只出现一次
            {
                while(i < j)
                {
                    hash[s[i]]--;
                    i ++;//i向后移
                    if(hash[s[j]] == 1) break;
                }
            }
            res = max(res, j - i + 1);
        }
        return res;        
    }
};

LeetCode 166. Fraction to Recurring Decimal

给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以字符串形式返回小数。

如果小数部分为循环小数,则将循环的部分括在括号内。

示例 1:

输入: numerator = 1, denominator = 2
输出: “0.5”

示例 2:

输入: numerator = 2, denominator = 1
输出: “2”

示例 3:

输入: numerator = 2, denominator = 3
输出: “0.(6)”

class Solution {
public:
    /*
    1.先将所有负数运算转化为正数运算。然后再算出分数的整数部分,再将精力集中在小数部分。
    2.用一个哈希表 unordered_map<int,int> 记录所有余数所对应的商在小数点后第几位,当计算到相同的余数时,上一次余数的位置和当前位置之间的数,就是该小数的循环节。
    */
    string fractionToDecimal(int numerator, int denominator) {
        long long n = numerator, d = denominator;
        bool minus = false; //判断有没有负号
        if(n < 0) minus = !minus, n = -n;
        if(d < 0) minus = !minus, d = -d;
        string res = to_string(n / d);
        n %= d;
        if(!n)//余数为0,整数,直接输出
        {
            if(minus && res != "0") return '-' + res;//负数
            return res;
        }
        
        res += '.';//小数点
        unordered_map<long long, int> hash; //余数 和 对应的位置
        while(n) //当前余数
        {
            if(hash[n]) //如果是当前出现过的余数 找到循环节
            {
                res = res.substr(0, hash[n]) + '(' + res.substr(hash[n]) + ')';
                break;
            }
            else hash[n] = res.size();
            n *= 10;
            res += to_string(n / d);//商
            n %= d;
        }
        if(minus) res = '-' + res;
        return res;
    }
};

LeetCode 208. Implement Trie (Prefix Tree)

实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。

示例:

Trie trie = new Trie();

trie.insert(“apple”);
trie.search(“apple”); // 返回 true
trie.search(“app”); // 返回 false
trie.startsWith(“app”); // 返回 true
trie.insert(“app”);
trie.search(“app”); // 返回 true

说明:
你可以假设所有的输入都是由小写字母 a-z 构成的。
保证所有输入均为非空字符串。

class Trie {
public:
    /** Initialize your data structure here. */
    
    struct Node
    {
      bool is_end;
        Node *next[26];
        Node()
        {
            is_end = false;//标记是否有单词以此节点为结尾
            for(int i = 0; i < 26; i++)
                next[i] = 0;//初始化节点为 0
        }
    } *root;
    
    Trie() {
        root = new Node();
    }
    
    /** Inserts a word into the trie. */
    void insert(string word) {
        Node *p = root;
        for(char c : word)
        {
            int son = c - 'a';//取出儿子的字母
            if(!p->next[son]) p->next[son] = new Node();//没有子树 创造
            p = p->next[son]; //进行下一位操作
        }
        p->is_end = true;//结束循环后将最后的节点标记 说明有一个单词以此节点为终点
    }
    
    /** Returns if the word is in the trie. */
    bool search(string word) {
        Node *p = root;
        for(char c : word)
        {
            if(p->next[c - 'a']) p = p->next[c - 'a']; //如果当前不为空 说明存在 继续往下搜索
            else 
                return false;
        }
        return p->is_end;//匹配到最后节点 且有以该节点为结尾的单词 返回true
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) {
        Node *p = root;
        for(char c : prefix)
        {
            if(p->next[c - 'a']) p = p->next[c - 'a'];
            else return false;
        }
        return true;
    }
};

/**
 * Your Trie object will be instantiated and called as such:
 * Trie* obj = new Trie();
 * obj->insert(word);
 * bool param_2 = obj->search(word);
 * bool param_3 = obj->startsWith(prefix);
 */

LeetCode 131. Palindrome Partitioning

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

返回 s 所有可能的分割方案。

示例:

输入: “aab”
输出:
[
[“aa”,“b”],
[“a”,“a”,“b”]
]

class Solution {
public:
    /*
    暴力枚举所有方案。
    对字符串从前往后搜索,搜索时维护如下几个状态:
    1.已经划分好的区间列表;
    2.当前正在枚举的区间;
    3.枚举到的字符串 s的下标;
    4.字符串 s
    
    在搜索时:分情况处理:
    如果已经遍历完字符串 s,则递归终止。终止前判断方案是否合法:即判断当前区间是否是回文串;
    否则,字符串 s还没遍历完,则
    1.如果当前区间不是回文串,则只能继续在该区间添加字符;
    2.如果当前区间是回文串,则既可以在该区间添加字符,也可以将该区间保存,并开启一个新的区间;
    */
    
    vector<vector<string>> ans;
    vector<string> path;//当前方案
    
    vector<vector<string>> partition(string s) {
        dfs("", 0, s);
        return ans;        
    }
    
    bool check(string &now)//检查是不是回文串
    {
        if(now.empty()) return false;
        for(int i = 0, j = now.size() - 1; i < j; i ++, j --)
            if(now[i] != now[j])
                return false;
        return true;
    }
    
    void dfs(string now, int u, string &s)
    {
        if(u == s.size())
        {
            if(check(now))
            {
                path.push_back(now);
                ans.push_back(path);
                path.pop_back();
            }
            return;
        }
        
        if(check(now))
        {
            path.push_back(now);
            dfs("", u, s);
            path.pop_back();
        }
        
        dfs(now + s[u], u + 1, s);
    }
};

LeetCode 227. Basic Calculator II

实现一个基本的计算器来计算一个简单的字符串表达式的值。

字符串表达式仅包含非负整数,+, - ,*,/ 四种运算符和空格 。 整数除法仅保留整数部分。

示例 1:

输入: “3+2*2”
输出: 7

示例 2:

输入: " 3/2 "
输出: 1

示例 3:

输入: " 3+5 / 2 "
输出: 5

说明:
你可以假设所给定的表达式都是有效的。
请不要使用内置的库函数 eval。

class Solution {
public:
    int calculate(string s) {
        stack<char> op;//操作符
        stack<int> num;
        s += "+0";
        for(int i = 0; i < s.size(); i ++)
        {
            if(s[i] == ' ') continue;
            if(s[i] == '*' || s[i] == '/' || s[i] == '+' || s[i] == '-') op.push(s[i]);
            else //取出数字 可能多位
            {
                int j = i;
                while(j < s.size() && s[j] >= '0' && s[j] <= '9') j++;
                num.push(atoi(s.substr(i, j - i).c_str())); //i是起始
                //atoi (表示 ascii to integer)是把字符串转换成整型数的一个函数
                i = j - 1;//跳到下一个数字
                if(!op.empty()) //有操作符
                {
                    if(op.top() == '*' || op.top() == '/')
                    {
                        int y = num.top();
                        num.pop();
                        int x = num.top();
                        num.pop();
                        // x * y  x / y
                        if(op.top() == '*') num.push(x * y);
                        else num.push( x / y);
                        op.pop();
                    }
                    else if(op.size() >= 2)
                    {
                        // x + y - z
                        int z = num.top();
                        num.pop();
                        int y = num.top();
                        num.pop();
                        int x = num.top();
                        num.pop();
                        char op2 = op.top();
                        op.pop();
                        char op1 = op.top();
                        op.pop();
                        if(op1 == '+') num.push(x + y), num.push(z);
                        else num.push( x - y), num.push(z);
                        op.push(op2);
                    }
                }
            }
        }
        num.pop();//pop掉一开始的 +0
        return num.top();
    }
};

LeetCode 30. Substring with Concatenation of All Words

给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。

示例 1:

输入:
s = “barfoothefoobarman”,
words = [“foo”,“bar”]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 “barfoor” 和 “foobar” 。
输出的顺序不重要, [9,0] 也是有效答案。

示例 2:

输入:
s = “wordgoodgoodgoodbestword”,
words = [“word”,“good”,“best”,“word”]
输出:[]

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        if (words.empty()) return {};
        vector<int> res;
        int n = s.size(), m = words.size(), w = words[0].size();
        unordered_map<string, int> hashmap;
        for(int i = 0; i < m; i++) hashmap[words[i]] ++;//统计每个单词出现个数
        for(int i = 0; i < w; i++)
        {
            //[i, i + w], [i + 2, i + 2w],..., [i + (m-1)w], [i + (m-1)w, i +mw] 双指针
            // i = 0, w, 2w, 3w, ...
            // i = 1, w+1, ...
            // ...
            // i = w-1, 
            unordered_map<string, int> tdict; //记录上面的次数
            int sum = 0;
            int l = i;//起始
            for(int j = l; j + w <= n; j += w)
            {
                string word;
                if(j - m * w >= l) //**
                {
                    word = s.substr(j - m * w, w);
                    if(tdict[word] == hashmap[word]) sum -= hashmap[word];
                    tdict[word] --;
                    if(tdict[word] == hashmap[word]) sum += hashmap[word];
                }
                word = s.substr(j, w);
                if(hashmap.count(word) == 0)
                {
                    sum = 0;
                    tdict.clear();
                    l = j + w;
                }
                else
                {
                    if(tdict[word] == hashmap[word]) sum -= hashmap[word];
                    tdict[word] ++; //**
                    if(tdict[word] == hashmap[word]) sum += hashmap[word];
                }
                
                if(sum == m) res.push_back(j - (m - 1) * w); // m - 1
            }
        }
        return res;
    }
};

LeetCode 214. Shortest Palindrome

给定一个字符串 s,你可以通过在字符串前面添加字符将其转换为回文串。找到并返回可以用这种方式转换的最短回文串。

示例 1:

输入: “aacecaaa”
输出: “aaacecaaa”

示例 2:

输入: “abcd”
输出: “dcbabcd”

class Solution {
public:
    string shortestPalindrome(string s) {
        string ls = s;
        reverse(s.begin(), s.end());
        s = ls + '#' + s;
        int n = s.size();
        vector<int> next(n + 1, 0);
        for(int i = 2, j = 0; i <= n; i ++)//KMP
        {
            while(j && s[j] != s[i - 1]) j = next[j];
            if(s[j] == s[i - 1]) j ++;
            next[i] = j;
        }
        int res = next[n];
        string sup = ls.substr(res);
        reverse(sup.begin(), sup.end());
        return sup + ls;
        
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值