簡體   English   中英

如何在不使用額外空間的情況下檢查雙向鏈表是否是回文?

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

最近,我去了一個面試,他們問我“檢查下面的雙鏈表是否是一個回文,而不使用任何額外的存儲空間,例如STL的鏈表,堆棧,隊列,樹,字符串,字符數組等等。”雖然我無法提供完美的解決方案。

下面是雙向鏈表的圖像:

雙鏈表的示例

這不是一個家庭作業問題,而只是一個尋找任何待分享解決方案的問題。

這里的問題是你有自然的迭代器,它的元素可以是多個字符,但你想只對字符序列進行操作。 所以我將使用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_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));
}

聲明兩個迭代器,start和end。 然后循環遍歷列表並同時遞減/遞增它們,比較每一步。 注意:此算法假定您已正確覆蓋運算符,但它也適用於任何類型的列表,而不僅僅是數字列表。

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

這里的關鍵是你使用迭代器而不是直接使用列表。

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迭代器,它只進行了大量的遞增,這很慢。 相反,循環中間的檢查處理大於大小寫,因此可以僅使用相等比較來編寫整個函數。

這是我用於回文測試的代碼。 它需要兩個迭代器並正確處理空范圍和奇數/偶數長度范圍。

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
}

我想添加一個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;
}

輸出:

賽車是一個回文。

注1:此解決方案的效率可能低於其他答案的效率,因為它必須逐步通過列表的一半才能找到其中心。 但是,恕我直言,它看起來不那么復雜。

注2:函數equal()由於list::rbegin()而以相反的順序將列表的前半部分與后半部分進行比較。 增加此迭代器會將其移至列表的開頭。

注意3:如果要將代碼應用於不同類型的容器,可以將其放入功能模板中,如大多數其他答案所示。

Ideone上的代碼

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM