【问题标题】:Reverse linked list from position left to right [closed]从左到右的反向链表[关闭]
【发布时间】:2021-06-24 09:23:30
【问题描述】:

我遇到了运行时错误,我不知道为什么。我尝试回溯,但无法弄清楚。请帮忙!

给定的约束:

  1. 列表中的节点数为 n。
  2. 1
  3. -500
  4. 1

================================================ =================== ==31==错误:AddressSanitizer:在地址 0x602000000098 在 pc 0x000000370bdd bp 0x7ffdce3742a0 sp 上的堆使用后释放 0x7ffdce374298 在 0x602000000098 线程 T0 处读取大小为 8 #2 0x7fe06dce80b2 (/lib/x86_64-linux-gnu/libc.so.6+0x270b2) 0x602000000098 位于 16 字节区域内的 8 个字节 [0x602000000090,0x6020000000a0) 在这里由线程 T0 释放: #3 0x7fe06dce80b2 (/lib/x86_64-linux-gnu/libc.so.6+0x270b2) 先前由线程 T0 在这里分配: #4 0x7fe06dce80b2 (/lib/x86_64-linux-gnu/libc.so.6+0x270b2) 错误地址周围的影子字节: 0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c047fff7fd0:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c047fff8000: 发发发发发发发发发发发发发发发发发发发发发发发发 => 0x0c047fff8010:fa fa fd [fd]fa fa fd fd fa fa 00 00 fa fa fd fd 0x0c047fff8020: 发发发发发发发发发发发发发发发发发发发发发发发发发发 0x0c047fff8030:发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发发上 0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8050: 发发发发发发发发发发发发发发发发发发 0x0c047fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 影子字节图例(一个影子字节代表 8 个应用程序字节): 可寻址:00 部分可寻址:01 02 03 04 05 06 07 堆左红区:fa 释放的堆区域:fd 堆栈左红区:f1 堆栈中间红区:f2 堆栈右红区:f3 返回后的堆栈:f5 作用域后的堆栈使用:f8 全球红区:f9 全局初始化顺序:f6 被用户中毒:f7 容器溢出:fc 数组 cookie:ac 内部对象红区:bb ASan 内部:fe 左分配红区:ca 右分配红区:cb 阴影间隙:cc ==31==正在中止

单链表的定义。

struct ListNode {
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode *h=head;
        if(left!=right) {
            for(int i=1;i<left-1;++i) 
            {
                h=h->next;
            }
            ListNode *fr=NULL;
            ListNode *prev=h;
            h=h->next;
            for(int j=left;j<=right;++j)
            {
                fr=h->next;
                h->next=prev;
                prev=h;
                h=fr;
            }
            head->next->next=prev->next;
            head->next=prev;
            //h=prev;
            return head;
        }
        else {
            return head;
        }
        
        
    }
};

【问题讨论】:

  • 写一些简单的本地测试用例并运行,找到最简单的失败的测试用例然后调试。您在这里所做的似乎是使用一些未知大小的数据,遇到错误然后将其转储给其他人以尝试解决。
  • head-&gt;next-&gt;next=prev-&gt;next 您正在设置第二个元素的下一个而不考虑值leftright。为什么? (...为什么总是第二个?)
  • @dratenik,感谢您的指出。
  • @dratenik,那么在初始化 prev 帮助之后,第三个变量,比如说ListNode *temp=prev 呢?然后 temp-&gt;next-&gt;next=prev-&gt;nexttemp-&gt;next=prev 相应地。如果您对此有更好的方法,请告诉我。
  • @JohnZwinck,感谢您的意见!在发布其他问题时,我会记住这一点。

标签: c++ data-structures linked-list runtime-error singly-linked-list


【解决方案1】:

有几点需要注意:

  • 在取消引用之前检查next 引用是否不是空指针。例如:永远不要做head-&gt;next-&gt;next=,因为您通常不知道head-&gt;next 是否不是空指针。这是您得到错误的原因。

  • head-&gt;next = prev; 不会做正确的事,除非反转从第二个节点开始。但在所有其他情况下,这是错误的。您需要对反转部分之前的节点的引用。

  • 您没有涵盖反转从节点 1 开始的情况,因此您需要返回与作为参数的引用不同的引用。

  • 虽然任务描述保证leftright 在范围内,但我会在leftright 参数上添加一些最小的完整性检查。它的成本不超过 2 行代码。

