【單調棧】視野總和、柱狀圖中最大的矩形、區間最大值


單調棧

  • 單調遞增棧:數據出棧的序列為單調遞增序列(比站內元素小就入棧,否則將棧中比當前元素小的元素彈出后再入棧)
  • 單調遞減棧:數據出棧的序列為單調遞減序列(比站內元素大就入棧,否則將棧中比當前元素大的元素彈出后再入棧)

視野總和

描敘:有n個人站隊,所有的人全部向右看,個子高的可以看到個子低的發型,給出每個人的身高,問所有人能看到其他人發現總和是多少。
輸入:4 3 7 1
輸出:2
思路:設置一個單調遞增棧,當這個人可以被站內人看到時(即比棧內數小時)入棧,同時將棧內每個人能看到的人數加一,如果遇到了一個比棧頂元素高的人,那么棧內一些人是看不到他的,所以將這些看不到他的人出棧,出棧完成后再將棧內剩下元素能看到的人數各自加一。大致是這種思路,但是由於這題求得是總和,所以不必求出每個人能看到多少人,避免每次有元素入棧都要遍歷原有棧內的元素。可以改為在每個元素出棧時計算他能看到的人數。例如第i個人要入棧(即第i個人比s.top()這個人高時),此時棧內s.top()這個人被第i個人擋住了,第i個人后面的都看不到了,所以其出棧時看到的人數為i~s.top()之間的人。

#include<bits/stdc++.h>
using namespace std;

int FieldSum(vector<int>& v)
{
	int sum=0;
	v.push_back(INT_MAX);
	stack<int> s;
	for(int i=0;i<v.size();i++)
	{
		while(!s.empty()&&v[i]>v[s.top()])
		{
			int x=s.top();
			s.pop();
			sum+=(i-x-1);   //i為當前元素,x為出棧元素,相減部分為x可以看到的數量;
		}
		s.push(i);
	}
	return sum;
}
int main()
{
	int a[6]={8,3,2,7,1,4};
    vector<int> v;
    for(int i=0;i<6;i++)
    {
    	v.push_back(a[i]);
	} 
	cout<<FieldSum(v)<<endl;
	return 0;
}

柱狀圖中最大

描述
Given n non-negative integers representing the histogram’s bar height where the width of each bar is 1,
find the area of largest rectangle in the histogram.

1584440534718

圖 4-1 Above is a histogram where width of each bar is 1, given height = [2,1,5,6,2,3].

1584440545597

圖 4-2 The largest rectangle is shown in the shaded area, which has area = 10 unit.
For example, Given height = [2,1,5,6,2,3], return 10.

單調棧

理解:當遍歷到第i個數時,會算出所有向左看時在數i前即包含第(i-1)個數的最大值,假設這個最大值矩形左邊邊界為left,右邊為right(即此時right+1大於left-1),這種情況下,以左邊界[left]>[right+1]但是[left-1]<[right+1],所以 這里的大小關系也是出棧的依據,以此來計算寬度,h[right]即為這一段上的最小值。

#include<bits/stdc++.h>
using namespace std;
//#define MAX 10005

int  largestRectangleArea(vector<int>& heights)
{
		stack<int> s;
	int max=0;
	heights.push_back(0);      //用於處理最后一個數據 
	int len=heights.size();
	for(int i=0;i<len;i++)
	{
		while(!s.empty()&&heights[s.top()]>heights[i])
		{
			int x=s.top();
			s.pop();
            //temp為寬度,如果s為空,則說明,x之前的元素都比x高,所以均可計入,寬度即為(i-1-0+1)
            //s為空時,兩邊的邊界依次為下標為0,下標為i-1;
			int temp=heights[x]*(s.empty()?i:(i-s.top()-1));
			max=max>temp?max:temp;
		}
		s.push(i);
	}
	
	return max;
}


int main()
{
    int a[6]={2,1,5,6,2,3};
    vector<int> v;
    for(int i=0;i<6;i++)
    {
    	v.push_back(a[i]);
	} 
	cout<<largestRectangleArea(v)<<endl;
	return 0;
    	
}

分治法

此題還可以使用分治法,記左右邊界為left,right,可以先找出一個序列中的最小值min,若最大矩形包含這個min,則最大矩形一定為(right-left+1)*min;所以可以分為(left,min-1),(min+1,right)兩個子問題尋找最大矩形,然后三種情況取最大值。(因為最大矩形連續,而第二三種情況一定不包含min,所以可以分開看成兩個無聯系的子問題)

