如何检查圆形单链表是否是回文?

时间:2012-09-10 05:21:03

标签: algorithm data-structures linked-list

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

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

现在我如何验证此列表是否为pallindrome?

我想到了以下解决方案:

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

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

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

    由于

5 个答案:

答案 0 :(得分:4)

由于您正在处理单个链接列表,因此必须使用一些额外空间或更多额外时间。

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

我们修改了所谓的Floyd的循环寻找算法:

  • 两个指针,“慢”和“快”,都从列表头开始;慢指针每次迭代前进一个列表元素,快速指针两个元素
  • 在每个步骤中,慢速指针推送堆栈上的当前元素

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

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

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

答案 1 :(得分:0)

这是伪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

答案 2 :(得分:0)

既然你知道链接列表确实是一个循环,并且你只是从头开始寻找回文,你可以让自己更容易。

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

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

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

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

然而你的停止状况需要多一点。有两种情况:

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

因此,如果H == T或H ==(T.Right()),则停止。

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

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

答案 3 :(得分:0)

只需粘贴我的实施,以便我们可以在这里完整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;
}

答案 4 :(得分:0)

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

修改Philip的解决方案:

我们修改了所谓的Floyd的循环寻找算法:

两个指针,“慢”和“快”,都从列表头开始;慢指针每次迭代前进一个列表元素,快速指针两个元素 在每个步骤中,慢速指针将当前元素推送到堆栈上 如果快速指针到达列表的末尾,则慢速指针指向列表的中间位置,所以现在:

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

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