string类OJ练习题

目录

文章目录

前言

一、反转字符串

[二、反转字符串 II](#二、反转字符串 II)

[三、反转字符串中的单词 III](#三、反转字符串中的单词 III)

四、验证一个字符串是否是回文

五、字符串相加(大数加法)

六、字符串相乘(大数乘法)

七、把字符串转化为整数(atoi)

总结



前言

string类相关的oj题,加强对 string 类接口使用的熟练度。


一、反转字符串

链接344. 反转字符串 - 力扣(LeetCode)

思路:

  • 第一题难度少些,思路就是左右互换。

题解:

cpp 复制代码
class Solution {
public:
    void reverseString(vector<char>& s) {
        int begin = 0;
        int end = s.size()-1;
        while(begin < end)
        {
            swap(s[begin++], s[end--]);
        }
    }
};

二、反转字符串 II

链接541. 反转字符串 II - 力扣(LeetCode)

思路1:

  • 先说我最开始的思路:定义变量 count 计数,利用算法库函数 reverse 进行对应区间的逆置
  • 即遍历 s ,每遍历一个字符,count++,如果 count == 2k,则按照题目要求逆置前k个字符,每逆置一次后 count 清零。
  • 遍历结束后,根据 count 剩余计数大小,将剩余字符进行反转

题解1:

cpp 复制代码
class Solution 
{
public:
    string reverseStr(string s, int k) 
    {
        size_t count = 0;//计数
        for (size_t i = 0; i < s.size(); ++i)
        {
            if (count == 2 * k)
            {
                reverse(s.begin() + i - 2 * k, s.begin() + i - k);
                count = 0;
            }
            ++count;
        }
        if (count < k)//情况1
        {
            reverse(s.end() - count, s.end());
        }
        if (count >= k && count <= 2 * k)//情况2
        {
            reverse(s.end() - count, s.end() - (count - k));
        }
       
        return s;
    }
};

思路2:

  • 先自己造一个 reverse 的轮子,因为算法库中的 reverse 只支持迭代器,我们自己造一个支持下标的 reverse 函数。
  • 主方法中,依旧是遍历 s,但是循环调整条件为 i+=2k,这样保证每次区间开头是跳过了2k个字符。循环中反转字符串的条件为 i+k<s.size(),这样前 k 个字符就能反转了。其他情况就是剩余字符不够 k 个,那么直接将剩余字符全部反转。

题解2:

cpp 复制代码
class Solution {
public:
  //翻转start到end区间的字符串
  void Reverse(string &s, int start, int end)
  {
    char tmp;
    end--;
    while(start < end)
    {
      tmp = s[start];
      s[start] = s[end];
      s[end] = tmp;
      start++;
      end--;
    }
  }

  string reverseStr(string s, int k) 
  {
    int len = s.size();
    for(int i=0; i<len; i+=2*k)
    {
      if(i+k < len)
        Reverse(s, i, i+k);
      else
        Reverse(s, i, len);
    }

    return s;
  }
};

三、反转字符串中的单词 III

链接557. 反转字符串中的单词 III - 力扣(LeetCode)

思路1:

  • 利用 string 的 find 和 substr 接口。创建两个 string 对象 tmp,ret,tmp 用于暂存每个单词并进行反转,ret 用于拼接 tmp 并返回最终串。再创建两个整形变量 pos1 和 pos2,pos1 初始化为0,后面更新时直接 = pos2+1 即可找到下一单词首字符下标,pos2 在 pos1 的基础上再寻找下一空字符位置。
  • 中间反转过程:使用循环实现,只要 pos2 != string::npos 即可继续循环,tmp 接收 s.substr(pos1, pos2-pos1) 返回的子串,也就是需要反转的单词,tmp 再使用 reverse 函数进行反转,反转后用 ret 进行拼接,ret 拼接 tmp 后还需要拼接一个空字符。接着更新 pos1 和pos2。
  • 循环结束后,pos2 如果等于 string::npos,就说明还有一个单词需要逆置。单独处理。

题解1:

cpp 复制代码
class Solution 
{
public:
    string reverseWords(string s) 
    {
        //tmp用于单词反转,ret尾插反转后的单词
        string tmp;
        string ret;
        size_t pos1 = 0;//单词长度由pos1和pos2决定
        size_t pos2 = s.find(' ');
        while (pos2 != string::npos)
        {
            tmp = s.substr(pos1, pos2 - pos1);
            reverse(tmp.begin(), tmp.end());
            ret += tmp + ' ';
            pos1 = pos2 + 1;
            pos2 = s.find(' ', pos1);
        }

        if (pos2 == string::npos)
        {
            tmp = s.substr(pos1);
            reverse(tmp.begin(), tmp.end());
            ret += tmp;
        }

        return ret;
    }
};

思路2:

  • 自己造一个支持下标的 reverse 函数。定义两个整形参数 start 和 end,start 初始化为 0,依旧是一个循环,循环条件是 start < s.size(),end 用于在 start 的基础上寻找下一个空字符,然后只要 end 没有返回 npos,就将区间 【start,end-1】进行反转,反转后 strat = end+1,指向下一个单词的首字符。
  • 如果 end == string::npos,就先将 end 重赋值为 s.size(),指向字符串尾部的'\0',再 break 结束循环。再单独反转一次 【start,end-1】区间即可,最后返回 s
  • 好处就是直接在 s 上修改,节省空间。

题解2:

cpp 复制代码
class Solution {
public:
  void Reverse(string &s, int start, int end)
  {
    char tmp;
    while(start < end)
    {
      tmp = s[start];
      s[start] = s[end];
      s[end] = tmp;
      start++;
      end--;
    }
  }

  string reverseWords(string s) 
  {
    size_t start = 0;
    size_t end = 0;
    while(start < s.size())
    {
      end = s.find(' ', start);
      if(end == string::npos)
      {
        end = s.size();
        break;
      }

      Reverse(s, start, end-1);
      start = end+1;
    }

    Reverse(s, start, end-1);
    return s;
  }
};

四、验证一个字符串是否是回文

链接: 125. 验证回文串 - 力扣(LeetCode)

思路1:

  • 将原字符串分割为两段子串,逆置一段,然后判断是否相等。
  • 首先利用C语言库函数 isalnum 和 isupper 分别判断是否为字母数字和大写字母,将原字符串转化为符合要求的字符串 s1。然后,创建两个 string 对象 s2 和 s3,定义一个下标 i,利用 for 循环,i < s.size()/2,将字符串 s 的前一半尾插到 s2,i 定义在循环外面,因此接着让s3 = s.substr(i),s3因为是后半段,因此需要判断一下长度是否与 s2 相等,先反转 s3,长度与s2不相等则尾删一次 s3。最后返回 s2 == s3的比较值。

题解1:

cpp 复制代码
class Solution {
public:
    bool isPalindrome(string s) {
        string s1;

        //去除非字母数字
        for (auto c : s)
        {
            if (isalnum(c))
            {
                s1 += c;
            }
        }

        //改大写为小写
        for (auto& c : s1)
        {
            if (isupper(c))
            {
                c += 32;
            }
        }

        //创建两个子串进行比较是否回文
        string s2;
        string s3;
        size_t i = 0;
        for (; i < s1.size()/2; i++)
        {
            s2 += s1[i];
        }
        s3 = s1.substr(i);

        reverse(s3.begin(), s3.end());//逆置
        if (s3.size() > s2.size())//子串长度不相等,"尾删"第二个子串
        {
            s3.pop_back();
        }
        
        return s2 == s3;
    }
};

思路2:

  • 利用类似快排的思路,不用改变原字符串,设置 left 和 right 下标,一个找字符串左边未比较的字符,一个找右边的,然后两两比较,直到 left > right,只有有一对不相等则不是回文。
  • 如果不记得C语言判断字母数字的库函数,可以自己造轮子。

题解2:

cpp 复制代码
class Solution {
public:
  bool isDigtalOrWord(char ch)
  {
    if( (ch>='0' && ch<='9')
    || (ch>='A' && ch<='Z')
    || (ch>='a' && ch<='z'))
      return true;

    return false;
  }

  bool isPalindrome(string s) 
  {
    if(s.empty())//为空直接返回
      return true;

    for(int i=0; i<s.size(); ++i)
    {
      s[i] = tolower(s[i]); //忽略大小写,字母全部转为小写      
    }

    int left = 0;    
    int right = s.size()-1;
    while(left < right)
    {
      //找到左边第一个未比较的字母
      while(left<right && !isDigtalOrWord(s[left]))
        left++;

      //找到右边第一个未比较的字母
      while(left<right && !isDigtalOrWord(s[right]))
        right--;

      //左右两边字母若不相等,则不是回文
      if(s[left] != s[right])
        return false;

      left++;
      right--;
    }

    return true;
  }
};

五、字符串相加(大数加法)

链接415. 字符串相加 - 力扣(LeetCode)

思路:

  • 我们知道基本算术都是从个位开始算的,这就导致我们会先算出最终结果的个位数,将结果放到 string 对象中,那么就需要头插,头插效率低,因此我们只能选择尾插,最后反转得到最终结果
  • 首先,设置两个表示下标的参数 end1 和 end2,分别表示字符串1和字符串2的最后一位,设置循环 while(end1>=0 || end2>=0),我们需要两字符串的从最后一位开始每一位都进行相加,因此两个下标都走到头才结束,设置进位参数 next,再设置两个参数 x1 和 x2,分别获取字符串1和字符串2对应位的数据。因为两个字符串长度不一定相等,因此当某一字符串走到头另一字符串还有未计算的位数时,就将对应的参数x设置为0,使用三目操作符:int x1 = end1 >= 0 ? num1[end1--] - '0' : 0、int x2 = end2 >= 0 ? num2[end2--] - '0' : 0,将后置 -- 融合到这个表达式中调整循环次数。
  • 计算过程:循环中定义一个参数 ret ,计算每次加法后的结果:int ret = x1 + x2 + next。关于进位 next = ret / 10 进行计算,因为进位的规则就是将大于10的计算结果进一位,如果 ret 小于 10 则 next = 0,如果 ret 大于 10 则 next = 1,直接将 ret / 10 就可以计算进位。而当前位的计算结果由: ret %= 10 直接获得,根据10进1,除10取余就能获取位的计算结果,ret 大于 10,取余就能获得位相加的结果,ret 小于10,除10取余数值不变。ret 和 next 计算完后就可以拼接结果串了:str += ret + '0'。
  • 循环结束后,还需要判断进位是否计算完,如果进位还等于1,就需要将结果串再加 '1'。最后逆置结果串得到最终计算结果。

题解:

cpp 复制代码
class Solution
{
public:
    string addStrings(string num1, string num2)
    {
        string str;//结果串

        //提前扩容,提升效率
        str.reserve(max(num1.size(), num2.size()) + 1);

        //先取两字符串尾部下标
        int end1 = num1.size() - 1, end2 = num2.size() - 1;

        //进位
        int next = 0;

        //只要有一个下标没走完,循环继续
        while (end1 >= 0 || end2 >= 0)
        {
            int x1 = end1 >= 0 ? num1[end1--] - '0' : 0;
            int x2 = end2 >= 0 ? num2[end2--] - '0' : 0;
            int ret = x1 + x2 + next;

            next = ret / 10;
            ret %= 10;

            str += ret + '0';
        }
        if (next == 1)
        {
            str += '1';
        }

        //逆置
        reverse(str.begin(), str.end());

        return str;
    }
};

六、字符串相乘(大数乘法)

链接43. 字符串相乘 - 力扣(LeetCode)

思路:

  • 注:本思路并非最佳思路,只是分享下我的思路

  • 我们知道,在乘法的计算过程中还存在加法运算,也就是中间错位相加的地方,我需要模拟这样的计算顺序,因此需要存储每一个待相加的字符串,我将这些待相加的字符串存储在一个 string 数组 str 中,至于数组的元素个数由 num2.size() 决定,因为乘数的个数决定待相加的字符串个数。接着设置 end1 和 end2 下标,依旧表示两个字符串的尾下标。

  • 我们首先需要获取所有待相加的字符串,获取流程:设置外循环 while(end2>=0) ,循环中设置 x2 = num2[end2] - '0',x2 就是获取每一位乘数。因为每循环一次就是计算一个待相加字符串,因此每循环一次,被乘数就需要重新获取,所以 end1 = num1.size() - 1 重置被乘数下标。进位 next 初始化为0。接着设置内循环 while (end1 >= 0),外循环说白了就是遍历乘数,内循环就是遍历被乘数,内循环中获取被乘数 x1 = num1[end1--] - '0',计算第一位结果:int ret = x1 * x2 + next,计算进位:next = ret / 10,计算结果位:ret %= 10,再将结果尾插到数组str中:str[end2] += (ret + '0'),因为是尾插,因此每计算一组待相加字符串后就需要逆置:reverse(str[end2].begin(), str[end2].end()),当然逆置前还需要判断一下是否还有进位未处理。逆置后将 end2-- ,接着循环处理下一个乘数对应的待相加字符串。例如获取 123 x 45 的待相加字符串:str[0] 表示的是末行待相加字符串

  • 补 '0' 对齐:当我们得到待相加字符串时,比如 123 X 45 的待相加字符串 "615" 和 "492",我们需要对 "492" 补上一个 '0' 和 "615" 的"5"对齐,我们错位相加的思路就是依次获取每一个待相加字符串的尾部数据,然后依次相加计算。第二行待相加数开始就需要进行补'0',n = num2.size() - 2,n 表示需要补 '0' 的字符串 str[n],因为乘数大于等于2时(或者说待相加字符串数量大于等于2时)才需要进行补'0',补'0'使用 append 接口进行操作,并配合循环 while(n>=0):str[n].append(num2.size() - n - 1, '0') 。这样就补全了 '0'。例如:123 x 456:

  • 补'0'前:

  • 补'0'后:

  • 错位相加:补完'0'后开始计算结果串 ret,设置一个死循环 while (1),循环内部就是获取每个待相加字符串的尾部数据进行相加计算,每计算一次每个待带相加字符串尾删一次,如果某个待相加字符串为空了就 continue 跳过,直到所有待相加字符串都为空了就 break 跳出循环,得到结果串后还需要逆置,逆置后还需要进行消'0'处理,也就是当结果串为一长串0的话,就直接返回"0"。

  • 特殊处理:当乘数只有一位时,计算完待相加字符串后就可以直接返回该串了,当然还需要进行消'0'处理。

题解:

cpp 复制代码
class Solution {
public:
    string multiply(string num1, string num2)
    {
        //根据乘数创建对象个数,存储每位乘数乘以被乘数后的值
        string* str = new string[num2.size()];
        int end1 = num1.size() - 1;
        int end2 = num2.size() - 1;

        //求每位乘数乘以被乘数后的值
        while (end2 >= 0)
        {
            int x2 = num2[end2] - '0';
            end1 = num1.size() - 1;
            int next = 0;//进位
            while (end1 >= 0)
            {
                int x1 = num1[end1--] - '0';
                int ret = x1 * x2 + next;

                next = ret / 10;
                ret %= 10;

                str[end2] += (ret + '0');
            }
            if (next)
            {
                str[end2] += (next + '0');
            }

            reverse(str[end2].begin(), str[end2].end());
            --end2;
        }

        //乘数为个位数,直接返回
        if (num2.size() == 1)
        {
            string ret = str[0];
            //消零
            if (ret[0] == '0')
            {
                ret = "0";
            }

            return ret;
        }

        //补0对齐
        int n = num2.size() - 2;
        while (n >= 0)
        {
            str[n].append(num2.size() - n - 1, '0');
            --n;
        }

        //求和
        string ret;
        int next = 0;
        while (1)
        {
            int cout = 0;
            int flag = 1;
            for (int i = 0; i < num2.size(); i++)
            {
                if (str[i].empty())
                {
                    continue;
                }
                flag = 0;
                int x = str[i].back() - '0';
                cout += x;
                str[i].pop_back();
            }
            if (flag == 1)
            {
                break;
            }
            cout += next;
            next = cout / 10;
            cout %= 10;

            ret += cout + '0';
        }
        if (next)
        {
            ret += next + '0';
        }
        reverse(ret.begin(), ret.end());
        delete[] str;

        //消零
        if (ret[0] == '0')
        {
            ret = "0";
        }

        return ret;
    }
};

七、把字符串转化为整数(atoi)

链接: LCR 192. 把字符串转换成整数 (atoi) - 力扣(LeetCode)

思路:

  • 这题我们先了解两个常量 INT_MAX 和 INT_MIN,这两个分别是整形最大值和整形最小值也就是 2的31次方减1 和 -2的31次方。就算不记得我们也可以自定义。
  • 过程:设置一个下标 int i = 0 和一个表示正负的布尔变量 bool sign = true,从头遍历数组,先用循环 while (i < str.size() && str[i] == ' ') 过滤掉空格,接着判断首个字符是否为正负号,如果该字符串为负数,移至下一个字符接着判断,如果字符串为正数,sign已经默认为true,直接移动到下一位即可,下面开始对非正负符号位进行判断,也就是说符号位后必须接数字,否则不合规直接返回0,数字中也不能开始就是0,因此 if 条件为 if (str[i] < '0' || str[i] > '9') return 0;
  • 以上 i 遍历后都通过,则开始转换数字,设置整形 res 和 num,num 转换字符为数字:num = str[i] - '0',res 进行乘10加等 num:res = res * 10 + num。在 while (i < str.size()) 循环中进行。因为 res 为整形 int,为了判断 res 是否溢出,我们设置整形 border = INT_MAX / 10,border 比整形的最大值长度少一位,因为 res 每次乘等10,所以 border 可以提前判断 res 下次运算是否溢出,但如果 res 刚好等于 border 时就需要判断 str[i] 是否大于 '7',因为整形最大值为 2147483647。下一个字符如果大于7就说明大于整形最大值。因此判断条件写成 if (res > border || res == border && str[i] > '7'),如果满足条件就根据 sign 返回:return sign == true ? INT_MAX : INT_MIN。
  • 循环结束一切顺利的话就根据 sign 返回结果:return sign == true ? res : -res;

题解:

cpp 复制代码
class Solution {
public:
    int myAtoi(string str)
    {
        bool sign = true;   //默认为正数

        // 跳过开头可能存在的空格
        int i = 0;
        while (i < str.size() && str[i] == ' ')
        {
            i++;
        }

        //接着判断首个字符是否为正负号
        if (str[i] == '-')
        {
            sign = false;  // 该字符串为负数,移至下一个字符接着判断
            i++;
        }
        else if (str[i] == '+')  // 字符串为正数,sign已经默认为true,直接移动到下一位即可
        {
            i++;
        }

        //下面开始对非正负符号位进行判断
        if (str[i] < '0' || str[i] > '9') // 正常数字第一位不能是0,必须为1~9之间的数字,否则就是非法数字
            return 0;

        int res = 0;   //这里res用的int型,需要更加仔细考虑边界情况,但如果用long的话可以省去一些麻烦
        int num = 0;
        int border = INT_MAX / 10;  // 用来验证计算结果是否溢出int范围的数据
        while (i < str.size())
        {
            // 遇到非数字字符,则返回已经计算的res结果
            if (str[i] < '0' || str[i] > '9')
                break;

            // 注意这句话要放在字符转换前,因为需要验证的位数比实际值的位数要少一位, 这里比较巧妙的地方在于
            // 1. 用低于int型数据长度一位的数据border判断了超过int型数据长度的值 
            // 2. 将超过最大值和低于最小值的情况都包括了
            if (res > border || res == border && str[i] > '7')
                return sign == true ? INT_MAX : INT_MIN;

            //开始对数字字符进行转换
            num = str[i] - '0';
            res = res * 10 + num;
            i++;
        }

        //最后结果根据符号添加正负号
        return sign == true ? res : -res;
    }
};

总结

以上就是本文的全部内容,感谢支持!

相关推荐
肖田变强不变秃4 分钟前
自研有限元软件与ANSYS精度对比-Bar3D2Node三维杆单元模型-央视大裤衩实例
c++·3d·有限元
软件工程师文艺6 分钟前
Three.js实现3D动态心形与粒子背景的数学与代码映射解析
开发语言·javascript·3d
shanks6611 分钟前
【PyQt】lambda函数,实现动态传递参数
开发语言·python·pyqt
zimoyin12 分钟前
Kotlin/Js Kotlin 编译为 JS (尝试)
开发语言·javascript·kotlin
南玖yy17 分钟前
C语言:深入了解指针3
c语言·开发语言·前端
军训猫猫头21 分钟前
64.进度条 C#例子 WPF例子
开发语言·c#·wpf
初九之潜龙勿用1 小时前
C#结合html2canvas生成切割图片并导出到PDF
开发语言·ui·pdf·c#·html·asp.net
银河梦想家1 小时前
【Day31 LeetCode】动态规划DP Ⅳ
算法·leetcode·动态规划
go54631584651 小时前
复现论文“去模糊算法”
算法
学编程的小程1 小时前
Java_类加载器
java·开发语言·类加载器