为什么我的链表代码在模块化可重用的 JavaScript 函数中不起作用?

Why does my linked list code not work in a modular reusable JavaScript function?

提问人:GNG 提问时间:12/8/2021 最后编辑:trincotGNG 更新时间:12/9/2021 访问量:33

问:

为什么当我使代码更加模块化时,我的链表解决方案会失败?

我正在以链表格式处理 Leetcode “添加 2 个数字”的问题 2。

这是个问题:

你会得到两个非空的链表,代表两个非负整数。这些数字以相反的顺序存储,并且它们的每个节点都包含一个数字。将两个数字相加,然后以链表形式返回总和。

您可以假设这两个数字不包含任何前导零,除了数字 0 本身。

我有一个带有重复代码的工作解决方案。 当我将最后两个函数的主体抽象为可重用的函数时,我的解决方案失败了。为什么会这样?

我所做的一个假设是链表节点是对象,因此应该通过引用传递。至少在 JavaScript 中是这样。

这是我成功的解决方案(在使代码更加模块化之前)......

var addTwoNumbers = function(l1, l2) {
    let l3 = new ListNode(0, null);
    let head = l3;
    let carry = 0;
    while (l1 != null && l2 != null) {
        l3.next = new ListNode(carry, null);
        l3 = l3.next;
        let sum = l1.val + l2.val + l3.val;
        let ones = sum % 10;
        carry = Math.floor(sum / 10);
        l3.val = ones;
        l1 = l1.next;
        l2 = l2.next;
    }
    while (l1 != null) {
        l3.next = new ListNode(carry, null);
        l3 = l3.next;
        let sum = l1.val + l3.val;
        let ones = sum % 10;
        carry = Math.floor(sum / 10);
        l3.val = ones;
        l1 = l1.next;
    }
    while (l2 != null) {
        l3.next = new ListNode(carry, null);
        l3 = l3.next;
        let sum = l2.val + l3.val;
        let ones = sum % 10;
        carry = Math.floor(sum / 10);
        l3.val = ones;
        l2 = l2.next;
    }
    if (carry == 1) {
        l3.next = new ListNode(carry, null);
    }
    return head.next;
};

这是我失败的解决方案......

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function(l1, l2) {
    let l3 = new ListNode(0, null);
    let head = l3;
    let carry = 0;
    while (l1 != null && l2 != null) {
        l3.next = new ListNode(carry, null);
        l3 = l3.next;
        let sum = l1.val + l2.val + l3.val;
        let ones = sum % 10;
        carry = Math.floor(sum / 10);
        l3.val = ones;
        l1 = l1.next;
        l2 = l2.next;
    }
    while (l1 != null) {
        carry = addRemainingNodes(l1, l3, carry);
    }
    while (l2 != null) {
        carry = addRemainingNodes(l2, l3, carry);
    }
    if (carry == 1) {
        l3.next = new ListNode(carry, null);
    }
    return head.next;
};

function addRemainingNodes(la, lb, carry) {
    lb.next = new ListNode(carry, null);
    lb = lb.next;
    let sum = la.val + lb.val;
    let ones = sum % 10;
    carry = Math.floor(sum / 10);
    lb.val = ones;
    la = la.next;
    return carry;
}
JavaScript 链接列表 引用传递 值抽象

评论


答:

1赞 trincot 12/9/2021 #1

我所做的一个假设是链表节点是对象,因此应该通过引用传递。

的确,它们是对象。变量可以具有对这些对象的引用。这不是将参数传递给函数时发生的特定情况。例如,是一个参考。正是这个引用通过值传递给函数,局部变量将其作为自己的接收(对象引用被复制)。当该函数赋值给 时,这只会影响该局部变量...而不是用于将此值传递给函数的变量。l3lblb

根据经验:当 JavaScript 函数为其参数变量之一赋值时,这只会对该局部变量1 产生影响。

但是,当函数改变参数变量的属性时,这将影响作为参数传递的对象。

总而言之,没有像 C++ 中那样的引用传递。JavaScript 使用按值传递机制,考虑到对象的值实际上是一个参考。

避免代码重用

就您而言,治疗应该像您对待的那样。就像函数返回被重新分配一样,它也应该返回.您可以通过返回具有这两个值的数组来做到这一点。然后,您可以在调用端使用解构分配。老实说,这看起来并不那么优雅,但有一种不同的方法可以避免代码重复:l3carrycarryl3

考虑到第二个和第三个循环永远不会进行迭代。这是因为在第一个循环完成后,最多只能有一个 和 是非。whilel1l2null

因此,对 和 之间的这种差异进行抽象,并仅使用一个循环来替换这两个循环:l1l2

    let lrest = l1 || l2; // This will select the non-null list if there is one
    while (lrest != null) {
        l3.next = new ListNode(carry, null);
        l3 = l3.next;
        let sum = lrest.val + l3.val;
        let ones = sum % 10;
        carry = Math.floor(sum / 10);
        l3.val = ones;
        lrest = lrest.next;
    }

1此规则存在罕见的例外情况。有一些别名行为,例如参数在非严格模式下的工作方式。