简体   繁体   English

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

[英]How can I check if a doubly-linked list is a palindrome without using extra space?

Recently, I went for an interview and they asked me "to check if below doubly-linked list is a palindrome without using any extra storage, such as STL's linked list, stack, queue, tree, string, character arrays, etc.." I was unable to give a perfect solution though. 最近,我去了一个面试,他们问我“检查下面的双链表是否是一个回文,而不使用任何额外的存储空间,例如STL的链表,堆栈,队列,树,字符串,字符数组等等。”虽然我无法提供完美的解决方案。

Below is an image of the doubly-linked list: 下面是双向链表的图像:

双链表的示例

This is not a homework question, but merely a question to find any solutions to be shared. 这不是一个家庭作业问题,而只是一个寻找任何待分享解决方案的问题。

The problem here is that you have natural iterators whose elements can be more than one character, but you want to do an operation over just the sequence of characters. 这里的问题是你有自然的迭代器,它的元素可以是多个字符,但你想只对字符序列进行操作。 So I would define another iterator type that behaves the way we need, using boost::iterator_facade . 所以我将使用boost::iterator_facade定义另一个行为方式,以我们需要的方式运行。 (Often for this sort of thing boost::iterator_adaptor is even more convenient, but in this case it wouldn't help.) (通常对于这种事情, boost::iterator_adaptor更方便,但在这种情况下它无济于事。)

The diagram suggests a raw C-like struct and pointer setup, so I'll assume the custom doubly linked list is defined like this: 该图表显示了一个原始的类C结构和指针设置,因此我假设自定义双向链表定义如下:

struct list_node {
    list_node* prev;
    list_node* next;
    const char* data;
};

class LinkedList {
public:
    list_node* head() const;
    list_node* tail() const;
    // ...
};

The custom iterator class needs to contain a list_node* pointer and a pointer to an element of the char array. 自定义迭代器类需要包含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;
};

For a past-the-end iterator only, we'll allow m_ch to point at the last node's terminating '\\0' . 仅对于过去的迭代器,我们将允许m_ch指向最后一个节点的终止'\\0' In the special case of a list with no elements, we'll set both members null for the single iterator which is both beginning and end and cannot be dereferenced. 在没有元素的列表的特殊情况下,我们将为单个迭代器设置两个成员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() and equal() are trivial: 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; }

And finally, to step forward or backward, the basic idea is to change only m_ch if that makes sense, or change m_node otherwise. 最后,向前或向后,基本思想是只在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;
    }
}

Now you can use that custom iterator in an ordinary palindrome algorithm (and many other algorithms). 现在,您可以在普通的回文算法(以及许多其他算法)中使用该自定义迭代器。

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

Declare two iterators, start and end. 声明两个迭代器,start和end。 Then loop through the list and decrement/increment them simultaneously, comparing at each step. 然后循环遍历列表并同时递减/递增它们,比较每一步。 NOTE: This algorithm assumes that you have properly overridden the operators, but it also works for any sort of list, not just numerical lists. 注意:此算法假定您已正确覆盖运算符,但它也适用于任何类型的列表,而不仅仅是数字列表。

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

The key here is that you are using iterators instead of actually working with the list directly. 这里的关键是你使用迭代器而不是直接使用列表。

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

We can't use > or >= because list iterators are not random access (in most imlementations) and so can only be compared equal/not-equal. 我们不能使用>>=因为列表迭代器不是随机访问(在大多数imlement中),所以只能比较相等/不相等。 std::distance is an option, but for non-randomaccess iterators it just does a whole lot of incrementing, which is slow. std::distance是一个选项,但对于非randomaccess迭代器,它只进行了大量的递增,这很慢。 Instead, the check in the middle of the loop handles the greater-than case, so that the entire function can be written using only equality comparisons. 相反,循环中间的检查处理大于大小写,因此可以仅使用相等比较来编写整个函数。

This is the code I use for palindrome testing. 这是我用于回文测试的代码。 It takes two iterators and correctly handles empty ranges and odd/even length ranges. 它需要两个迭代器并正确处理空范围和奇数/偶数长度范围。

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
}

I'd like to add a C++11 solution (due to a range-based for loop and an auto specifier ) which makes use of std::list , std::advance() , and std::equal() , resulting in quite short code: 我想添加一个C ++ 11解决方案(由于基于范围的for循环和一个auto说明符 ),它使用了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;
}

Output: 输出:

racecar is a palindrome. 赛车是一个回文。

Note 1: This solution might be less efficient than that of the other answers, because it has to step through half of the list to find its center first. 注1:此解决方案的效率可能低于其他答案的效率,因为它必须逐步通过列表的一半才能找到其中心。 However, IMHO, it looks less complicated. 但是,恕我直言,它看起来不那么复杂。

Note 2: The function equal() compares the first half of the list to its second half in reverse order because of list::rbegin() . 注2:函数equal()由于list::rbegin()而以相反的顺序将列表的前半部分与后半部分进行比较。 Increasing this iterator moves it towards the beginning of the list. 增加此迭代器会将其移至列表的开头。

Note 3: If you want to apply the code for different kinds of containers, you can put it into a function template as shown in most of the other answers. 注意3:如果要将代码应用于不同类型的容器,可以将其放入功能模板中,如大多数其他答案所示。

Code on Ideone Ideone上的代码

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM