一、題目:替換空格
題目:請實現一個函數,把字符串中的每個空格替換成"%20"。例如輸入“We are happy.”,則輸出“We%20are%20happy.”。
在網絡編程中,如果URL參數中含有特殊字符,如空格、'#'等,可能導致服務器端無法獲得正確的參數值。我們需要將這些特殊符號轉換成服務器可以識別的字符。轉換的規則是在'%'后面跟上ASCII碼的兩位十六進制的表示。比如空格的ASCII碼是32,即十六進制的0x20,因此空格被替換成"%20"。再比如'#'的ASCII碼為35,即十六進制的0x23,它在URL中被替換為"%23"。
二、解題思路
2.1 O(n2)的解法
最直觀的做法是從頭到尾掃描字符串,每一次碰到空格字符的時候做替換。由於是把1個字符替換成3個字符,我們必須要把空格后面所有的字符都后移兩個字節,否則就有兩個字符被覆蓋了。下圖展示了從前往后把字符串中的空格替換成'%20'的過程:
假設字符串的長度是n。對每個空格字符,需要移動后面O(n)個字符,因此對含有O(n)個空格字符的字符串而言總的時間效率是O(n2)。
2.2 O(n)的解法
Step1.先遍歷一次字符串,這樣就能統計出字符串中空格的總數,並可以由此計算出替換之后的字符串的總長度。
以前面的字符串"We arehappy."為例,"We are happy."這個字符串的長度是14(包括結尾符號'\0'),里面有兩個空格,因此替換之后字符串的長度是18。
Step2.從字符串的后面開始復制和替換。
准備兩個指針,P1和P2。P1指向原始字符串的末尾,而P2指向替換之后的字符串的末尾。接下來向前移動指針P1,逐個把它指向的字符復制到P2指向的位置,直到碰到第一個空格為止。接着向前復制,直到碰到第二、三或第n個空格。
從上面的分析我們可以看出,所有的字符都只復制(移動)一次,因此這個算法的時間效率是O(n),比第一個思路要快。
三、解決問題
3.1 代碼實現
public static void ReplaceBlank(char[] target, int maxLength) { if (target == null || maxLength <= 0) { return; } // originalLength 為字符串target的實際長度 int originalLength = 0; int blankCount = 0; int i = 0; while (target[i] != '\0') { originalLength++; // 計算空格數量 if (target[i] == ' ') { blankCount++; } i++; } // newLength 為把空格替換成'%20'之后的長度 int newLength = originalLength + 2 * blankCount; if (newLength > maxLength) { return; } // 設置兩個指針,一個指向原始字符串的末尾,另一個指向替換之后的字符串的末尾 int indexOfOriginal = originalLength; int indexOfNew = newLength; while (indexOfOriginal >= 0 && indexOfNew >= 0) { if (target[indexOfOriginal] == ' ') { target[indexOfNew--] = '0'; target[indexOfNew--] = '2'; target[indexOfNew--] = '%'; } else { target[indexOfNew--] = target[indexOfOriginal]; } indexOfOriginal--; } }
3.2 單元測試
由於C#語言的特殊性,這里在測試初始化時做了一些特殊處理操作:

const int maxLength = 100; char[] target = new char[maxLength]; // Pre-Test [TestInitialize] public void ReplaceBlankInitialize() { for (int i = 0; i < maxLength; i++) { target[i] = '\0'; } } public char[] GenerateNewTarget() { int length = 0; for (int i = 0; i < maxLength && target[i] != '\0'; i++) { length++; } char[] newTarget = new char[length]; for (int i = 0; i < maxLength && target[i] != '\0'; i++) { newTarget[i] = target[i]; } return newTarget; }
(1)Test1:空格在句子中間

// Test1:空格在句子中間 [TestMethod] public void ReplaceBlankTest1() { // "hello world" target[0] = 'h'; target[1] = 'e'; target[2] = 'l'; target[3] = 'l'; target[4] = 'o'; target[5] = ' '; target[6] = 'w'; target[7] = 'o'; target[8] = 'r'; target[9] = 'l'; target[10] = 'd'; Program.ReplaceBlank(target, maxLength); string compared = new string(this.GenerateNewTarget()); string expected = "hello%20world"; Assert.AreEqual(compared, expected); }
(2)Test2:空格在句子開頭

// Test2:空格在句子開頭 [TestMethod] public void ReplaceBlankTest2() { // " helloworld" target[0] = ' '; target[1] = 'h'; target[2] = 'e'; target[3] = 'l'; target[4] = 'l'; target[5] = 'o'; target[6] = 'w'; target[7] = 'o'; target[8] = 'r'; target[9] = 'l'; target[10] = 'd'; Program.ReplaceBlank(target, maxLength); string compared = new string(this.GenerateNewTarget()); string expected = "%20helloworld"; Assert.AreEqual(compared, expected); }
(3)Test3:空格在句子末尾

// Test3:空格在句子末尾 [TestMethod] public void ReplaceBlankTest3() { // "helloworld " target[0] = 'h'; target[1] = 'e'; target[2] = 'l'; target[3] = 'l'; target[4] = 'o'; target[5] = 'w'; target[6] = 'o'; target[7] = 'r'; target[8] = 'l'; target[9] = 'd'; target[10] = ' '; Program.ReplaceBlank(target, maxLength); string compared = new string(this.GenerateNewTarget()); string expected = "helloworld%20"; Assert.AreEqual(compared, expected); }
(4)Test4:連續有兩個空格

// Test4:連續有兩個空格 [TestMethod] public void ReplaceBlankTest4() { // "helloworld " target[0] = 'h'; target[1] = 'e'; target[2] = 'l'; target[3] = 'l'; target[4] = 'o'; target[5] = ' '; target[6] = ' '; target[7] = 'w'; target[8] = 'o'; target[9] = 'r'; target[10] = 'l'; target[11] = 'd'; Program.ReplaceBlank(target, maxLength); string compared = new string(this.GenerateNewTarget()); string expected = "hello%20%20world"; Assert.AreEqual(compared, expected); }
(5)Test5:傳入NULL

// Test5:傳入NULL [TestMethod] public void ReplaceBlankTest5() { target = null; Program.ReplaceBlank(target, 0); char[] expected = null; Assert.AreEqual(target, expected); }
(6)Test6:傳入內容為空的字符串

// Test6:傳入內容為空的字符串 [TestMethod] public void ReplaceBlankTest6() { // "" Program.ReplaceBlank(target, maxLength); string compared = new string(this.GenerateNewTarget()); string expected = ""; Assert.AreEqual(compared, expected); }
(7)Test7:傳入內容為一個空格的字符串

// Test7:傳入內容為一個空格的字符串 [TestMethod] public void ReplaceBlankTest7() { // " " target[0] = ' '; Program.ReplaceBlank(target, maxLength); string compared = new string(this.GenerateNewTarget()); string expected = "%20"; Assert.AreEqual(compared, expected); }
(8)Test8:傳入的字符串沒有空格

// Test8:傳入的字符串沒有空格 [TestMethod] public void ReplaceBlankTest8() { // "helloworld " target[0] = 'h'; target[1] = 'e'; target[2] = 'l'; target[3] = 'l'; target[4] = 'o'; target[5] = 'w'; target[6] = 'o'; target[7] = 'r'; target[8] = 'l'; target[9] = 'd'; Program.ReplaceBlank(target, maxLength); string compared = new string(this.GenerateNewTarget()); string expected = "helloworld"; Assert.AreEqual(compared, expected); }
(9)Test9:傳入的字符串全是空格

// Test9:傳入的字符串全是空格 [TestMethod] public void ReplaceBlankTest9() { // " " target[0] = ' '; target[1] = ' '; target[2] = ' '; target[3] = ' '; target[4] = ' '; Program.ReplaceBlank(target, maxLength); string compared = new string(this.GenerateNewTarget()); string expected = "%20%20%20%20%20"; Assert.AreEqual(compared, expected); }
單元測試結果如下圖所示: