面試題那些事(3)—棧

棧的定義--Stackide

棧只容許在末端進行插入和刪除的線性表。棧具備後進先出的特性(LIFO,Last In First Out)。測試

spacer.gifwKioL1cNDoHDV1cAAAANevlioW4359.png

問題:url

實現一個棧,要求實現Push(出棧)、Pop(入棧)、Min(返回最小值的操做)的時間複雜度爲O(1)spa


問題分析:blog

根據棧的特性要實現入棧和出棧是比較容易的,只須要藉助系統給的stack便可。可是最難的是要知足Min(返回最小值的操做)ip

的時間複雜度爲O(1)get

咱們先來討論最通常的思路:it


思路1io

使用一個變量來保存最小的值_min,每次入棧時讓入棧的元素d和_min進行比較,若是d<_min則更新_min,不然不更新。ast

可是這存在一個很大的問題,若是最小值出棧了怎麼辦?


好比說有一個棧爲:8,10,6,2,9.

則_min的值爲2,9出棧的話那麼_min的值不受影響。可是若是2出棧的話,則_min的值就沒法獲得。


思路2

既然只用一個變量無法解決這個問題,那咱們就增長變量。_min 保存當前的最小值,_second 保存次小值。


可是在實現Pop的時候又出現了新的問題,若是_min和_second都出棧以後,_min和_second該如何更新?

假設有棧爲8,10,6,2,9

_min爲2,_second爲6.當2出棧以後_min更新爲_second,可是_second該如何更新?


思路3

既然須要不斷的更新最小值,那就把全部的最小值給保存起來。


解決方案一:

每次push()和pop()時將數據和最小值同時壓入。

spacer.gifwKiom1cNDf7zQsb5AAARJIvSZBM835.png

假設有一個棧 8 7 10 6 2 2 9

則存儲的結構爲:

spacer.gif

wKioL1cNDsbBdrIzAAAbslUQTYM931.png

入棧時的操做:

1)若是棧爲空或者入棧的元素d比當前棧的最小元素_s.top()._min小,則_min=d;

2)若是入棧元素d比當前棧的元素大於或者等於則直接將當前棧的最小值_s.top()._min賦給_min。

出棧操做:直接出棧

返回Min則 :當棧不爲空時,返回當前棧頂元素的_min即_s.top()._min。


實現代碼:

//節點結構
template<class T>
struct Node
{
public:
	T _data;//數據域
	T _min;//當前最小值
};

//棧的實現
template<class T>
class Stack
{
public:
	void Push(const T& d)
	{
		Node<T> Data;
		Data._data = d;//不能將d直接入棧,由於棧爲一個結構體棧

		if (_s.empty() || d < _s.top()._min)//若是棧爲空或者入棧元素小於當前棧的最小值
		{
			Data._min = d;
		}
		else//入棧元素大於等於當前棧的最小值
		{
			Data._min = _s.top()._min;
		}

		_s.push(Data);//入棧
	}

	void Pop()
	{
		_s.pop();
	}
	T& Min()
	{
		if (_s.empty())
		{
			throw exception("棧爲空");
		}
		return _s.top()._min;
	}
private:
	stack<Node<T>> _s;
};


總結:代碼實現起來比較容易簡潔,好理解可讀性比較強,可是對於重複比較多的棧的元素的存取則比較浪費空間,

由於每次入棧和出棧時,都至關於入棧兩次。

例如:八、六、六、六、九、五、五、五、五、十、二、二、十一、二、2


解決方案二:

使用兩個棧來實現。s1實現棧的push和pop,s2棧用於存儲在push和pop過程當中的最小值


好比有一組數要入棧: 8,10,6,2,9

S1: 8,10,6,2,9

S2: 8 6 2

每次入棧時,都會和S2.top 比較,若是小於S2.top 則會同時入S1和S2棧。

即到棧頂9時,最小值是2, 若是S1棧9出,和S2.top比較,若是大於S2.top,則S2不作任何操做,若是等於S2.top, 則S2.pop();

好比S1的2出棧時,2 == S2.top(), 而後S2.pop();這樣就節省了不少空間。


可是上述方法還有問題:

好比數據

S1:8 7 10 6 2 2 2 9 這時

S2:8  7 6 2

當S1中2出棧時,S2中的2也會出棧,這樣從S2中讀取到的最小值就是6,可是明顯錯了。


解決方案:

既然棧中可能存在多個重複的最小值_min,解決方法有兩種:

方法一:S2中每一個元素添加一個計數器。這樣當連續3次2出棧,S2中的2纔出棧。

方法二:S2中入棧的時候將<=S2.top的元素通通入棧,即S2爲8,7,6,2,2,2.


實現代碼(計數器):


template<typename T>
struct DataCount
{
public:
	DataCount(const T & d)
		:_data(d)
		,_count(1)
	{}
public:
	T _data;
	int _count;
};

