今天要跟大家介绍的是数据结构当中的字符串,字符串是由若干个字符组成的序列,由于现今使用的计算机硬件结构是面向数值计算的需要而设计的,在处理字符串数据时比处理整数和浮点数要复杂得多,而且字符串在编程时使用的频率非常的高,下面先让我们一起看看 C/C++ 中字符串的特性。
C/C++ 中每个字符串都以字符 '\0' 作为结尾,这样我们就能很方便地找到字符串的最后尾部。但正由于这个特点,每个字符串中都有一个额外字符的开销,稍不留神就会造成字符串的越界。比如下面代码:
char str[10];
strcpy(str,"0123456789"); // 10位
我们先声明一个长度为 10 的字符数组,然后把字符串"0123456789" 复制到数组中。"0123456789" 这个字符串看起来只有 10 个字符,但实际上它的末尾还有一个 '\0' 字符,因此它的实际长度为 11 字符。要正确地复制该字符串,至少需要一个长度为 11 字节的数组。
大多牵扯到字符串的内容,多是模拟,当然不是简单粗暴的模拟,需要在有限的时间与空间里完成相应的算法,众所周知的有 KMP 算法等,但是今天我们只是简单的涉及下,KMP 留在后面的文章里,下面我们看看相关题目。
面试题:二进制求和
给定两个二进制字符串,返回他们的和(用二进制表示)。
输入为非空字符串且只包含数字 1 和 0。
给定两个二进制字符串,返回他们的和(用二进制表示)。
输入为非空字符串且只包含数字
1
和0
。示例 1:
输入: a = "1010", b = "1011"输出: "10101"
来源(leetcode )
我昨天写了代码,虽然通过了,但是时间消耗还是挺大的,没办法,自身实力比较菜,就不给你们看我的代码了,咱们一起来瞅瞅耗时为 4 的代码,想法一样差不多,就是代码实现的过程不同。
一开始就说了,字符串的题目主要是模拟的过程,在这里我们要模拟的有这么几种情况,值得注意的是③④⑤,它们需要进位处理,当要进位的时候,怎么处理才是最简单方便的呢?。
我们看看代码,可以看到代码中用了 temp 判断是否有进位和其他几种情况对应字符的处理,用 carrybit 标记了进位的情况,有进位则高位加一,我在代码中也写有详细的注释,相信自己动手模拟几组就可以明白的,上代码。
string addBinary(string a, string b) {
int na=a.size(),nb=b.size();
int n=max(na,nb),carrybit=0; // carrybit 初始化为零,一开始是不能可能有进位的
string res;
if(na<nb){ // 这个 if 语句是为了是两个字符创的长度相等,方便之后进行比较。
for(int i=0;i<nb-na;i++)
a.insert(a.begin(),'0');
}
else if(na>nb){
for(int i=0;i<na-nb;i++)
b.insert(b.begin(),'0');
}
reverse(a.begin(),a.end());// 从低位开始
reverse(b.begin(),b.end());
for(int i=0;i<n;i++){
int temp=0;
if(carrybit) temp=(a[i]-'0')+(b[i]-'0')+1;// carrybit 有进位,则加一
else temp=(a[i]-'0')+(b[i]-'0');
switch(temp){
case 0:
res.insert(res.begin(),'0'); // 对应 ① 这种情况
carrybit=0;
break;
case 1:
res.insert(res.begin(),'1'); // 对应 ② 这两种情况
carrybit=0;
break;
case 2:
res.insert(res.begin(),'0'); // ③④⑤,carrybit为 1, 有进位
carrybit=1;
break;
case 3:
res.insert(res.begin(),'1'); //对应 ④ 的情况,低位有进位,且高位为 1 1 ,则此位为 1
carrybit=1;
break;
default:
break;
}
}
if(carrybit) res.insert(res.begin(),'1'); // 最后判断最高位是否还有进位,也还是对应 ④ 的情况。
return res;
}
面试题:替换空格
题目:请实现一个函数,把字符串中的每个空格替换成“%20”。例如,输入“We are happy.” , 则输出 “We%20are%20happy.” 。 来源:(剑指offer 面试题5)
哈哈哈,第一眼看到这题的时候,想的非常的简单,就在输出的时候判断下是否是空格,如果是空格则输出 %20 不就好了,但如果这样子的话,那这就偏离面试官的想法了,年轻人,你很危险哟。
言归正传,现在我们开始考虑怎么模拟替换操作。最直观的做法是从头到尾扫描字符串,每次碰到空格字符的时候进行替换。由于是把 1 个字符替换成 3 个字符,我们必须要把空格后面所有的字符都后移 2 两个字节,否则就有两个字符被覆盖了,那么问题也就在这里,从前往后替换的过程中,每替换一次,后面的字符就要整体向后移动两位,时间消耗是巨大的。假设字符串的长度是 n,对每个空格字符,需要移动后面 O(n) 个字符,因此对于含有 O(n) 个空格字符的字符串而言,总的时间效率是 O(n*n) ,这显然会不符面试官的要求,年轻人,你会很危险哟。
从前往后模拟图:
注:浅蓝色代表需要移动一次的字符,深蓝色背景表示需要移动两次的字符。
接下来我们换个思路,把从前向后替换改成从后向前替换,为什么要这样子呢,这样节省下的时间就是第一种思路中最浪费时间的步骤,我们不需要再重复的进行移动。我们知道每替换一个空格,长度就增加 2,因此替换以后字符串的长度等于原来的长度加上 2 *空格数目,现在我们从字符串的后面开始复制和替换,首先准备两个指针:P1 和 P2 。P1 指向原始字符串的末尾,而 P2 指向替换之后的字符串的末尾,文字可能会讲不明白,我们来看看模拟图的模拟操作。
注:(a)把第一个指针指向字符串的末尾,把第二个指针指向替换之后的字符串的末尾。(b)依次复制字符串的内容,直至第一个指针碰到第一个空格。(c)把第一个空格替换成“%20”,把第一个指针向前移动一格,把第二个指针向前移动 3 格。(d)依次向前复制字符串中的字符,直至碰到空格。(e)替换字符串中的倒数第二个空格,把第一个指针向前移动 1格,把第二个指针向前移动 3 格。
上代码:
void ReplaceBlank(char string[],int len){
if(len<=0||string==NULL){
cout<<"NULL String"<<endl;
return ;
}
int p1 = len;// 初始实际长度
int Blanknum = 0;// 空格数量
int i = 0;
while(string[i]!='\0'){
if(string[i]==' ')
Blanknum++;
i++;
}
int p2 = p1 + Blanknum*2;// 空格替换后的长度
while(p1>=0&&p2>p1){
if(string[p1]==' '){ // 替换空格, p2 前进 3 格
string[p2--] = '0';
string[p2--] = '2';
string[p2--] = '%';
}
else {
string[p2--] = string[p1];// 依次替换
}
p1--; // 不管怎样,p1 都是需要前进一格的
}
cout<<string<<endl;
}
好了,接触了两个题目之后,相信你对字符串也有了一定的了解了,如果对文章中的内容不懂或者有错误,欢迎大家私聊我或者在评论区留言,我们一起讨论。