3

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:

Example of a double linked list

This is not a homework question, but merely a question to find any solutions to be shared.

honk
  • 9,137
  • 11
  • 75
  • 83
Santosh Sahu
  • 2,134
  • 6
  • 27
  • 51
  • Write a loop with two iterators, one moves forwards from the start of the list, the other moves backwards from the end of the list. Loop until the whole list has been covered. If the iterators are pointing to the same values at each step, then the list is a palindome. Seems trivial, frankly. – john Sep 24 '13 at 14:53
  • @john seeing the figure if you iterate and check at each point then the above double linked list is not pallindrome but when we see it is a pallindrome.. dat's where i failed... – Santosh Sahu Sep 24 '13 at 14:55
  • I doubt the requirement for *no* extra space can be met. About the best you can hope for is to limit it to a small, constant amount of extra space (e.g., two pointers/iterators). – Jerry Coffin Sep 24 '13 at 15:05
  • Yeah, that was probably an imprecise way of saying O(1) memory. – aschepler Sep 24 '13 at 15:22
  • What are the 100 ... 400 in the diagram? If they're memory addresses, that linked list is not properly linked. – aschepler Sep 24 '13 at 15:27
  • Also, MALYALAM is not quite a palindrome. – aschepler Sep 24 '13 at 16:46

5 Answers5

4

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));
}
aschepler
  • 70,891
  • 9
  • 107
  • 161
3

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.

David says Reinstate Monica
  • 19,209
  • 22
  • 79
  • 122
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;
}

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.

SoapBox
  • 20,457
  • 3
  • 51
  • 87
  • You have a bug if the container only has one element. You walk the iterators past each other before testing `b == e` in the loop. – Blastfurnace Sep 24 '13 at 15:27
2

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
}
Blastfurnace
  • 18,411
  • 56
  • 55
  • 70
0

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

honk
  • 9,137
  • 11
  • 75
  • 83