leetcode经典题目(8)--字符串

1. 字符串循环移位包含

题目描述:给定两个字符串 s1 和 s2,要求判定 s2 是否能够被 s1 做循环移位得到的字符串包含。
解题思路:s1 进行循环移位的结果是 s1s1 的子字符串,因此只要判断 s2 是否是 s1s1 的子字符串即可。

int main(){
    string str1, str2;
    while (cin >> str1 >> str2){
        string str3 = str1+str1;
        if (str3.find(str2) == string::npos)
            cout << "false" << endl;
        else
            cout << "true" << endl;
    }
    return 0;
}
2. 数组循环移位

题目描述:把一个含有N个元素的数组向右循环移动 K 位。
解法一:每次将数组向又移动一位,进行K次。如果K的值大于N,则取K对N的余数。

void RightShift(vector<int> &arr, int k){
    int n = arr.size();
    k = k % n;
    while (k > 0){
        int tmp = arr[n-1];
        for (int i = n-2; i >= 0; i--
        )
            arr[i+1] = arr[i];
        arr[0] = tmp;
        k--;
    }
}

解法二:假设原数组序列为abcd1234,循环后移4位,变为1234abcd,可以看出1234和abcd是不变的,将这两段看做整体。右移K位的过程就是把数组的两部分交换一下。变换的过程通过一下步骤完成:
(1)将abcd和1234;两个片段分别逆序排列,得到dcba4321;
(2)将dcba4321逆序得到1234abcd。

void Reverse(vector<int> &arr, int l, int r){
    int i = l, j = r;
    while (i < j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
        i++;j--;
    }
}
void RightShift(vector<int> &arr, int k){
    int n = arr.size();
    k = k % n;
    Reverse(arr,0,n-k-1);
    Reverse(arr,n-k,n-1);
    Reverse(arr,0,n-1);
}
3. 翻转字符串里的单词(NO.151)

题目描述:给定一个字符串,逐个翻转字符串中的每个单词。注意:输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
输入: " the sky is blue "
输出: “blue is sky the”
我的解法一:先对字符串进行处理,去除多余的空格,然后根据查找空格的位置提取单词到一个vector中,再反向添加都一个字符串中。但是复杂度很高。

class Solution {
public:
    string reverseWords(string str) {
        str = str + " ";
        while (str[0] == ' ')//去除开头的空格
            str.erase(str.begin());
        int i = 0;
        while (!str.empty() && i < str.size()- 1){//对于连续的空格,只保留一个
            if (str[i] == ' ' && str[i+1] == ' ')
                str.erase(str.begin()+i);
            else
                i++;
        }
        if (str.empty())
            return "";
        vector<string> vec;
        while (!str.empty()){
            string tmp = str.substr(0,str.find(' '));
            vec.push_back(tmp);
            str = str.substr(str.find(' ')+1);
        }
        string res;
        for (int i = vec.size()-1; i >=0 ; i--){
            res = res + vec[i] + " ";
        }
        return res.substr(0,res.size()-1);
    }
};

我的解法二:换了一种方式提取单词。直接遍历字符串,如果遇到的不是空格,则将其添加到单词中,如果遇到空格且现在的单词不为空,则将该单词添加到单词列表中。

class Solution {
public:
    string reverseWords(string str) {
        str = str + ' ';
        vector<string> vec;
        string word = "";
        for (int i = 0; i < str.size(); i++){
            if (str[i] != ' '){
                word += str[i];
            }
            else{
                if (word != ""){
                    vec.push_back(word);
                    word = "";
                }
            }
        }
        string res;
        for (int i = vec.size()-1; i >=0 ; i--){
            res = res + vec[i] + " ";
        }
        
        return res.substr(0,res.size()-1);
    }
};
4. 两个字符串包含的字符是否完全相同(NO.242)

题目描述:给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。即比较两个字符串出现的字符数量是否相同
解题思路:使用两个数组分别存储两个字符串中各个字符的出现次数,比较相同字符的出现次数是否相同。为节省空间,可以使用一个数组存储字符的出现次数,在遍历字符串s时加,在遍历字符串t时减,相当于让两个字符串中同一字符的出现次数相减,最后判断是否为0即可。

class Solution {
public:
    bool isAnagram(string s, string t) {
        vector<int> arr1(26,0);
        vector<int> arr2(26,0);
        for (char c1 : s)
            arr1[c1-'a']++;
        for (char c2 : t)
            arr2[c2-'a']++;
        for (int i = 0; i < 26; i++){
            if (arr1[i] != arr2[i])
                return false;
        }
        return true;       
    }
};
class Solution {
public:
    bool isAnagram(string s, string t) {
        vector<int> arr(26,0);
        for (char c1 : s)
            arr[c1-'a']++;
        for (char c2 : t)
            arr[c2-'a']--;
        for (int i = 0; i < 26; i++){
            if (arr[i] != 0)
                return false;
        }
        return true;       
    }
};
5. 计算一组字符集合可以组成的回文字符串的最大长度(NO.409)

