目录
[二、反转字符串 II](#二、反转字符串 II)
[三、反转字符串中的单词 III](#三、反转字符串中的单词 III)
前言
string类相关的oj题,加强对 string 类接口使用的熟练度。
一、反转字符串
思路:
- 第一题难度少些,思路就是左右互换。
题解:
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;
}
};
四、验证一个字符串是否是回文
思路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;
}
};
五、字符串相加(大数加法)
思路:
- 我们知道基本算术都是从个位开始算的,这就导致我们会先算出最终结果的个位数,将结果放到 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;
}
};
六、字符串相乘(大数乘法)
思路:
-
注:本思路并非最佳思路,只是分享下我的思路
-
我们知道,在乘法的计算过程中还存在加法运算,也就是中间错位相加的地方,我需要模拟这样的计算顺序,因此需要存储每一个待相加的字符串,我将这些待相加的字符串存储在一个 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;
}
};
总结
以上就是本文的全部内容,感谢支持!