leedcode-初级算法-字符串


title: leedcode 初级算法 字符串
tags: [leedcode,算法,字符串]
categories:

  • 算法
  • leedcode
    date: 2021-06-17 00:04:43

leedcode 初级算法 字符串

链接

反转字符转

题目说明

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。

题目解析

懒得解析了。

class Solution
{
public:
    void reverseString(vector &s)
    {
        int i = 0;
        int j = s.size() - 1;
        while (i < j)
        {
            swap(s[i++], s[j--]);
        }
    }
};

整数反转

题目说明

给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。

如果反转后整数超过 32 位的有符号整数的范围 [−231,  231 − 1] ,就返回 0。

假设环境不允许存储 64 位整数(有符号或无符号)。

题目解析

将整数的每一位存在一个vector中,倒序再存入一个数字中。或者就不用这样麻烦,直接从最后一位拿出来放在前面就行。

因为只能使用32位整数,所以需要使用下面的方法进行判断是否超界。

我们需要在「推入」数字之前,判断是否满足

INT_MIN=

INT_MAX=2^31-1=2147483647=[INT_MAX]/10*10+7
所以可以变为
ans*10+i<=[INT_MAX]/10*10+7
所以:
[ans-[INT_MAX]/10]*10<=7-i

  1. ans>[INT_MAX]/10 左边大于10,绝对大于右边。所以不行。
  2. ans=[INT_MAX]/10 由于原数字符合条件,所以其最高位=1或者2,即7-i>0 左边为零,满足。
  3. ans<[INT_MAX]/10 显然满足

综上只需判断
ans<=[INT_MAX]/10即可
对于负数同理存在
ans>=[INT_MIN]/10
所以判别条件可变为
[INT_MIN]<=ans<=[INT_MAX]/10

class Solution
{
public:
    int reverse(int x)
    {
        vector v;
        int a = x;
        while (a != 0)
        {
            v.push_back(a % 10);
            a /= 10;
        }
        long ans = 0;
        for (int i : v)
        {
            if (ans < INT32_MIN / 10 || ans > INT32_MAX)
                return 0;
            ans *= 10;
            ans += i;
        }
        if (ans > INT32_MAX)
            return 0;
        return ans;
    }
};

字符串中的第一个唯一字符

题目说明

给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。

题目解析 方法一

依次存入unordered_multimap 第一个是字符,第二个是索引.遍历字符串,设当前字符为,c,则count©==1?,相等则返回直至最后没有相等的返回-1;

class Solution
{
public:
    int firstUniqChar(string s)
    {
        unordered_multimap map;
        for(int i=0;i<=s.size()-1;i++)
        {
            map.insert({s[i],i});
        }
        for (int i = 0; i <= s.size()-1; i++)
        {
             if (map.count(s[i]) == 1) return i;
        }
        return -1;
    }
};
题目解析 方法二

或者放入unordered_map 第一个是字符,第二个是该字符出现的次数。全部插入后,遍历string,寻找map[s[i]]==1;并返回i;

class Solution
{
public:
    int firstUniqChar(string s)
    {
        //字符,出现的次数
        unordered_map map;
        unordered_map::iterator it;
        for (int i = 0; i <= s.size() - 1; i++)
        {
            //若不存在的话默认为零
            map[s[i]]++;
        }
        //遍历map
        int min = -1;
        for (int i = 0; i <= s.size(); i++)
        {
            if (map[s[i]] == 1)
                return i;
        }
        return min;
    }
};
题目解析 方法三

依次存入unordered_map 第一个是字符,第二个是索引.遍历字符串插入,若存在则索引变为-1。遍历map,寻找索引不为-1的最小值。

class Solution
{
public:
    int firstUniqChar(string s)
    {
        //字符,索引值
        unordered_map map;
        unordered_map::iterator it;
        for (int i = 0; i <= s.size() - 1; i++)
        {
            it = map.find(s[i]);
            if (it == map.end())
            {
                map.insert({s[i], i});
            }
            else
            {
                map[s[i]] = -1;
            }
        }
        //遍历map
        int min = s.size();
        for (it = map.begin(); it != map.end(); it++)
        {
            if (it->second != -1)
            {
                if (it->second < min)
                    min = it->second;
            }
        }
        if (min == s.size())
            return -1;
        return min;
    }
};
题目解析 方法四

unordered_map map; //字符和索引值
deque> dq; //字符和索引值