template<typename T>
class Stack
{
	//拷貝構造、賦值、均使用默認的便可,由於拷貝構造和賦值的時候調用的是stack的,不存在淺拷貝
public:
	void Push(const T &d)
	{
		_s.push(d);//d入數據棧

		DataCount<T> tmp(d);//因爲_min爲DataCount的棧,因此d不能直接入棧

		if (_min.empty()|| d < _min.top()._data)//若是存放最小的棧爲空或者小於
		{
			_min.push(tmp);
		}
		else if (d == _min.top()._data)//入棧的元素等於_min棧的棧頂元素,計數器_count+1;
		{
			_min.top()._count++;
		}
	}

	void Pop()
	{
		if (_s.empty())
		{
			return;
			//throw exception("棧爲空");
		}

		//若是數據棧出棧的元素等於_min棧的棧頂元素
		if (_s.top() == _min.top()._data)
		{
			if (_min.top()._count==1)//數據棧中最小的元素只有一個
			{
				_min.pop();
			}
			else//數據棧中存在多個相等的最小元素
			{
				_min.top()._count--;
			}
		}

		//若是數據棧出棧的元素大於_min棧的棧頂元素
		//因爲_min棧中存放的都是較小的元素,因此不可能比數據棧要出棧的元素更大
		_s.pop();
	}

	T& Min()
	{
		if (_min.empty())
		{
			//return -1;
			throw exception("棧爲空");
		}
		return _min.top()._data;
	}

private:
	stack<T> _s; //存儲基本數據的棧
	stack<DataCount<T>> _min; //存儲最小值的棧
};


實現代碼(重複元素進棧)

template<typename T>
class Stack
{
public:
	void Push(const T &d)
	{
		_s.push(d);//d入數據棧

		if (_min.empty()|| d <= _min.top())//若是存放最小的棧爲空或者入棧的元素小於等於_min棧的棧頂元素
		{
			_min.push(d);
		}
	}

	void Pop()
	{
		if (_s.empty())
		{
			return;
			//throw exception("棧爲空");
		}

		//若是數據棧出棧的元素等於_min棧的棧頂元素
		if (_s.top() == _min.top())
		{
			_min.pop();
		}

		//若是數據棧出棧的元素大於_min棧的棧頂元素
		//因爲_min棧中存放的都是較小的元素,因此不可能比數據棧要出棧的元素更大
		_s.pop();
	}

	T& Min()
	{
		if (_min.empty())
		{
			//return -1;
			throw exception("棧爲空");
		}
		return _min.top();
	}

private:
	stack<T> _s; //存儲基本數據的棧
	stack<T> _min; //存儲最小值的棧
};



比較:

方法一:

優勢:適用於大量的重複的較小值的存儲

缺點:在通常場景下空間浪費比較大,由於S2中每一個元素添加一個計數器。

例如:八、六、六、六、九、五、五、五、五、十、二、二、十一、二、2

使用計數器比較節省空間。


方法二:

優勢:在通常場下比較節省空間。

缺點:對於大量的重複的較小值得存儲比較浪費

例如:八、六、五、十二、九、二、十一、7


測試代碼:  

void test()
{
	Stack<int> s1;
	s1.Push(10);
	s1.Push(11);
	s1.Push(6);
	s1.Push(2);
	s1.Push(2);
	s1.Push(2);
	s1.Push(12);
	Stack<int> s3(s1);
	Stack<int> s2;
	s2 = s1;
	try
	{
		cout << s1.Min() << endl;
		s1.Pop();
		cout << s1.Min() << endl;
		s1.Pop();
		cout << s1.Min() << endl;
		s1.Pop();
		cout << s1.Min() << endl;
		s1.Pop();
		cout << s1.Min() << endl;
		s1.Pop();
		cout << s1.Min() << endl;
		s1.Pop();
		cout << s1.Min() << endl;
		s1.Pop();
		cout << s1.Min() << endl;
		s1.Pop();
		cout << s1.Min() << endl;
		s1.Pop();
		cout << s1.Min() << endl;
	}
	catch (...)
	{
		cout << "棧爲空" << endl;
	}

	try
	{
		cout << s2.Min() << endl;
		s2.Pop();
		cout << s2.Min() << endl;
		s2.Pop();
		cout << s2.Min() << endl;
		s2.Pop();
		cout << s2.Min() << endl;
		s2.Pop();
		cout << s2.Min() << endl;
		s2.Pop();
		cout << s2.Min() << endl;
		s2.Pop();
		cout << s2.Min() << endl;
		s2.Pop();
		cout << s2.Min() << endl;
	}
	catch (...)
	{
		cout << "棧爲空" << endl;
	}
}
int main()
{
	test();
	getchar();
	return 0;
}
相關文章
相關標籤/搜索