目录
文章目录
前言
一、反转字符串
二、反转字符串 II
三、反转字符串中的单词 III
四、验证一个字符串是否是回文
五、字符串相加(大数加法)
六、字符串相乘(大数乘法)
七、把字符串转化为整数(atoi)
总结
前言
string类相关的oj题,加强对 string 类接口使用的熟练度。
一、反转字符串
链接:344. 反转字符串 - 力扣(LeetCode)
思路:
- 第一题难度少些,思路就是左右互换。
题解:
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:
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:
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);elseReverse(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:
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:
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:
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:
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'。最后逆置结果串得到最终计算结果。
题解:
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’处理。
题解:
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;
题解:
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;}
};
总结
以上就是本文的全部内容,感谢支持!