使用一个额外的队列来查找最前面的为一个那个字符。遍历字符串,若当前字符再map中未出现的话,就插入map并且从后面插入队列。反之,则令该key在map中的值为-1,将队列中从头开始的所有字符在map对应的value==-1的元素全部弹出。则遍历完字符串之后剩下的队列的front就是所求。

class Solution
{
public:
    int firstUniqChar(string s)
    {
        unordered_map map;
        deque> dq;
        for (int i = 0; i <= s.size() - 1; i++)
        {
            //没找到
            if (map.count(s[i]) == 0)
            {
                map[s[i]] = i;
                dq.push_back({s[i], i});
            }
            else
            {
                map[s[i]] = -1;
                while (!dq.empty() && map[dq.front().first] == -1)
                    dq.pop_front();
            }
        }
        return dq.empty() ? -1 : dq.front().second;
    }
};

有效的字母异位词

题目说明

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。(两个字符串排序后相等)

题目解析 方法一

先判断两个字符串的长度是否相等,若不想等则返回false,使用两个哈希表存储字符和出现的次数,在遍历一个哈希表,查找其对应的字符在两个哈希表中出现的次数是否一样?

class Solution
{
public:
    bool isAnagram(string s, string t)
    {
        if (s.size() != t.size())
            return false;
        unordered_map map1;
        unordered_map map2;
        unordered_map::iterator it;
        for (int i = 0; i <= s.size() - 1; i++)
        {
            map1[s[i]]++;
            map2[t[i]]++;
        }
        for (it = map1.begin(); it != map1.end(); it++)
        {
            if (it->second != map2[it->first])
                return false;
        }
        return true;
    }
};

但是这么简单的问题使用哈希表就有些小题大做了,直接使用一个数组 vector v(26,0),即可,如下:

class Solution
{
public:
    bool isAnagram(string s, string t)
    {
        if (s.size() != t.size())
            return false;
        vector v(26, 0);
        for (char c : s)
            v[c - 'a']++;
        for (char c : t)
            if (--v[c - 'a'] < 0)
                return false;
        return true;
    }
};
题目解析 方法二

对两个字符串排序之后判断是否相等即可

class Solution
{
public:
    bool isAnagram(string s, string t)
    {
        if (s.size() != t.size())
            return false;
        sort(s.begin(), s.end());
        sort(t.begin(), t.end());
        return (s == t);
    }
};

验证回文串

题目说明

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

说明:本题中,我们将空字符串定义为有效的回文串。

题目解析

先将串中的无关字符舍去并处理相关字符,再使用双指针从前后遍历判断即可。

直接使用双指针在遍历的时候处理也行。

class Solution
{
public:
    bool isPalindrome(string s)
    {
        if (s.empty())
            return true;
        vector v;
        for (char c : s)
        {
            if (c <= 'Z' && c >= 'A')
                v.push_back(c + 32);
            else if (c <= 'z' && c >= 'a')
                v.push_back(c);
            else if (c <= '9' && c >= '0')
                v.push_back(c);
        }
        int i = 0;
        int j = v.size() - 1;
        while (i < j)
            if (v[i++] != v[j--])
                return false;
        return true;
    }
};

字符串转换整数 (atoi)

题目说明

请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。

函数 myAtoi(string s) 的算法如下:

