如何在不使用额外空间的情况下检查双向链表是否是回文?

时间:2013-09-24 14:49:32

标签: c++ data-structures doubly-linked-list palindrome

最近,我去了一个面试,他们问我“在不使用任何额外存储的情况下检查下面的双链表是否是回文,例如STL的链表,堆栈,队列,树,字符串,字符数组等等..“虽然我无法提供完美的解决方案。

下面是双向链表的图片:

Example of a double linked list

这不是一个家庭作业问题,而只是一个可以找到任何待分享解决方案的问题。

5 个答案:

答案 0 :(得分: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));
}

答案 1 :(得分:3)

声明两个迭代器,start和end。然后循环遍历列表并同时递减/递增它们,比较每一步。注意:此算法假定您已正确覆盖运算符,但它也适用于任何类型的列表,而不仅仅是数字列表。

for(int i=0;i<list.size()/2;i++){
    if(*start!=*end) return false;
    start++;
    end--;
}
return true;

这里的关键是你使用迭代器而不是直接使用列表。

答案 2 :(得分:3)

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;
}

我们不能使用>>=,因为列表迭代器不是随机访问(在大多数imlement中),因此只能比较相等/不相等。 std::distance是一个选项,但对于非randomaccess迭代器,它只进行了大量的递增,这很慢。相反,循环中间的检查处理大于大小写的情况,因此只能使用相等比较来编写整个函数。

答案 3 :(得分:2)

这是我用于回文测试的代码。它需要两个迭代器并正确处理空范围和奇数/偶数长度范围。

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 :(得分:0)

我想添加一个C++11range-based for loopauto specifier解决方案(由于std::liststd::advance()std::equal(),导致代码很短:

#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:由于equal(),函数list::rbegin()将列表的前一半与后一半进行比较。增加此迭代器会将其移至列表的开头。

注3:如果要将代码应用于不同种类的容器,则可以将其放入功能模板中,如大多数其他答案所示。

Code on Ideone