Leetcode:單調棧_可見山峰問題
題目描述
一個不含有負數的陣列可以代表一圈環形山,每個位置的值代表山的高度。比如,{3,1,2,4,5},{4,5,3,1,2}或{1,2,4,5,3}都代表同樣結構的環形山。3->1->2->4->5->3 方向叫作 next 方向(逆時針),3->5->4->2->1->3 方向叫作 last 方向(順時針)。
山峰 A 和 山峰 B 能夠相互看見的條件為:
- 如果 A 和 B 是同一座山,認為不能相互看見。
- 如果 A 和 B 是不同的山,並且在環中相鄰,認為可以相互看見。
- 如果 A 和 B 是不同的山,並且在環中不相鄰,假設兩座山高度的最小值為 min。如果 A 通過 next 方向到 B 的途中沒有高度比 min 大的山峰,或者 A 通過 last 方向到 B 的途中沒有高度比 min 大的山峰,認為 A 和 B 可以相互看見。
問題如下:
給定一個含有負數可能有重複值的陣列 arr,請問有多少對山峰能夠相互看見?
輸入
5
3 1 2 4 5
輸出
7
思路
樸素的做法是,研究任意兩個點之間是否可以互相看見,需要順時針、逆時針的研究中間的點是否遮擋。
採取單調棧的思路,維護一個遞減的單調棧,初始化最大的元素入棧,從下一個元素開始,遍歷一圈(假設所有的點不重複):
- 如果新來的元素比當前棧頂更小,那麼直接入棧。
- 如果新來的元素比當前棧頂更大,那麼彈出棧頂,直到棧頂比當前元素大。
彈出過程,每彈出一個k,容易知道k與新來的元素一定可以相互看見(中間元素更小或者沒有元素),同時k與彈出k後的棧頂一定可以相互看見(直接相鄰)。因此count++
遍歷結束後,棧中仍然剩餘一定的元素:
若剩餘2個以上,那麼從第三大開始的每個元素,至少與最大,次大元素相互可見。
若只剩餘2個,那麼這兩個相互可見。
進階:存在重複的點
若存在重複的點,那麼需要注意的問題:棧中每個元素不再是值,而是pair<值,數量>
①第一階段:每個元素入棧
同樣的首先,最大值入棧 stack.push(pair(maxvalue,1));
如果新來的元素與棧頂相等:那麼棧頂元素的個數+1
如果新來的元素比棧頂更小:那麼壓入棧頂stack.push(pair(newvalue,1))
如果新來的元素比棧頂更大:那麼不斷彈出棧頂中比新來元素更小的值
因為彈出的一個數值value的數可能存在重複,假設重複的個數有k個,那麼這k個可以望見的點總數:
K2+K(K-1)/2 即:
- 每個元素可以與新來元素相互看見,可以與彈出value後棧頂元素相互看見
- 重複的元素相互之間兩兩可以相互看見,即組合數
注意,刪除元素後,新來的元素可能與棧頂相等,那麼top.count++
②第二階段:清算棧剩餘元素
棧中可能剩餘一定的元素(至少最大值一定還在其中),那麼可能有3大型別:
第三大及往後元素:每個元素一定與其棧中左右相鄰元素相互看見(最小那個元素順時針可以與最大值相互看見)
第二大元素:
若最大值元素個數超過2:那麼每個元素都可以與2個不同的最大值相互看見(順時針和逆時針)
若最大值元素個數為1:那麼每個元素順時針,逆時針看見的最大值為同一個
最大值元素,只能相互之間相互看見(因為採取的小看大的計數方式:否則計數重複)
另外每種型別內部,可以兩兩相互看見:組合數計算
程式碼實現
int main()
{
int n;
cin>>n;
vector<int> arr(n);
stack<pair<int,int>> stk;
int _max=-1;
int _index=-1;
for(int i=0;i<n;i++)
{
cin>>arr[i];
if(arr[i]>_max)
{
_max=arr[i];
_index=i;
}
}
//開始從index開始向next方向遍歷一圈
long long count=0;
stk.push(pair<int,int>(arr[_index],1));//將(最大值,1)入棧
for(int i=1;i<n;i++) //遍歷一圈
{
int index=(_index+i)%n;//當前的研究節點
while(stk.top().first<arr[index])
{
int num=stk.top().second;//彈出的這個中間值的數量
count+=2*num+num*(num-1)/2;//每個元素都可以和左、右 相互看見+它們內部兩兩相互看見
stk.pop();//彈出它們(因為被兩個更大的包圍=》不可能在與兩堵牆之外的相望)
}
if(stk.top().first==arr[index]) //新來的值和棧頂的值相等
{
stk.top().second++;
}
else //新來的值更大 那麼彈出中間那些節點
{
stk.push(pair<int,int>(arr[index],1));
}
}
//結束之後 可能還有剩餘 (至少最大的還包含在其中)
while(stk.size()>2) //在兩個較大值之間還有較小值
{
int num=stk.top().second;//彈出的這個中間值的數量
count+=2*num+num*(num-1)/2;//每個元素都可以和左、右 相互看見+它們內部兩兩相互看見
stk.pop();//彈出它們
}
if(stk.size()==2) //還剩兩個
{
int num=stk.top().second;
stk.pop();
int num2=stk.top().second;
if(num2>1) //存在兩個以上的最大值
count+=2*num+num*(num-1)/2;
else
{
count+=num+num*(num-1)/2;
}
}
if(stk.size()>0)
{
int num=stk.top().second;
count+=num*(num-1)/2;
}
cout<<count<<endl;
}
相關文章
- bzoj1007: [HNOI2008]水平可見直線(單調棧)
- leetcode.最小棧問題LeetCode
- LeetCode Monotone Stack Summary 單調棧小結LeetCodeMono
- LeetCode 周賽上分之旅 #47 前字尾分解結合單調棧的貢獻問題LeetCode
- bzoj1345: [Baltic2007]序列問題Sequence(單調棧)
- Leetcode321. 拼接最大數——單調棧的使用LeetCode
- 單調棧/單調佇列佇列
- 單調棧 和 單調佇列佇列
- 單調棧和單調佇列佇列
- 單調棧模板
- 【LeetCode刷題】896. 單調數列LeetCode
- 淺談單調棧
- 問卷調查中常見問題及解決方法
- 一個詭異的"可見性"問題
- 演算法之單調棧演算法
- 單調棧學習小思考
- LeetCode問題LeetCode
- 解密詭異併發問題的幕後黑手:可見性問題解密
- GNU GPL 許可證常見問題解答(三)
- LeetCode通關:棧和佇列六連,匹配問題有絕招LeetCode佇列
- 特殊資料結構:單調棧資料結構
- [JSOI2008] 最大數 (單調棧)JS
- HISTOGRA - 最大矩形面積(單調棧)
- Android可見APP的不可見任務棧(TaskRecord)銷燬分析AndroidAPP
- Leetcode 演算法題解系列 - 最小棧LeetCode演算法
- 【筆記】線段維護單調棧筆記
- 單調棧進階-接雨水-最大矩形
- ABC372D ABC379F 題解 單調棧二分
- iOS FTPManager的簡單使用及常見問題iOSFTP
- leetcode題解(陣列問題)LeetCode陣列
- I - Max answer 計蒜客 - 38228 單調棧
- Largest Submatrix of All 1’s(思維+單調棧)
- 常見問題
- LeetCode-最小棧LeetCode
- kuangbin 專題二十三:二分 尺取 單調棧佇列 String佇列
- 【多執行緒】常見問題簡單總結執行緒
- 多重揹包問題的單調佇列優化佇列優化
- 【LeetCode】簡單題目集LeetCode