int maxArea(int left,int right,vector<int>& v)
{
	if(left==right)
		return v[left];
	bool sortedlr=true;               //是否從左到右遞增 
	bool sortedrl=true;               //是否從右到左遞增 
	int min=left;
	
	for(int i=left;i<=right;i++)
	{
		if(v[i]>v[i-1]) 
		{
			sortedrl=false;
		}
		if(v[i]<v[i-1])
		{
			sortedlr=false;
		}
		if(v[i]<v[min])
		{
			min=i;
		}
	}
	if(sortedlr)
	{
		int maxa=v[left]*(right-left+1);
		
		for(int i=left+1;i<=right;i++)
		{
			int temp=v[i]*(right-i+1);
			maxa=maxa>temp?maxa:temp;
		}
		return maxa;
		
	}
	if(sortedrl)
	{
		int maxa=v[right]*(right-left+1);
		for(int i=right-1;i>=left;i--)
		{
			int temp=v[i]*(i-left+1);
			maxa=maxa>temp?maxa:temp;
		}
		return maxa;
	}
	else
	{
		//繼續分治
		int l=0;
		if(left<min)
		{
			l=maxArea(left,min-1,v);
		}
		int r=0;
		if(right>min)
		{
			r=maxArea(min+1,right,v);
		} 
		int x=l>r?l:r;
		int temp=v[min]*(right-left+1);
		return x>temp?x:temp;
	}
} 


int largestRectangleArea(vector<int>& heights)
{
	int left=0;
	int right=heights.size()-1;
	if(right==-1)
	return 0;
	else if(right==0)
	{
		return heights[0];
	}
	else 
	{
		return maxArea(left,right,heights);
	}
} 

求最大區間

描述:給出一組數字,求一區間,使得區間元素和乘以區間最小值最大,結果要求給出這個最大值和區間的左右端點
輸入:3 1 6 4 5 2
輸出:60
       3 5
解釋:將3到5(6+4+5)這段區間相加,將和與區間內最小元素相乘獲得最大數字60
思路:使用暴力解法求出所有區間,再求出區間的最小值相乘跟新數據,並不是一種很好的算法,所以經過上面倆題的磨煉,此時我們應該使用一個單調遞減棧同矩形相似,如果以一個數為該區間最小值,則算出該最小值可以構成的最大區間。

#include<bits/stdc++.h>
using namespace std;

int findMax(vector<int>& v,int &p,int &q)
{
	v.push_back(0);
	stack<int> s;
	int max=0;
	int sum;
	
	for(int i=0;i<v.size();i++)
	{
		while(!s.empty()&&v[s.top()]>v[i])
		{
			int x=s.top();
			s.pop();
			sum=0;
			int j;
			if(s.empty())
			{
				//p=0;
				j=0;
			}
			else
			{
				//p=s.top()+1;
				j=s.top()+1;
			}
				
			for(j;j<i;j++)
			{
				sum+=v[j];
			}
			
			
			int temp=sum*v[x];
			max=max>temp?max:temp;
			if(max==temp)
			{
				p=s.empty()?0:s.top()+1;
				q=i-1;
			}
		}
		s.push(i);
	}
	return max;
}

int main()
{
	int a[]={3,1,6,4,5,2};
	vector<int> v;
	int p,q;
	for(int i=0;i<6;i++)
	{
		v.push_back(a[i]);
	}
	int x=findMax(v,p,q);
	cout<<x<<endl;
	cout<<(p+1)<<" "<<(q+1)<<endl;       //p,q,為下標,輸出為序號 
	return 0;
}

尋找無序數組每個元素的后面第一個比它大的元素值

如題

#include<bits/stdc++.h>
using namespace std;
vector<int> nextmax(vector<int> &v)
{
	stack<int> s;
	vector<int> res(v.size());
	
	int size=v.size();
	int i=0;
	while(i<size)
	{
		if(s.empty()||v[s.top()]>=v[i])
		{
			s.push(i++); 
		}
		else
		{
			int tmp=s.top();
			res[tmp]=v[i];
			s.pop();
		}
	}
	while(!s.empty()) 
	{
		res[s.top()]=INT_MAX;         //將后面沒有比他更大的元素置為INT_MAXINT_MAX
		s.pop();
	}
	return res;
}
int main()
{
	int a[10]={1,2,4,2,5,76,89,3,45,34};
	vector<int> v;
	for(int i=0;i<10;i++)
	{
		v.push_back(a[i]);
	}
	vector<int> res=nextmax(v);
	for(int i=0;i<res.size();i++)
	{
		cout<<res[i]<<endl;
	}
	return 0;
	
} 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM