【问题标题】:How to check if a circular single linked list is pallindrome or not?如何检查循环单链表是否为回文?
【发布时间】:2012-09-02 23:15:23
【问题描述】:

问题:我有一个单链表(即只有指向下一个节点的指针的列表)。此外,这是一个循环链表(在此示例中,最后一个节点具有指向第一个节点的指针)。列表中的每个节点都包含一个字符。

此类列表的示例可以是:a->b->c->b->a

现在如何验证此列表是否为回文?

我想到了以下解决方案:

  1. 从列表的头部开始。找到列表的长度,然后找到中间节点。现在从列表的头部重新开始,并继续将元素推入堆栈直到中间。现在从 mid 和 pop 元素遍历列表。如果弹出元素的值等于当前节点的值。如果不是,则返回 false。否则,继续直到堆栈为空并且我们已经验证了所有字符。缺点:使用额外的堆栈空间:(

  2. 从列表的头部开始。找到列表的长度,然后找到中间节点。现在反转这个列表的第二半。然后使用 2 个指针(一个指向开始,另一个指向中间 + 1 个元素),检查值是否相同。如果不是,则返回 false。否则继续,直到我们再次到达起始节点。缺点:更改原始数据结构。

有没有更优雅的方法来解决这个问题(希望不使用 O(n) 额外空间或更改原始列表)?我对算法感兴趣,而不是任何具体的实现。

谢谢

【问题讨论】:

  • 为什么这个人删除了他的答案,其中有类似于 2 的建议?
  • 这是我的问题,您想检查它是否包含列表中head 中的回文?或者回文可以从任何节点开始?
  • @st0le 我在考虑回文从头部开始的情况。这比回文可以从任何节点开始的情况更简单(或者我们必须在列表中找到最长的回文)
  • 如果您可以使用额外的 O(n) 内存,则不需要弗洛伊德算法,也不需要答案中表现出的任何聪明才智。只需将列表复制到数组并检查它是否是回文。完成。

标签: algorithm data-structures linked-list


【解决方案1】:

这是在伪 Haskell 中(这些天我不记得确切的语法了)并且我已经为非循环情况编写了 - 为了解决这个问题,只需将与 [] 匹配的子句替换为您的任何条件用来识别你已经绕了一圈。

p(xs) = q(xs, Just(xs)) != Nothing


q([], maybeYs) = maybeYs

q(x : xs, Nothing) = Nothing

q(x : xs, maybeYs) =
    let maybeZs = q(xs, maybeYs) in
    case maybeZs of
        Nothing        -> Nothing
        Just (x :: zs) -> Just(zs)
        otherwise      -> Nothing

【讨论】:

  • 你能解释一下吗,不知道haskell :(
  • @AlanFoster - 嗨。 “高水平”是一件好事,在这里:它意味着你可以用最少的低级干扰来表达自己(其中低级是机器或语言级别而不是问题级别)。
  • @AkashdeepSaluja - 嗨。这只是从列表的末尾到前面,检查前面的每个列表项是否与后面的对应项匹配。 Maybe 类型有两个值:Nothing 或 Just(x),对于某个值 x。在不太文明的语言中,您可以使用 null 或非 null。如果此列表不是回文,则函数 q 返回 Nothing。
  • @AkashdeepSaluja - 理解我上面所做的一个简单方法是我将源列表与其反转进行比较。如果它们匹配,则为回文。
  • @Rafe 我认为它是单链表,不可能从后到前遍历。
【解决方案2】:

由于您正在处理一个单个链表,您必须使用一点额外的空间或更多的额外时间。

您的第一种方法听起来很合理,但您可以在一次运行中确定列表的长度回文性。

我们修改了所谓的弗洛伊德循环寻找算法:

  • 两个指针,“slow”和“fast”,都从链表头开始;慢指针每次迭代前进一个列表元素,快指针两个元素
  • 在每一步中,慢速指针将当前元素压入堆栈

如果快指针到达链表的末尾,慢指针指向链表的中间,所以现在:

  • 慢速指针前进到列表的末尾,并且在每一步中:
  • 它从堆栈中弹出一个元素并将其与当前列表元素进行比较(如果它们不相等,则返回false
  • 如果慢指针到达链表末尾,则为回文

对于具有奇数个元素的列表,需要做一些额外的工作。

【讨论】:

  • 它很聪明,但它仍然需要 O(n) 额外空间并且它是 O(n^2) 复杂度(您需要检查所有节点作为起点) - 这类似于简单将列表复制到数组中,然后进行检查
  • @izprgmr 我认为列表的头部已经给出,所以只有一个起点和复杂度 O(n)。
  • 时间复杂度和空间复杂度都是O(n)。您只对列表进行一次迭代,就可以存储一半的元素。
  • Philip:你的方法比我的好得多。谢谢:)
【解决方案3】:

既然您知道链表确实会产生一个循环,并且您只是在寻找从头开始的回文,您可以自己解决这个问题。

A -> B -> C -> B -> A

在这种情况下,从 head 处的指针(称为 H)和 head.Left() 处的指针开始(称为 T)。

现在继续将头指针 H 向右移动,将尾指针 T 向左移动。

当您遍历列表时,验证这些元素的值是否相等(即回文)。

但是,您的停止条件需要更多时间。有两种情况:

  • 两个指针都指向同一个元素(即奇数个元素)
  • H 指针指向 T 右侧的元素。

所以,如果 H==T 或 H==(T.Right()) 则停止。

使用这种方法(或类似方法),您只需访问每个元素一次。

如果您不知道链表是​​否是循环的,请像其他解决方案一样使用 Tortoise and Hare 方法。

【讨论】:

  • 当列表是双向链表时,您的解决方案会很好。然而,就我而言,我没有这样的设施。它是一个单链表,仅在一个方向上指向节点。
【解决方案4】:

只需粘贴我的实现,以便我们相互比较,完整的test 在这里:

/**
 * Given a circular single linked list and the start pointer, check if it is a palindrome
 * use a slow/fast pointer + stack is an elegant way
 * tip: wheneve there is a circular linked list, think about using slow/fast pointer
 */

#include <iostream>
#include <stack>
using namespace std;

struct Node
{
    char c;
    Node* next;

    Node(char c) {this->c = c;}
    Node* chainNode(char c)
    {
        Node* p = new Node(c);
        p->next = NULL;
        this->next = p;
        return p;
    }
};

bool isPalindrome(Node* pStart)
{
    Node* pSlow = pStart;
    Node* pFast = pStart;

    stack<Node*> s;
    bool bEven = false;
    while(true)
    {
        // BUG1: check fast pointer first
        pFast = pFast->next;
        if(pFast == pStart)
        {
            bEven = false;
            break;
        }
        else
        {
            pFast = pFast->next;
            if(pFast == pStart)
            {
                bEven = true;
                break;
            }
        }

        pSlow = pSlow->next;
        s.push(pSlow);

    }
    if(s.empty()) return true; // BUG2: a, a->b->a
    if(bEven) pSlow = pSlow->next; // BUG3: a->b->c->b->a, a->b->c->d->c->b->a: jump over the center pointer

    while(!s.empty())
    {
        // pop stack and advance linked list
        Node* topNode = s.top();
        s.pop();
        pSlow = pSlow->next;

        // check
        if(topNode->c != pSlow->c)
        {
            return false;
        }
        else
        {
            if(s.empty()) return true;
        }
    }

    return false;
}

【讨论】:

    【解决方案5】:

    我认为我们不需要额外的空间。这可以通过 O(n) 复杂度来完成。

    修改菲利普的解决方案:

    我们修改了所谓的弗洛伊德循环寻找算法:

    两个指针,“slow”和“fast”,都从链表头开始;慢指针每次迭代前进一个列表元素,快指针两个元素 在每一步中,慢指针将当前元素压入堆栈 如果快指针到达链表的末尾,慢指针指向链表的中间,所以现在:

    在链表的开头有另一个指针(起始点),现在 - 一一移动起始指针和慢速指针并比较它们 - 如果它们不相等,则返回 false - 如果慢指针到达链表末尾,则为回文

    这是 O(n) 时间复杂度,不需要额外的空间。

    【讨论】:

    • "慢速指针将当前元素压入堆栈"这不会需要更多空间吗?...
    猜你喜欢
    • 1970-01-01
    • 2012-09-21
    • 2017-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多