题目描述:给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。在构造过程中,请注意区分大小写。比如 “Aa” 不能当做一个回文字符串。
输入:“abccccdd”;输出:7
解释: 我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
解题思路:首先记录各个字符出现的次数,如果出现偶数次,直接加在结果上,如果出现奇数次,则先减去1变为偶数,再加在结果上。如果有出现了奇数次的字符,则可将其作为最中心的元素,所以结果还要再加1.

class Solution {
public:
    int longestPalindrome(string s) {
        vector<int> arr(52,0);
        for (char c : s){//对各个字符的出现次数计数
            if (c >= 'a' && c <= 'z')
                arr[c-'a']++;
            else if (c >= 'A' && c <= 'Z')
                arr[c-'A'+26]++;
        }
        int flag = 0;//记录是否有出现奇数次的字符
        int res = 0;
        for (int i = 0; i < 52; i++){
            if (arr[i] % 2 == 0)//偶数次直接相加
                res += arr[i];
            else{
                res += arr[i] - 1;//奇数次减1再相加
                flag = 1;
            }
        }
        res += flag;
        return res;
    }
};
6. 字符串同构(No.205)

题目描述:给定两个字符串 s 和 t,判断它们是否是同构的。如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的。所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身。
输入: s = “paper”, t = “title”
输出: true
解题思路:记录一个字符上次出现的位置,如果两个字符串中的字符上次出现的位置一样,那么就属于同构。

class Solution {
public:
    bool isIsomorphic(string s, string t) {
        vector<int> preIndexOfS(256,0);
        vector<int> preIndexOfT(256,0);
        if (s.size() != t.size())
            return false;
        for (int i = 0; i < s.size(); i++){
            if (preIndexOfS[s[i]] != preIndexOfT[t[i]])//比较上次出现的位置
                return false;
            preIndexOfS[s[i]] = i + 1;//更新字符出现的位置
            preIndexOfT[t[i]] = i + 1;
        }
        return true;
    }
};
7. 回文子字符串个数(NO.647)

题目描述:给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被计为是不同的子串。
输入: “abc”
输出: 3
解释: 三个回文子串: “a”, “b”, “c”.
解题方法一:以第i个字符为中心,向两边扩展,如果左右两边字符相同,则是回文子串;以第i个和第i+1个字符为中心,如果这两个字符相同,则是一个回文子串且可以向两边扩展,如果左右两边字符相同,则又是一个回文子串。遍历字符串,以当前字符为中心扩展。

class Solution {
public:
    int cnt = 0;
    int countSubstrings(string s) {
        for (int i = 0; i < s.size(); i++){
            extendSubString(s, i, i);//子串长度为奇数
            extendSubString(s, i, i+1);//子串长度为偶数
        }
        return cnt;
    }
private:
    void extendSubString(string s, int start, int end){
        while (start >= 0 && end < s.size() && s[start] == s[end]){
            start--;
            end++;
            cnt++;
        }
    }
};

解题方法二:使用动态规划。dp[i][j] 表示从i到j的字符是否为回文子串。如果s[i]==s[j],考虑从i+1到j-1之间的字符串:如果只有一个或者没有字符,或者是回文字符串,则i到j之间字符也是回文字符串。因为更新dp[i][j]时用到dp[i+1][j-1],所以对i的遍历要从后往前。

class Solution {
public:
    int countSubstrings(string s) {
        int n = s.size();
        vector<vector<bool>> dp(n,vector<bool>(n,false));
        int res = 0;
        for (int i = n - 1; i >= 0; i--){
            for (int j = i; j < n; j++){
                if (s[i] == s[j] && (j-i <= 1 || dp[i+1][j-1])){
                    dp[i][j] = true;
                    res++;
                }
            }
        }
        return res;
    }
};
8. 最长回文子串(NO.5)

题目描述:给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
输入: “babad”
输出: “bab”.注意: “aba” 也是一个有效答案。

class Solution {
public:
    string longestPalindrome(string s) {
        string longStr;
        for (int i = 0; i < s.size(); i++){
            string tmp1 = extend(s,i,i);
            string tmp2 = extend(s,i,i+1);
            if (tmp1.size() > longStr.size())
                longStr = tmp1;
            if (tmp2.size() > longStr.size())
                longStr = tmp2;
        }
        return longStr;
    }
    string extend(string s, int i, int j){
        string res;
        if (s[i] != s[j])
            return res;
        while (i >= 0 && j < s.size() && s[i] == s[j]){
                i--;
                j++;
        }
        res = s.substr(i+1,j-i-1);
        return res;
    }
};
9. 最长回文子序列的长度(NO.516)