读入字符串并丢弃无用的前导空格
检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
如果整数数超过 32 位有符号整数范围 [−2^31,  2^31 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −2^31 的整数应该被固定为 −2^31 ,大于 2^31 − 1 的整数应该被固定为 2^31 − 1 。
返回整数作为最终结果。
注意:

本题中的空白字符只包括空格字符 ’ ’ 。
除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。

题目解析
class Solution
{
public:
    int myAtoi(string s)
    {
        /*
读入字符串并丢弃无用的前导空格
检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
将前面步骤读入的这些数字转换为整数(即,"123" -> 123, "0032" -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
如果整数数超过 32 位有符号整数范围 [−2^31,  2^31 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −2^31 的整数应该被固定为 −2^31 ,大于 2^31 − 1 的整数应该被固定为 2^31 − 1 。
返回整数作为最终结果。
*/
        bool flag = 0;     //正数
        bool finish_0 = 0; //前导0是否已经处理完
        long ans = 0;
        int a = -1;
        while (s[++a] == ' ')
            ;
        //a指向第一个不是空格的地方
        if (s[a] == '-')
        {
            flag = 1;
            a++;
        }
        else if (s[a] == '+')
        {
            flag = 0;
            a++;
        }
        //a指向符号位后一位,若没有符号位则指向第一个可能是数字的那一位。
        int b = a;
        for (b = a; b <= s.size() - 1; b++)
        {
            char c = s[b];
            if (c <= '9' && c >= '0')
                continue;
            else
                break;
        }
        b--;
        cout << a << b << endl;
        //b指向最后一个数字。
        for (int i = a; i <= b; i++)
        {
            int digit = s[i] - '0';
            ans = ans * 10 + digit;
            cout << digit << " ";
            if (ans > INT32_MAX)
            {
                ans = INT32_MAX;
                if (flag == 1)
                {
                    ans++;
                }
            }
        }
        cout << endl;
        //负数
        if (flag == 1)
        {
            ans = -ans;
        }
        if (ans > INT32_MAX)
        {
            ans = INT32_MAX;
        }
        if (ans < INT32_MIN)
        {
            ans = INT32_MIN;
        }
        cout << ans << endl;
        return ans;
    }
};

实现 strStr()

题目说明

实现 strStr() 函数。

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回  -1 。

题目解析 方法一

暴力判断即可,不过还是要节省一点时间的。

class Solution
{
public:
    int strStr(string haystack, string needle)
    {
        int m = haystack.size();
        int n = needle.size();
        if (m == 0)
        {
            if (n == 0)
                return 0;
            else
                return -1;
        }
        if (n == 0)
            return 0;
        int i = 0;
        int j = 0; //mississippi sipp
        while (1)
        {
            while (1)
            {
                cout << i << endl;
                if (m - i < n)
                    return -1;
                if (haystack[i] != needle[0])
                {
                    i++;
                    continue;
                }
                else if (haystack[i + n - 1] != needle[n - 1])
                {
                    i++;
                    continue;
                }
                else
                    break;
            }
            if (m - i < n)
                return -1;
            int a = i; //m
            int b = 0; //n
            while (1)
            {
                if (b == n)
                    return i;
                if (haystack[a++] != needle[b++])
                    break;
            }
            //没找到
            i++;
        }
    }
};
题目解析 方法二

KMP

kmp算法的简单介绍:
两个字符串s1,s2,指针t1,t2分别指向两个字符串中的字符。现在要从s1中寻找s2出现的最早的一次,没有出现的话就返回-1.

普通暴力算法是一个字符一个字符的去匹配,匹配失败了就从头开始并后移一位。
kmp的思想是可不可以在匹配失败之后向后多移动几位呢?
假设在第n位匹配失败,即s1[n]!=s2[n]则t2前移几位,前移的程度要最大,即移动过后t2之前的字符仍然要保持匹配,再比较t2当前和t1是否对应字符相等即可,重复t2==0仍然匹配失败的话说明此处t1指向的字符是不可能正确的,必须略过,所以t1++,之后又开始了新一轮的比较。知道最后匹配成功则返回 t1-s2.size(),失败的条件不在赘述。
前面提到了需要再字符匹配失败之后t2向前移动,但是需要移动几位合适呢?现在t2前面a个字符都是匹配成功的,现在看这a个字符组成字符串a,如果a存在两个真子串相等的话(设为a1,a2),显然a2已经是匹配成功的了,如果此时将a1移动到a2所在的地方那么自然也是匹配成功的了,所以上面提到的将t2前移几位的程度也呼之欲出,即t2要指向子串a1紧紧后面的那一位,这样就最大程度的利用了已知的信息。而这里提到的a1,a2就是a的最长相等真前缀和真后缀,所以问题的重点有落在了怎样求出s2的最长真前缀的长度呢???
我们用数组 kmp 来表示s2每一位对应的最长真前缀的长度。怎么求呢?
明显kmp[0]=0,因为只有一个字符并没有真前缀和真后缀。我们这里使用两个指针now和x分别指向a1,a2要进行匹配的字符。
如果s2[x]==s2[now]则显然可以匹配的字符数量要加1,即kmp[x]==kmp[x-1]+1;
如果不相等的话,就需要查看前面的就要像上面s2和s1进行你匹配是那样指针前移,这里又要移动几位呢?和上面的分析一样,显然kmp[now-1]个字符已经匹配成功,所以需要将**now=kmp[now-1]**在进行s2[x]s2[now]是否相等的比较了,不成立的话再进行now=kmp[now-1];若now0,则判断s2[0]==s2[x],不等的话说明连开头和结尾的字符都不想等,则显然kmp[x]=0;好了,算出了kmp[x],x++,进行下一论循环即可。
哈哈哈,终于写明白了。

class Solution
{
public:
    int strStr(string haystack, string needle)
    {
        if (needle.empty())
            return 0;
        if (haystack.empty())
            return -1;

        string s1(haystack);
        string s2(needle);
        cout << s1 << endl
             << s2 << endl;
        //kmp:
        vector kmp(s2.size(), 0);
        kmp[0] = 0;
        for (int i = 1; i <= s2.size() - 1; i++)
        {
            if (s2[kmp[i - 1]] == s2[i])
            {
                kmp[i] = kmp[i - 1] + 1;
            }
            else
            {
                int x = i;
                int now = kmp[i - 1];
                while (1)
                {
                    //aabaaac
                    cout << now << x << endl;
                    if (s2[now] == s2[x])
                        break;
                    if (now >= 1)
                        now = kmp[now - 1];
                    if (now <= 0)
                    {
                        now = -1;
                        if (s2[0] == s2[x])
                            now = 0;
                        break;
                    }
                }
                kmp[i] = now + 1;
            }
        }
        for (int x : kmp)
            cout << x << " ";
        cout << endl;
        cout << "匹配" << endl;
        //匹配
        int t1 = 0;
        int t2 = 0;
        while (1)
        {
            cout << "t1: " << t1 << " t2: " << t2 << endl;
            //成功
            if (t2 == s2.size())
            {
                return t1 - s2.size();
            }
            //失败
            if (t1 == s1.size() && t2 != s2.size())
            {
                return -1;
            }
            //匹配该字符失败
            if (s1[t1] != s2[t2])
            {
                if (t2 != 0)
                {
                    t2 = kmp[t2 - 1];
                }
                else
                {
                    t1++;
                }
            }
            else //匹配该字符成功
            {
                t1++;
                t2++;
            }
        }
    }
};
题目解析 方法三

还有一个基于kmp算法的改进版。我们将两个字符串拼接起来为s,并使用一个两串中都不存在的字符’#‘作为分隔符(如 s1=“aaa” s2=“aa” 的情况,如果中间没有#的话就不行,因为这种情况无论如何算kmp的时候都会算上两个子串的公共部分)。s=s2+’#’+s1; 别加反了。。。
遍历s,根据上面的算法求解s的kmp数组,若kmp[n]==s2.size()且n>=2*s2.size(),这样保证是s1的子串和s2匹配,而不是s2的或者s1和s2中间共有的。结果返回i - 2 * s2.size()即可。

class Solution
{
public:
    int strStr(string haystack, string needle)
    {
        if (needle.empty())
            return 0;
        if (haystack.empty())
            return -1;

        string s1(haystack);
        string s2(needle);
        string s = s2 + "#" + s1;
        cout << s1 << endl
             << s2 << endl
             << s << endl;
        //kmp:
        vector kmp(s.size(), 0);
        kmp[0] = 0;
        for (int i = 1; i <= s.size() - 1; i++)
        {
            if (s[kmp[i - 1]] == s[i])
            {
                kmp[i] = kmp[i - 1] + 1;
            }
            else
            {
                int x = i;
                int now = kmp[i - 1];
                while (1)
                {
                    //aabaaac
                    //cout << now <<" "<< x <= 1)
                        now = kmp[now - 1];
                    if (now <= 0)
                    {
                        now = -1;
                        if (s[0] == s[x])
                            now = 0;
                        break;
                    }
                }
                kmp[i] = now + 1;
            }
            if (kmp[i] == s2.size())
            {
                //cout<<"相等"<= 2 * s2.size())
                {
                    //cout<<"匹配"<

外观数列

题目说明

给定一个正整数 n ,输出外观数列的第 n 项。

「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。

你可以将其视作是由递归公式定义的数字字符串序列:

countAndSay(1) = “1”
countAndSay(n) 是对 countAndSay(n-1) 的描述,然后转换成另一个数字字符串。

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

第一项是数字 1
描述前一项,这个数是 1 即 “ 一 个 1 ”,记作 “11”
描述前一项,这个数是 11 即 “ 二 个 1 ” ,记作 “21”
描述前一项,这个数是 21 即 “ 一 个 2 + 一 个 1 ” ,记作 “1211”
描述前一项,这个数是 1211 即 “ 一 个 1 + 一 个 2 + 二 个 1 ” ,记作 “111221”

题目解析

很好理解题意,就是遍历并且判断数量即可。
使用一个数组vector v来保存结果。

class Solution
{
public:
    string countAndSay(int n)
    {
        string s = "1";
        vector> v;
        //cout<<"1"< temp;
        v.push_back(temp);
        v[0].push_back(1); //1
        for (int i = 1; i <= n - 1; i++)
        {
            //每一轮循环pushback进去一个temp来申请内存空间。v[i]
            v.push_back(temp);
            //遍历v[i-1] 并得到结果
            int j = 0;
            while (j <= v[i - 1].size() - 1)
            {
                int count = 0;
                int num = v[i - 1][j];
                int k = j;
                while (k <= v[i - 1].size() - 1)
                {
                    if (v[i - 1][k] == num)
                    {
                        k++;
                        count++;
                    }
                    else
                        break;
                }
                j = k;
                v[i].push_back(count);
                v[i].push_back(num);
            }
            s.clear();
            for (int j = 0; j <= v[i].size() - 1; j++)
            {
                char c = v[i][j] + '0';
                s.push_back(c);
            }
            //cout << s<

最长公共前缀

题目说明

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 “”。

题目解析 方法一

纵向扫描,若不符合条件则退出。

class Solution
{
public:
    string longestCommonPrefix(vector &strs)
    {
        string s = "";
        int min = INT32_MAX; //记录最短的长度
        int strnum = strs.size();
        vector::iterator it;
        //就一个串 或者没有串
        if (strs.size() <= 1)
            return strs[0];
        //判断是否存在空字符串
        for (it = strs.begin(); it != strs.end(); it++)
        {
            min = (min < it->size()) ? min : it->size();
            cout << min << endl;
        }
        if (min == 0)
            return s;
        cout << "至少两个字符串,而且没有空串" << endl;
        int index = 0;
        int v = 0;
        for (index = 0; index <= min - 1; index++)
        {
            //记录当前要比较的字符
            char c = strs[0][index];
            int flag = 0; //不存在不相等的。
            for (v = 0; v <= strnum - 1; v++)
            {
                if (strs[v][index] != c)
                {
                    flag = 1;
                }
            }
            //当前字符串通过考核
            if (flag == 0)
            {
                s.push_back(c);
            }
            else
            {
                //未通过
                break;
            }
        }
        return s;
    }
};
题目解析 方法二

横向扫描,遍历字符串数组,得到相邻字符串的公共前缀,在用这个公共前缀与下一个字符串进行比较。

class Solution
{
public:
    string longestCommonPrefix(vector &strs)
    {
        string s = strs[0];
        int strnum = strs.size();
        for (int i = 1; i <= strnum - 1; i++)
        {
            s = CommonPrefix(s, strs[i]);
            cout << s << endl;
        }
        return s;
    }
    string CommonPrefix(string &s1, string &s2)
    {
        string s = "";
        int len = (s1.size() > s2.size()) ? s2.size() : s1.size();
        if (len == 0)
            return s;
        for (int i = 0; i <= len - 1; i++)
        {
            if (s1[i] == s2[i])
                s.push_back(s1[i]);
            else
                break;
        }
        return s;
    }
};
题目解析 方法三

分治

class Solution
{
public:
    string longestCommonPrefix(vector &strs)
    {
        string s = "";
        if (strs.size() == 0)
            return s;
        if (strs.size() == 1)
            return strs[0];
        s = Prefix(strs, 0, strs.size()); //前闭后开
        return s;
    }
    string Prefix(vector strs, int begin, int end)
    {
        int mid = (begin + end) / 2;
        //就一个字符串
        if (end - begin == 1)
            return strs[begin];
        else if (end - begin == 2) //正好两个串
        {
            return CommonPrefix(strs[begin], strs[begin + 1]);
        }
        else
        {
            return CommonPrefix(Prefix(strs, begin, mid), Prefix(strs, mid, end));
        }
    }
    string CommonPrefix(string s1, string s2)
    {
        string s = "";
        int len = (s1.size() > s2.size()) ? s2.size() : s1.size();
        if (len == 0)
            return s;
        for (int i = 0; i <= len - 1; i++)
        {
            if (s1[i] == s2[i])
                s.push_back(s1[i]);
            else
                break;
        }
        return s;
    }
};

你可能感兴趣的:(算法)