[英]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::list
, std::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:如果要將代碼應用於不同類型的容器,可以將其放入功能模板中,如大多數其他答案所示。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.