给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。
输入:“bbbab”
输出:4.一个可能的最长回文子序列为 “bbbb”。

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int n = s.size();
        int maxVal = 1;
        vector<vector<int>> dp(n, vector<int>(n,0));
        for (int i = s.size() - 2; i >= 0; i--){
            dp[i][i] = 1;
            for (int j = i + 1; j < s.size(); j++){
                if (s[i] == s[j]){
                    dp[i][j] = dp[i+1][j-1] + 2;
                }
                else
                    dp[i][j] = max(dp[i][j-1], dp[i+1][j]);
                if (dp[i][j] > maxVal)
                    maxVal = dp[i][j];
            }
        }
        return maxVal;
    }
};
10. 判断一个整数是否是回文数(NO.9)

题目描述:判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
解题思路:如果题目没有要求,可以将数字转化为字符串,再使用双指针进行判断。若题目要求不让转化为字符串,则将整数分割为两部分,后一部分逆序,比较这两部分的值。

class Solution {
public:
    bool isPalindrome(int x) {
        if (x == 0)
            return true;
        if (x < 0 || x % 10 == 0)
            return false;
        int right = 0;
        while (x > right){
            right = right * 10 + x % 10;
            x /= 10;
        }
        return x == right || x == right / 10;
    }
};
11. 统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数(NO.696)

题目描述:给定一个字符串 s,计算具有相同数量0和1的非空(连续)子字符串的数量,并且这些子字符串中的所有0和所有1都是组合在一起的。重复出现的子串要计算它们出现的次数。
解题思路一:利用扩展的思想。遍历字符串的各个位置,以第i位和i+1位为中心,如果这两个数不相等,则计数加1,并且可以向两边扩展,扩展的条件是:左边的元素都与第i位的相同,右边的元素都与第i+1位的形同。

class Solution {
public:
    int countBinarySubstrings(string s) {
        int cnt = 0;
        for (int i = 0; i < s.size()-1; i++){
            if (s[i] != s[i+1]){
                cnt++;
                int left = i - 1, right = i + 2;
                while (left >= 0 && right < s.size()){
                    if (s[left] == s[left+1] && s[right] == s[right-1]){
                        cnt++;
                        left--;
                        right++;
                    }
                    else
                        break;
                }
            }
        }
        return cnt;
    }
};

解题思路二:统计连续的0和1分别有多少个,如:111100011000,得到4323;在4323中的任意相邻两个数字,取小的一个加起来,就是3+2+2 = 7.

class Solution {
public:
    int countBinarySubstrings(string s) {
        int pre = 0;
        int i = 0;
        int sum = 0;
        while (i < s.size()){
            int len = 1;
            while (s[i+1] == s[i]){
                i++;
                len++;
            }
            i++;
            sum += (pre > len ? len : pre);
            pre = len;
        }
        return sum;
    }
};
12. 判断一个字符串是否为IPV4或者IPV6

编写一个函数来验证输入的字符串是否是有效的 IPv4 或 IPv6 地址。
IPv4 地址由十进制数和点来表示,每个地址包含4个十进制数,其范围为 0 - 255, 用(".")分割。比如, 172.16.254.1; 同时,IPv4 地址内的数不会以 0 开头。比如,地址 172.16.254.01 是不合法的。IPv6 地址由8组16进制的数字来表示,每组表示 16 比特。这些组数字通过 (":")分割。比如, 2001:0db8:85a3:0000:0000:8a2e:0370:7334 是一个有效的地址。而且,我们可以加入一些以 0 开头的数字,字母可以使用大写,也可以是小写。所以, 2001:db8:85a3:0:0:8A2E:0370:7334 也是一个有效的 IPv6 address地址 (即,忽略 0 开头,忽略大小写)。然而,我们不能因为某个组的值为 0,而使用一个空的组,以至于出现 (: 的情况。 比如, 2001:0db8:85a3::8A2E:0370:7334 是无效的 IPv6 地址。同时,在 IPv6 地址中,多余的 0 也是不被允许的。比如, 02001:0db8:85a3:0000:0000:8a2e:0370:7334是无效的。

bool isHex(char c){
    return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
bool isIpv6(string str){
    int cnt = 0;
    string tmp;
    for (int i = 0; i < str.size(); i++){
        if (isHex(str[i])){
            tmp += str[i];
            if (tmp.size() > 4)
                return false;
        }
        else if (str[i] == ':'){
            if (tmp.size() != 4)
                return false;
            cnt++;
            tmp = "";
        }
        else
            return false;
    }
    return cnt == 7;
}
bool isIpv4(string str){
    int cnt = 0;//记录点的个数
    int dotIndex = -1;//记录上一个点的位置
    int tmp = 0;//记录每个区间的数字
    for (int i = 0; i < str.size(); i++){
        if (str[i] == '.'){
            if (i - dotIndex == 1)//两个点相邻
                return false;
            dotIndex = i;
            cnt++;
            tmp = 0;//重置tmp,用于记录下一个区间的值
        }
        else if (str[i] >= '0' && str[i] <= '9'){
            tmp = tmp * 10 + str[i] - '0';
            if (tmp > 255)
                return false;
        }
        else
            return false;
    }
    return cnt == 3;
}

你可能感兴趣的:(LeetCode)