弦
C/C+ のすべての文字列は"\0
文字 ", で終わるため、文字列の最後を簡単に見つけることができます。ただし、この機能により、各文字列に余分な文字のオーバーヘッドが生じます。注意しないと、 , これにより、文字列が範囲外になります。たとえば、次のコード:
char str [10];
strcpy(str, "0123456789");
まず長さ 10 の文字配列を宣言し、次に文字列「0123456789」を配列にコピーします。文字列「0123456789」は 10 文字しかないように見えますが、実際には最後に「0」文字があるため、実際の長さは 11 バイトになります。この文字列を正しくコピーするには、少なくとも 11 バイトの長さの配列が必要です。
メモリを節約するために、C/C++ は定数文字列を別のメモリ領域に置きます。複数のポインタが同じ定数文字列に割り当てられている場合、それらは実際には同じメモリ アドレスを指します。しかし、定数メモリを使用して配列を初期化する場合、状況は異なります。面接の質問を通じてこの知識を学びましょう。次のコードを実行すると結果はどうなるでしょうか?
int main(int argc, char* argv[])
{
char str1[] = "hello world";
char str2[] = "hello world";
char* str3 = "hello world";
char* str4 = "hello world";
if(str1 == str2)
printf("str1 and str2 are same.\n");
else
printf("str1 and str2 are not same.\n");
if(str3 == str4)
printf("str3 and str4 are same.\n");
else
printf("str3 and str4 are not same.\n");
return 0;
}
str1 と str2 は 2 つの文字列配列ですが、それぞれ 12 バイトの長さのスペースを 2 つ割り当て、それぞれの配列に「hello world」の内容をコピーします。これらは初期アドレスが異なる 2 つの配列であるため、strl と str2 の値も異なるため、出力の最初の行は「str1 と str2 は同じではありません」となります。
str3 と str4 は 2 つのポインタです。文字列の内容を格納するためにメモリを割り当てる必要はなく、メモリ内の「hello world」のアドレスを指すだけで済みます。「helloworld」は定数文字列であるため、メモリ内にコピーが 1 つだけ存在するため、str3 と str4 は同じアドレスを指します。したがって、str3 と str4 の値を比較した結果は同じとなり、出力の 2 行目は「str3 と str4 は同じです」となります。
スペースを置き換える
質問: 文字列内の各スペースを「%20」に置き換える関数を実装してください。例えば「We are happy.」と入力すると「We%20are%20happy.」と出力されます。
ネットワーク プログラミングでは、URL パラメーターにスペースや「#」などの特殊文字が含まれている場合、サーバーは正しいパラメーター値を取得できない可能性があります。これらの特殊記号をサーバーが認識できる文字に変換する必要があります。変換規則は、「%」の後に ASCII コードの 2 桁の 16 進表現が続きます。たとえば、スペースの ASCII コードは 32、16 進数では 0x20 であるため、スペースは「%20」に置き換えられます。別の例として、「#」の ASCI コードは 35 (16 進数では 0x23) であり、URL では「%23」に置き換えられます。
この質問を見たときに最初に考えなければならないのは、元の空白文字が置換後に「%」、「2」、「0」になるため、文字列が長くなるということです。元の文字列を置き換える場合、文字列の背後にあるメモリを上書きして変更することが可能です。新しい文字列を作成し、その新しい文字列に対して置換を実行すると、十分なメモリを自分で割り当てることができます。解決策は 2 つあるため、面接官に明確に質問し、ニーズを明確に伝えてもらう必要があります。インタビュアーが、元の文字列を置き換えて、入力文字列の後に十分な空きメモリがあることを確認するように求めたとします。
時間計算量が O(n 2 ) の解では、オファーを取得するには十分ではありません。
次に、置換操作を実行する方法を検討します。最も直感的な方法は、文字列を最初から最後までスキャンし、スペース文字が見つかるたびに文字列を置き換えることです。1 文字を 3 文字に置き換えるので、スペースの後のすべての文字を 2 バイト戻す必要があります。そうしないと、2 文字が上書きされます。
たとえば、「We are happy.」のすべてのスペースを最初から最後まで「%20」に置き換えます。視覚化するために、テーブルを使用して文字列を表すことができ、テーブル内の各グリッドは文字を表します (図 2.3(a) を参照)。
注: (a) 文字列「We are happy.」。(b) 文字列の最初のスペースを「%20」に置き換えます。灰色の背景は、移動する必要がある文字を示します。©文字列内の 2 番目のスペースを「%20」に置き換えます。明るい灰色の背景は 1 回移動する必要がある文字を示し、濃い灰色の背景は 2 回移動する必要がある文字を示します。
最初のスペースを置き換えると、文字列は図 2.3(b) の内容になります。表内の灰色の背景のグリッドは、移動する必要がある領域を示します。次に 2 番目のスペースを置換します。置換後の内容は図 2.3© に示されています。同時に、濃い灰色の背景でマークされた「幸せ」の部分が 2 回移動していることに気付きます。
文字列の長さが n であるとします。空白文字ごとに、次の O(n) 文字を移動する必要があるため、O(n) 個の空白文字を含む文字列の合計時間効率は O(n 2 ) になります。
この考えを面接官に説明すると、面接官は満足せず、もっと早い方法を見つけるように求めてきます。前回の分析で、配列内の多くの文字が何度も移動されていることがわかりました。移動の回数を減らすことはできますか? 答えは「はい」です。考え方を変えて、前から後ろへの交換から後ろから前への交換に変えてみましょう。
時間計算量 O(n) ソリューション。これを利用してオファーを取得します
まず文字列を 1 回走査して、文字列内のスペースの合計数を数え、置換後の文字列の全長を計算します。スペースが置換されるたびに長さは 2 ずつ増加するため、置換後の文字列の長さは、元の長さにスペースの数を 2 倍加えたものと等しくなります。先ほどの文字列「We are happy.」を例にとると、「We are happy.」という文字列の長さは 14 (終了記号「0'」を含む) で、中にスペースが 2 つあるため、後の文字列は交換長さは18です。
文字列の末尾からコピーと置換を開始します。まず、P1とP2の2つのポインタを用意します。P1 は元の文字列の末尾を指し、P2 は置換された文字列の末尾を指します (図 2.4(a) を参照)。次に、ポインタ P1 を前方に移動し、最初のスペースが見つかるまで、ポインタ P1 が指す文字を P2 が指す位置に 1 つずつコピーします。このときの文字列は図 2.4(b)のようになり、背景が灰色の部分が文字がコピー(移動)された部分です。最初のスペースに遭遇した後、P1 を 1 スペース前に移動し、文字列 "%20" を P2 の前に挿入します。「%20」の長さは 3 であるため、図 2.4© に示すように、P2 も 3 スペース前に移動する必要があります。
次に、2 番目のスペースが見つかるまで前方にコピーします (図 2.4(d) を参照)。前回と同様に、P1 を 1 スペース前に移動し、P2 を 3 スペース前に移動して「%20」を挿入します (図 2.4(e) を参照)。この時点で、P1 と P2 は同じ位置を指しており、すべてのスペースが置き換えられたことを示しています。
上記の分析から、すべての文字が 1 回だけコピー (移動) されることがわかります。そのため、このアルゴリズムの時間効率は O(n) であり、最初のアイデアよりも高速です。
注: 図の網掛け部分は移動された文字を示します。(a) 最初のポインタは文字列の末尾を指し、2 番目のポインタは置換された文字列の末尾を指します。(b) 最初のポインタが最初のスペースに到達するまで、文字列の内容を順番にコピーします。©最初のスペースを「%20」に置き換え、最初のポインタを 1 スペース前に移動し、2 番目のポインタを 3 スペース前に移動します。(d) 文字列内の文字を、スペースが見つかるまで順にコピーします。©文字列内の最後から 2 番目のスペースを置換し、最初のポインタを 1 スペース前に移動し、2 番目のポインタを 3 スペース前に移動します。
面接の過程で、先ほどの分析と同様に、自分の考えを説明するために 1 つまたは 2 つの模式図を描くこともでき、これは自分の考えを明確にするだけでなく、面接官とのコミュニケーションをより効率的にすることにも役立ちます。面接官が私たちのアイデアを確認したら、コードを書き始めることができます。参照コードは次のとおりです。
コード例
#include <cstdio>
#include <cstring>
/*length 为字符数组str的总容量,大于或等于字符串str的实际长度*/
void ReplaceBlank(char str[], int length)
{
if (str == nullptr && length <= 0)
return;
/*originalLength 为字符串str的实际长度*/
int originalLength = 0;
int numberOfBlank = 0;
int i = 0;
while (str[i] != '\0')
{
++originalLength;
if (str[i] == ' ')
++numberOfBlank;
++i;
}
/*newLength 为把空格替换成'%20'之后的长度*/
int newLength = originalLength + numberOfBlank * 2;
if (newLength > length)
return;
int indexOfOriginal = originalLength;
int indexOfNew = newLength;
while (indexOfOriginal >= 0 && indexOfNew > indexOfOriginal)
{
if (str[indexOfOriginal] == ' ')
{
str[indexOfNew--] = '0';
str[indexOfNew--] = '2';
str[indexOfNew--] = '%';
}
else
{
str[indexOfNew--] = str[indexOfOriginal];
}
--indexOfOriginal;
}
}
// ====================测试代码====================
void Test(const char* testName, char str[], int length, const char expected[])
{
if (testName != nullptr)
printf("%s begins: ", testName);
ReplaceBlank(str, length);
if (expected == nullptr && str == nullptr)
printf("passed.\n");
else if (expected == nullptr && str != nullptr)
printf("failed.\n");
else if (strcmp(str, expected) == 0)
printf("passed.\n");
else
printf("failed.\n");
}
// 空格在句子中间
void Test1()
{
const int length = 100;
char str[length] = "hello world";
Test("Test1", str, length, "hello%20world");
}
// 空格在句子开头
void Test2()
{
const int length = 100;
char str[length] = " helloworld";
Test("Test2", str, length, "%20helloworld");
}
// 空格在句子末尾
void Test3()
{
const int length = 100;
char str[length] = "helloworld ";
Test("Test3", str, length, "helloworld%20");
}
// 连续有两个空格
void Test4()
{
const int length = 100;
char str[length] = "hello world";
Test("Test4", str, length, "hello%20%20world");
}
// 传入nullptr
void Test5()
{
Test("Test5", nullptr, 0, nullptr);
}
// 传入内容为空的字符串
void Test6()
{
const int length = 100;
char str[length] = "";
Test("Test6", str, length, "");
}
//传入内容为一个空格的字符串
void Test7()
{
const int length = 100;
char str[length] = " ";
Test("Test7", str, length, "%20");
}
// 传入的字符串没有空格
void Test8()
{
const int length = 100;
char str[length] = "helloworld";
Test("Test8", str, length, "helloworld");
}
// 传入的字符串全是空格
void Test9()
{
const int length = 100;
char str[length] = " ";
Test("Test9", str, length, "%20%20%20");
}
int main(int argc, char* argv[])
{
Test1();
Test2();
Test3();
Test4();
Test5();
Test6();
Test7();
Test8();
Test9();
return 0;
}
テストケース
1) 入力文字列にスペースが含まれています (スペースは文字列の先頭にあり、スペースは文字列の末尾にあり、スペースは文字列の途中にあり、文字列内に複数の連続したスペースがあります)。
2) 入力文字列にスペースは含まれません。
3) 特別な入力テスト (string は NULL ポインター、string は空の文字列、string にはスペース文字が 1 つだけ含まれ、string には複数の連続したスペースのみが含まれます)。