这是建议的解决方案:

ListNode* reverseBetween(ListNode* head, int left, int right) {
    if (left < 1) left = 1;
    if (!head || left >= right) return head;
    ListNode *h = head;
    for (int i = 1; i < left - 1; ++i) {
        h = h->next;
        if (!head) return head;
    }
    ListNode *prev = left > 1 ? h->next : h;
    if (!prev) return head;
    ListNode *unmoved = left > 1 ? h : nullptr;
    ListNode *moved = prev;
    h = prev->next;
    for (int j = left + 1; j <= right && h; ++j) {
        ListNode * fr = h->next;
        h->next = prev;
        prev = h;
        h = fr;
    }
    moved->next = h;
    if (unmoved) {
        unmoved->next = prev;
        return head;
    } else {
        return prev;
    }
}

【讨论】:

    【解决方案2】:

    所以您的问题是,您引用了前一个节点并创建了一个循环,而其他一些节点仍位于您的内存中,但不再在代码中解决。通常垃圾收集器会清理它,但在 C++ 中,您必须自己管理内存分配。

    我稍微调整了您的逻辑,以阻止它创建循环 (变量被重命名)

    ListNode* reverseBetween(ListNode* head, int left, int right) {
      ListNode* current = head;
      if (left != right) {
        for (int i = 1; i < left - 1; ++i)
        {
          current = current->next;
        }
        
        ListNode* ankerLeft = left == 1 ? nullptr : current;
        ListNode* ankerRight = current->next;
        
        ListNode* nextNode = nullptr;
        ListNode* prevNode = ankerRight;
        
        for (int j = left-1; j <= right; ++j)
        {
          if (current != nullptr) {
            nextNode = current->next;
            current->next = prevNode;
            prevNode = current;
            current = nextNode;
          }
        }
    
        if (ankerLeft != nullptr) {
          ankerLeft->next = prevNode;
          ankerRight->next = current;
        }
        else {
          head->next = current;
          head = prevNode;
        }
    
        return head;
      }
      else {
        return head;
      }
    }
    

    现在也可以反转整个列表。 (感谢@dratenik 的评论)

    这对我来说很好,不会丢失任何保留的内存


    编辑:

    在进一步阅读您的逻辑之后,您唯一缺少的是对反向列表的最后一个节点的引用,以再次正确连接到原始列表 head-&gt;next-&gt;next = prev-&gt;next; 不会给你正确的节点,因为你已经覆盖了节点,我在你的代码中包含了一个anker,以保持对逆向算法的最后一个节点的引用。另外prev-&gt;next 是错误的节点来引用你的结束,因为prev-&gt;next已经被反转它会返回错误的节点。

    这是您添加了anker的代码,您的反向算法运行良好,我稍微调整了后逻辑,现在应该可以正常运行了。

    ListNode* reverseBetween(ListNode* head, int left, int right) {
      ListNode* h = head;
      if (left != right) {
      for (int i = 1; i < left - 1; ++i)
      {
        h = h->next;
      }
      ListNode* fr = NULL;
      ListNode* prev = h;
      ListNode* ankerRight = h->next;
      h = h->next;
      for (int j = left; j <= right; ++j)
      {
        fr = h->next;
        h->next = prev;
        prev = h;
        h = fr;
      }
      // this would overwrite the wrong reference and create a loop
      // head->next->next = prev->next;
    
      head->next = prev;
      ankerRight->next = h;
      return head;
      }
      else {
        return head;
      }
    }
    

    如果您想反转整个列表,您的逻辑仍然会失败。

    希望我能提供帮助并清楚地解释一切。

    【讨论】:

    • 反例:reverseBetween([10,20,30],1,3) - 预期 [30,20,10],实际结果:segfault
    • @dratenik 哦,你是对的,如果你试图反转整个列表,逻辑会起作用,我会看看它。
    • @dratenik 解决了问题,现在一切正常,感谢您的回复
    • 这永远不会移动第一个节点,即使left=1
    • @trincot 如果你想从左边开始= 1,第一个节点将是右边的节点,所以我们没有anker,如果你想从节点2开始,你仍然想要头部作为参考,因此当您反转(2,5)时不移动第一个节点是有意义的
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-07
    • 2011-03-04
    • 1970-01-01
    • 1970-01-01
    • 2014-04-18
    • 1970-01-01
    • 2020-04-03
    相关资源
    最近更新 更多