简体   繁体   中英

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.

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 . (Often for this sort of thing boost::iterator_adaptor is even more convenient, but in this case it wouldn't help.)

The diagram suggests a raw C-like struct and pointer setup, so I'll assume the custom doubly linked list is defined like this:

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.

#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' . 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.

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:

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.

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. 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. std::distance is an option, but for non-randomaccess iterators it just does a whole lot of incrementing, which is slow. 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:

#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. 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() . 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.

Code on Ideone

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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