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

Posted

技术标签:

【中文标题】为啥我的链表代码在模块化可重用 JavaScript 函数中不起作用?【英文标题】:Why does my linked list code not work in a modular reusable JavaScript function?为什么我的链表代码在模块化可重用 JavaScript 函数中不起作用? 【发布时间】:2022-01-13 12:19:23 【问题描述】:

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

我正在处理链表格式的question 2 on Leetcode“添加 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;

【问题讨论】:

【参考方案1】:

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

它们确实是对象。变量可以对这些对象有引用。这不是将参数传递给函数时发生的特定情况。例如,l3 是一个引用。正是这个引用按值传递给函数,本地变量lb接收这个作为它自己的值(对象引用被复制)。当那个函数赋值lb,那么这只会影响那个局部变量...而不是那个用来把这个值传递给函数的变量。

根据经验:当 JavaScript 函数分配一个值给它的一个参数变量时,这只对那个局部变量有影响1

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

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

避免代码重用

在你的情况下,l3 的处理应该像你对待carry 一样。就像函数返回重新分配的carry 一样,它也应该返回l3。您可以通过返回具有这两个值的数组来做到这一点。然后您可以在调用方使用解构赋值。老实说,这看起来并不那么优雅,但是有一种不同的方法可以避免代码重复:

请考虑第二个和第三个while 循环会进行迭代的情况永远不会发生。这是因为在第一个循环结束后,l1l2 中的一个最多可以是非 null

因此,抽象出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此规则存在罕见的例外情况。有一些别名行为,例如 arguments 在非严格模式下的工作方式。

【讨论】:

以上是关于为啥我的链表代码在模块化可重用 JavaScript 函数中不起作用?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的链表内容在退出函数时会消失?

将第一个节点添加到hashmap中的链表时,为啥必须将新节点直接分配给索引指针?

为啥我的打印功能不起作用?链表

Linux Kernel数据结构:链表

C语言写的链表。明明没有错误,为啥编译器还会报错,?而且还爆出100+的错误,求解。

如何从java中的链表中删除一个对象?