【问题标题】:Why is one algorithm faster than the other (adding numbers with linked list)为什么一种算法比另一种算法快(用链表添加数字)
【发布时间】:2019-10-23 11:04:37
【问题描述】:

我需要帮助理解为什么以下问题的第二个解决方案比第一个解决方案运行得更快。

问题出自leetcode。问题是:

给定两个代表两个非负整数的非空链表。这些数字以相反的顺序存储,它们的每个节点都包含一个数字。将两个数字相加并将其作为链表返回。 你可以假设这两个数字不包含任何前导零,除了数字 0 本身。

一种解决方案是:

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
import numpy as np

class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        sum_ = ListNode(0)
        root = sum_
        carry_over = 0

        # O(n of bigger list) - time
        # if there are still numbers to be added in list 1 or list 2, do
        while l1 or l2:
            # if list 1 is not null and has a value
            if l1 and l1.val:
                # add it to our sum ListNode value
                sum_.val += l1.val
            if l2 and l2.val:
                sum_.val += l2.val

            # we might need to carry over the decimal from the previous sum
            sum_.val += carry_over
            # if the new sum is >= 10, then we need to carry over the decimal
            carry_over = np.floor(sum_.val / 10)

            # if carry over is more than zero, we need to just use the remainder. i.e. if 11, then sum at this location is 1; and we carry over 1 forward.
            if carry_over > 0: sum_.val = sum_.val % 10

            # type case from float to int. Why are we in float anyway?
            sum_.val = int(sum_.val)

            l1_next = l1 and l1.next
            l2_next = l2 and l2.next

            # continue, if there are more numbers
            if l1_next or l2_next:
                sum_.next = ListNode(0)
                sum_ = sum_.next
                l1 = l1.next if l1_next else None
                l2 = l2.next if l2_next else None
            # stop here, if no more numbers to add.
            else:
                if carry_over:
                    sum_.next = ListNode(int(carry_over))
                l1, l2 = None, None

        return root

另一个是:

class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        # these are array representation of the linked list
        input_1 = []
        input_2 = []

        # loop through all nodes in l1 linked list and add to input_1
        while l1 is not None:
            input_1.append(str(l1.val))
            l1 = l1.next

        while l2 is not None:
            input_2.append(str(l2.val))
            l2 = l2.next

        # this is string numbers from l1 and l2, but in the correct order (not reversed)
        input_1 = "".join(reversed(input_1))
        input_2 = "".join(reversed(input_2))

        # now typecast the strings to integers and add
        out = str(int(input_1) + int(input_2))

        # lastly, create a ListNode with this number to bbe returned
        last = None
        for x in out:
            n = ListNode(x)
            n.next = last
            last = n

        return last

第一个解决方案的运行时间约为 200 毫秒,而第二个解决方案的运行时间为 100 毫秒。我可以看到两者都是 O(n of the large list)。我想第一个运行速度较慢的原因是由于地板和模运算?最初,我认为第二个会因为大量的字符串类型转换而运行得更慢。

【问题讨论】:

  • 你不应该向在线评委或比赛学习的一件事是他们命名变量的方式。不要使用一两个字母的非描述性名称,而是将它们命名为描述其用途的名称。还包括描述您的代码的 cmets、它的作用以及 为什么 它会做什么。最后,不要将此类网站用作学习资源,而应将其用作练习您在其他地方所学知识的一种方式(例如参加真正的现场课程)。
  • 1.同意; 2. 完成; 3.不学,按你说的练。很好奇为什么第一个解决方案的性能比第二个差两倍。
  • 你可以随时抽出line_profiler
  • @isquared-KeepitReal 当您询问性能时,请附上您用于对解决方案进行基准测试的代码。
  • 提交解决方案时在leetcode上给出了性能。

标签: python performance linked-list


【解决方案1】:

两种解决方案的时间复杂度相同O(max(n,m)),其中nm 是输入链表的长度。

为了计算 ms 时间差,我们应该查看每个解决方案中的一些开销。

在第一个解决方案中,您使用的是numpy,这需要时间来导入。您可以使用本机 sum_.val // 10 而不是 np.floor(sum_.val / 10)。这可能会显着加快求解速度。

对这一行进行性能评估显示了显着的速度提升(~18 倍)。在 leetcode 中导入 numpy 可能还会产生开销。

timeit.timeit('np.floor(x / 10)', globals=globals(), number=1000000)
# 0.826268769800663

timeit.timeit('x // 10', globals=globals(), number=1000000)
# 0.04531952366232872

在第二种解决方案中,这里的主要加速是使用"".join() 从列表中获取字符串。这是O(n)。如果解决方案改为使用字符串连接,我们会看到O(n^2),因为字符串是不可变的,您必须不断地将两个字符串复制到一个新字符串中。

str 连接性能不佳的示例:

input_1 = ""
while l1 is not None:
    input_1 += str(l1.val))
    l1 = l1.next

需要注意的是,leetcode 性能评估会在每次提交时发生变化。虽然您应该会看到第一个解决方案的运行时间在删除 numpy 后有了显着改善!

【讨论】:

  • 感谢字符串连接技巧!
猜你喜欢
  • 1970-01-01
  • 2011-10-18
  • 2021-04-24
  • 1970-01-01
  • 1970-01-01
  • 2011-05-28
  • 1970-01-01
  • 2018-10-03
  • 1970-01-01
相关资源
最近更新 更多