问题:我有一个链表(即只有指向下一个节点的指针的列表)。另外,这是一个循环链表(在这个例子中,最后一个节点有一个指向第一个节点的指针)。列表中的每个节点都包含一个char。
此类列表的示例可以是:a-> b-> c-> b-> a
现在我如何验证此列表是否为pallindrome?
我想到了以下解决方案:
从列表头部开始。找到列表的长度,然后查找中间节点。现在从列表的头部再次开始并继续将元素推入堆栈直到中间。现在遍历mid和pop元素中的列表。如果弹出元素的值等于当前节点的值。如果没有,返回false。否则,继续直到堆栈为空,我们已经验证了所有字符。缺点:使用额外的堆栈空间:(
从列表头部开始。找到列表的长度,然后查找中间节点。现在反转这个列表的下半部分。然后使用2个指针(一个指向开始,另一个指向mid + 1'元素),检查值是否相同。如果没有,返回false。否则继续,直到我们再次到达开始节点。缺点:改变原始数据结构。
有没有更优雅的方法来解决这个问题(希望不使用O(n)额外空间或更改原始列表)?我对算法感兴趣而不是任何特定的实现。
由于
答案 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.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)时间复杂度,不需要额外的空间。