一道单链表题引发的思考

最近在刷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。请提高警惕,也不忘初心!

 

posted @ 2019-07-11 16:58  freephp  阅读(386)  评论(0编辑  收藏  举报