最近在刷leetCode,遇到兩道原理相近的題,覺得十分有趣和典型,通過思考和借鑑其他coder寫法發現了新的smell。
先從簡單的那道題聊起,原題是這樣描述了:
給定兩個字串形式的非負整數 num1 和num2 ,計算它們的和。
注意:
num1 和num2 的長度都小於 5100.
num1 和num2 都只包含數字 0-9.
num1 和num2 都不包含任何前導零。
你不能使用任何內建 BigInteger 庫, 也不能直接將輸入的字串轉換為整數形式。
舉個具體例子:
輸入 : "10",
:"10"
輸出 : ”20"
拿著這個題,第一反應就是想利用php的靈活性搞點黑魔法的實現。因為php的弱型別,字串和數字之間的轉換不要太酸爽,於是秒寫出了下面的解法1:
function addStrings($num1, $num2) { $num3= $num1 + $num2; return (string)$num3; }
這也太簡單了吧?
當然不可能這麼容易,因為會遇到$num1和$num2太大,出現諸如下面的輸出報錯:
這就涉及到大int相加的進位問題,關於這個問題參考別人的實現如下:
function bigDataAdd($a,$b) { $m = strlen($a); $n = strlen($b); $num = $m>$n?$m:$n;//取最長數進行迴圈相加和進位 $result = '';//結果 $flag = 0; //進位標誌 while($num--){ $t1 = 0;//用來儲存當前位加數 $t2 = 0;//用來儲存當前位被加數 if($m>0){ $t1 = $a[--$m]; } if($n>0){ $t2 = $b[--$n]; } $t = $t1+$t2+$flag;//當前位加法運算考慮上一輪的進位標誌 $flag = intval($t/10);//本輪是否進位 $result = ($t%10).$result;//向高位新增結果 } //最高位加完發現還有進位標誌,需要再向最高位+1 if ($flag) { $result = $flag.$result; } return $result; }
迴圈進位相加,最後完成大int相加。
所以第一版的程式碼改為:
function addStrings($num1, $num2) { $num3= bigDataAdd($num1, $num2); return (string)$num3; }
這種思路就是:將兩個非負數字符串當成整型相加,然後直接轉字串返回結果。唯一要特別處理的就是大int相加進位的問題。
仔細想想,這道題從出題人的角度可能並不是只想利用php的特性,畢竟最開始能使用的實現語言並沒有php,要考慮其他程式設計語法的實現(java,c++,c),可見這道題正統解法應是他法。
另外一種解法就是要把字串的每一個下標進行遍歷相加,處理好進位問題。
所以我們需要用一個進位符$carry去記錄每次是否進位,然後也利用它來儲存每次遍歷中的臨時結果。
大概的思路:
分別去倒序遍歷$num1和$num2,處理好進位,然後再返回最後結果(reverse).
具體的實現是:
unction addStrings($num1, $num2) { $carry = 0; $i = strlen($num1) - 1; $j = strlen($num2) - 1; $return_str = ''; while($i >=0 || $j >= 0 || $carry != 0) { if ($i >= 0) { $carry += $num1[$i] + 0; $i--; } if ($j >= 0) { $carry += $num2[$j] + 0; $j--; } $return_str .= (string)($carry % 10); $carry = floor($carry / 10); } return strrev($return_str); }
這就是解法二,效能和耗時都蠻不錯的:打敗了94%的php使用者。
如果再抽象一下我們的入參,其實字串也是一種單連結串列的具體實現。所以也可以將字串封裝成ListNode(簡單的單連結串列)。其他思路和上面是一樣的,只是將遍歷兩個字串變成遍歷兩個連結串列。
具體如下:
class NewSolution { public function addTowStrings($num1, $num2) { $list1 = null; $list2 = null; // 構建list1 $length1 = strlen($num1); $length2 = strlen($num2); $list1 = $this->makeStringToListNode(strrev($num1)); $list2 = $this->makeStringToListNode(strrev($num2)); $q = $list1; $p = $list2; $carry = 0; $dummyNode = new ListNode(0); $currentNode = $dummyNode; $newNode = $currentNode; while($q != null || $p != null || $carry != 0) { //print_r("come here"); // 遍歷兩個連結串列 if ($q != null) { $carry += $q->val; } if ($p != null) { $carry += $p->val; } $sum = $carry % 10; $currentNode->next = new ListNode($sum); $currentNode = $currentNode->next; $carry = floor($carry / 10); if ($q != null) { $q = $q->next; } if ($p != null) { $p = $p->next; } } $realNewNode = $newNode->next; $result = ""; while($realNewNode != null) { $result .= (string) $realNewNode->val; $realNewNode = $realNewNode->next; } return strrev($result); } public function makeStringToListNode($string) { $length = strlen($string); // 啞節點 $dummyNode = new ListNode(0); $currentNode= $dummyNode; $return_node = $currentNode; for ($i = 0; $i < $length; $i++) { $currentNode->next = new ListNode($string[$i]); $currentNode = $currentNode->next; } return $return_node->next; } } $s = new NewSolution(); $str = $s->addTowStrings("13002", "11");
這種寫法比較學院派了,效能和記憶體使用都是非常差的。
所以刷題既要保證能get到核心考點,又要兼顧效能才行。而php的確因為過於自由化導致我們可能會深陷到dark magic的marsh。請提高警惕,也不忘初心!