记录《剑指offer》上的算法题。完整的代码例子可以在我的Github。
这是一道有关字符串的问题。首先在C/C++中,会把常量字符串放到单独的一个内存区域中,当几个指针赋值给相同的常量字符串时,它们实际上会指向相同的内存地址;但如果用常量内存初始化数组,数组的地址是不相同的。
下面给出替换空格的题目:
请实现一个函数,把字符串中的每个空格替换成”%20”。例如输入“We are happy.”, 则输出”We%20are%20happy.”。
在网络编程中,对于含有特殊字符的URL参数,如空格,”#“等,可能导致服务器端无法获得正确的参数值。我们需要将这些特殊符号转换成服务器可以识别的字符,替换的规则是在’%’后面跟上ASCII码的两位十六进制的表示。比如空格的ASCII码是32,即十六进制的0x20
,因此空格被替换成”%20”。
这里首先需要分两种情况,第一种是在原来的字符串上做替换;第二种是创建新的字符串并在新的字符串上做替换。现在假设是第一种情况,在原来的字符串上做替换并保证输入的字符串后面留有足够的空余内存。
最直观的做法是从头到尾扫描字符串,每一层碰到空格字符的时候做替换,由于需要将1个字符替换成3个字符,所以每次替换的时候,都要将空格后面的字符都往后移动两个字节,否则就有两个字符被替换了。因此,这种做法的时间复杂度是O(n2)。
上述做法是从前往后替换,现在可以换个思路,从后往前进行替换。首先是先遍历一遍字符串,计算字符串中空格的总数,由此计算出替换之后字符串的总长度,这里是每替换一个空格就需要增加两个字节的长度,因此替换之后的字符串长度应该是原来的长度加上两倍的空格数目。然后从后面开始复制和替换空格,需要准备两个指针P1和P2,P1指向当前字符串末尾,P2指向替换之后的字符串的末尾,然后向前移动P1,逐个把它指向的字符复制到P2指向的位置,而遇到空格的时候,P2就需要向前移动3格,因为需要复制过来的是3个字符,而P1还是移动一位。而当P2和P1都指向同一个位置的时候,说明所有空格都替换完毕了。
这种做法的时间复杂度是O(n),效率是远高于第一种做法,下面给出这种做法的代码实现和测试例子。
#include<iostream>
using std::cout;
using std::endl;
using std::cin;// 替换空格,length是字符数组string的总容量
void ReplaceBlank(char string[], int length){if (string == NULL || length <= 0){return;}// 字符串string的实际长度int originLength = 0;int numberOfBlank = 0;int i = 0;// 统计空格的数量和字符的数量while (string[i] != '\0'){++originLength;if (string[i] == ' ')++numberOfBlank;++i;}// 替换空格后的字符串新长度int newLength = originLength + numberOfBlank * 2;if (newLength > length)return;int indexOfOriginal = originLength;int indexOfNew = newLength;while (indexOfOriginal >= 0 && indexOfNew > indexOfOriginal){if (string[indexOfOriginal] == ' '){string[indexOfNew--] = '0';string[indexOfNew--] = '2';string[indexOfNew--] = '%';}else{string[indexOfNew--] = string[indexOfOriginal];}indexOfOriginal--;}
}// 测试
int main(void){char str[10] = " hello";char str2[10] = "he llo";char str3[10] = "hello ";char str4[30] = "We are happy.";char str5[30] = "helloWorld.";char *str6 = NULL;char str7[10] = "";char str8[10] = " ";char str9[20] = " ";// 空格位于字符串的最前面cout << "origin: " << str;ReplaceBlank(str, 10);cout << ",--> " << str << endl;// 空格位于字符串的最后面cout << "origin: " << str2;ReplaceBlank(str2, 10);cout << ",--> " << str2 << endl;// 空格位于字符串的中间cout << "origin: " << str3;ReplaceBlank(str3, 10);cout << ",--> " << str3 << endl;// 字符串中有多个连续空格cout << "origin: " << str4;ReplaceBlank(str4, 30);cout << ",--> " << str4 << endl;// 输入的字符串没有空格cout << "origin: " << str5;ReplaceBlank(str5, 30);cout << ",--> " << str5 << endl;// 字符串是空指针ReplaceBlank(str6, 10);// 字符串是空字符串cout << "origin: " << str7;ReplaceBlank(str7, 10);cout << ",--> " << str7 << endl;// 字符串只有一个空格字符cout << "origin: " << str8;ReplaceBlank(str8, 10);cout << ",--> " << str8 << endl;// 字符串中只有多个连续空格cout << "origin: " << str9;ReplaceBlank(str9, 20);cout << ",--> " << str9 << endl;system("pause");return 0;
}
这种从后往前复制的思路还可以用在合并两个数组的情况。