如何在不使用额外空间的情况下检查双向链表是不是为回文?
Posted
技术标签:
【中文标题】如何在不使用额外空间的情况下检查双向链表是不是为回文?【英文标题】:How can I check if a doubly-linked list is a palindrome without using extra space?如何在不使用额外空间的情况下检查双向链表是否为回文? 【发布时间】:2013-09-29 20:43:13 【问题描述】:最近,我去面试,他们问我“检查下面的双向链表是否是回文而不使用任何额外的存储,例如 STL 的链表、堆栈、队列、树、字符串、字符数组等.." 但是我无法给出完美的解决方案。
下面是双向链表的图片:
这不是一个家庭作业问题,而只是一个寻找任何解决方案的问题。
【问题讨论】:
用两个迭代器编写一个循环,一个从列表的开头向前移动,另一个从列表的末尾向后移动。循环直到覆盖整个列表。如果迭代器在每一步都指向相同的值,则列表是回文。坦率地说,这似乎微不足道。 @john 如果您在每个点进行迭代和检查,就会看到该图,那么上面的双链表不是 pallindrome,但是当我们看到它是一个 pallindrome 时……这是我失败的地方…… 我怀疑是否可以满足 no 额外空间的要求。您可以期望的最好的结果是将其限制在一个小的、恒定数量的额外空间(例如,两个指针/迭代器)。 是的,这可能是 O(1) 内存的不精确说法。 图中的 100 ... 400 是什么?如果它们是内存地址,则该链表未正确链接。 【参考方案1】:声明两个迭代器,start 和 end。然后遍历列表并同时减少/增加它们,在每一步进行比较。注意:此算法假定您已正确覆盖运算符,但它也适用于任何类型的列表,而不仅仅是数字列表。
for(int i=0;i<list.size()/2;i++)
if(*start!=*end) return false;
start++;
end--;
return true;
这里的关键是您使用的是迭代器,而不是直接使用列表。
【讨论】:
【参考方案2】:template<typename List>
bool isPalindrome(List const &list)
auto b = list.begin();
auto e = list.end();
while (b != e)
--e;
if (b == e) // for lists with exactly 1 or an even number of elements
break;
if (*b != *e)
return false;
++b;
return true;
我们不能使用>
或>=
,因为列表迭代器不是随机访问(在大多数实现中),因此只能比较相等/不相等。 std::distance
是一个选项,但对于非随机访问迭代器,它只是做了很多增量,这很慢。相反,循环中间的检查处理大于的情况,因此整个函数可以只使用相等比较来编写。
【讨论】:
如果容器只有一个元素,你就有一个错误。在循环中测试b == e
之前,您可以让迭代器彼此经过。【参考方案3】:
这是我用于回文测试的代码。它需要两个迭代器并正确处理空范围和奇/偶长度范围。
template <typename BidIt>
bool is_palindrome(BidIt first, BidIt last)
if (first == last) return false; // empty range
for (;;)
if (first == --last) break;
if (*first != *last) return false; // mismatch
if (++first == last) break;
return true; // success
【讨论】:
【参考方案4】:这里的问题是您有自然迭代器,其元素可以是多个字符,但您只想对字符序列执行操作。因此,我将使用boost::iterator_facade
定义另一个按照我们需要的方式运行的迭代器类型。 (通常boost::iterator_adaptor
这种事情更方便,但在这种情况下它没有帮助。)
该图建议了一个原始的类 C 结构和指针设置,所以我假设自定义双向链表是这样定义的:
struct list_node
list_node* prev;
list_node* next;
const char* data;
;
class LinkedList
public:
list_node* head() const;
list_node* tail() const;
// ...
;
自定义迭代器类需要包含一个list_node*
指针和一个指向char
数组元素的指针。
#include <boost/iterator_facade.hpp>
class ListCharIter :
public boost::iterator_facade<
ListCharIter, // Derived class for CRTP
const char, // Element type
std::bidirectional_iterator_tag > // Iterator category
public:
ListCharIter() : m_node(nullptr), m_ch(nullptr)
// "Named constructors":
static ListCharIter begin(const LinkedList& listobj);
static ListCharIter end(const LinkedList& listobj);
private:
list_node* m_node;
const char* m_ch;
ListCharIter(list_node* node, const char* where)
: m_node(node), m_ch(where)
// Methods iterator_facade will use:
char dereference() const;
bool equal(const ListCharIter& other) const;
void increment();
void decrement();
// And allow boost to use them:
friend class boost::iterator_core_access;
;
仅对于过去的迭代器,我们将允许 m_ch
指向最后一个节点的终止 '\0'
。在没有元素的列表的特殊情况下,我们将为单个迭代器设置两个成员 null ,该迭代器既是开始又是结束,并且不能被取消引用。
inline ListCharIter ListCharIter::begin(const LinkedList& listobj)
list_node* node = listobj.head();
const char* str = nullptr;
if (node)
str = node->data;
return ListCharIter(node, str);
inline ListCharIter ListCharIter::end(const LinkedList& listobj)
list_node* node = listobj.tail();
const char* nul = nullptr;
if (node)
nul = node->data;
while (*nul != '\0') ++nul; // Find the '\0'.
return ListCharIter(node, nul);
dereference()
和 equal()
是微不足道的:
inline char ListCharIter::dereference() const
return *m_ch;
inline bool ListCharIter::equal(const ListCharIter& other) const
return this->m_node == other.m_node && this->m_ch == other.m_ch;
最后,要前进或后退,基本的想法是只更改m_ch
,如果有意义,或者更改m_node
,否则。
inline void ListCharIter::increment()
++m_ch;
// If m_node->next is null, we're advancing
// past the end of the entire list.
while (*m_ch == '\0' && m_node->next)
m_node = m_node->next;
m_ch = m_node->data; // Start of new node.
// while loop repeats if m_node contains "".
inline void ListCharIter::decrement()
if (m_ch == m_node->data)
// Already at the start of this node.
do
m_node = m_node->prev;
m_ch = m_node->data; // Start of new node.
// while loop repeats if m_node contains "".
while (*m_ch == '\0');
// Find the char before the terminating nul.
while (m_ch[1] != '\0') ++m_ch;
else
--m_ch;
现在您可以在普通回文算法(以及许多其他算法)中使用该自定义迭代器。
template<typename BidirIter>
bool is_palindrome(BidirIter start, BidirIter stop)
for (;;)
if (start == stop) return true;
if (*start != *stop) return false;
++start;
if (start == stop) return true;
--stop;
bool is_palindrome(const LinkedList& the_list)
return is_palindrome(ListCharIter::begin(the_list),
ListCharIter::end(the_list));
【讨论】:
【参考方案5】:我想添加一个使用std::list
、std::advance()
和std::equal()
的C++11 解决方案(由于range-based for
loop 和auto
specifier),从而得到非常短的代码:
#include <list>
#include <algorithm>
#include <iostream>
using namespace std;
int main()
// Fill a doubly-linked list with characters.
string str = "racecar";
list<char> l;
for (char c : str)
l.emplace_back(c);
// Find the center of the list.
auto it = l.begin();
advance(it, l.size() / 2);
// Compare the first half of the list to the second half.
if (equal(l.begin(), it, l.rbegin()))
cout << str.c_str() << " is a palindrome." << endl;
else
cout << str.c_str() << " is not a palindrome." << endl;
return 0;
输出:
赛车是回文。
注意 1:此解决方案的效率可能低于其他答案,因为它必须先遍历列表的一半才能找到其中心。但是,恕我直言,它看起来不那么复杂。
注意 2:由于list::rbegin()
,函数equal()
将列表的前半部分与后半部分以相反的顺序进行比较。增加此迭代器会将其移向列表的开头。
注意3:如果你想将代码应用于不同类型的容器,你可以将它放入一个函数模板中,如大多数其他答案所示。
Code on Ideone
【讨论】:
以上是关于如何在不使用额外空间的情况下检查双向链表是不是为回文?的主要内容,如果未能解决你的问题,请参考以下文章
在不使用额外数据结构和储存空间的情况下,翻转一个给定的字符串
如何在不手动检查的情况下确定网站是不是使用 webassembly?
如何在不使用 SafeArea 的情况下检查设备是不是有缺口?