//“接雨水”算法:
//vNums中數字代表一系列相鄰並排的水槽
//如索引號爲"0"和"1"的數字0和1代表這一水槽只有右邊,即該水槽無法接到雨水
//而索引號從"1"到"3"的水槽可以接到1個單位的水量...
//求解思路:"滑動窗口","一個"水槽"一個"水槽的處理,此處的"一個"代表的是一個大的可以儲存水的“凹”處水槽
//水槽柱高
vector<int> vNums{ 0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1 };
//nRainAll儲存水槽可以儲存的所有水量
//nIndex計算第一個高度非"0"的水槽
int nRainAll = 0, nIndex = 0;
//找到第一個可以裝水的水槽
while (0 == vNums[nIndex])
{
nIndex++;
}
//nOneStart記錄當前水槽槽邊開始索引號
//nOne記錄滑動窗口當前槽邊索引位置
//nOneMiddle記錄水槽底處,即水槽的"中間"位置,該位置的左邊水槽邊的高度下降,
//該位置的右端,水槽邊的高度上升。
//nOneEnd記錄的是最右端水槽邊的索引號。
int nOneStart = 0, nOne = 0, nOneMiddle = 0, nOneEnd = 0;
//算法開始
//從第一個非0,即有邊的水槽開始"滑動窗口",尋找每一個可以盛水的水槽
for (int i = nIndex; i < vNums.size(); i++)
{
//nSegSum記錄當前水槽可以裝的水量
int nSegSum = 0;
//判斷該索引位置所代表的水槽邊的右側是否還有水槽邊
if (i + 1 < vNums.size())
{
//如果還有水槽邊的話,就要將水槽的當前邊nOne設置爲當前索引值i
//水槽的開始邊索引值nOneStart設置爲當前邊索引值
//水槽的"中部"nOneMiddle也設置爲當前索引號
//並設置臨時變量nNext,探索當前水槽邊的右邊是否滿足其水槽邊小於當前水槽邊的條件
//如果滿足條件,則一直向右滑動,直到“當前”槽邊不再小於其前一個槽邊
nOneStart = i;
nOne = i;
nOneMiddle = i;
int nNext = i + 1;
//這裏是滑動槽邊,直到槽邊大於等於“當前”槽邊的槽邊或槽邊被探索完
//這樣探索的思路就是:將水槽的左邊,即槽邊從高到低的所有槽邊找到,直到槽邊高度開始變大,
//即到達"槽底",到達水槽的最低點,這裏定義的水槽"中點"(用nOneMinddle記錄其索引值)。
while ((nNext < vNums.size()) && (vNums[nNext] < vNums[nOne]))
{
nOne = nNext;
nNext++;
}
//這裏代表的就是找到槽底後跳出來的是因爲當前索引"nNext"大於大於其前一個槽邊高度,
//即槽邊高度開始"上升",所以求槽底的索引號時需要減去1,做減減運算
nNext--;
//這裏判斷我們找到的槽底的索引值是否與水槽左端的索引值相同,如果相同,則意味着
//該水槽槽底與槽邊高度相同,顯然無法注水,因此用continue跳出該水槽的運算,
//直接進行下一個水槽的盛水運算;反之,如果不同,說明當前水槽可以盛水(至少目前來看可以,因爲
//水槽還得判斷水槽右邊是否滿足槽邊高於槽底的要求),此時我們需要更新槽底索引號nOneMiddle爲nNext
if (nNext != nOneMiddle)
{
nOneMiddle = nNext;
}
else
{
continue;
}
//這裏表示開始判斷水槽右端的高度是否滿足水槽邊高度高於槽底的盛水要求
//同樣,水槽右端需要向右滑動1個單位的窗口
if (nOneMiddle + 1 < vNums.size())
{
//這裏首先將右端水槽索引號設置爲槽底索引號
nOneEnd = nOneMiddle;
//nNext臨時變量指向槽底的下一個槽邊
nNext = nOneMiddle + 1;
//同尋找槽底的邏輯:這裏以槽底爲出發點,在滑動窗口過程中尋找下一個槽邊小於等於當前槽邊的索引號
while ((nNext<vNums.size()) && (vNums[nNext] > vNums[nOneMiddle]))
{
nOneMiddle = nNext;
nNext++;
}
//到這裏就表示尋找到槽邊高度開始下降了或者是水槽邊被探索完,仍然沒有找到開始下降的水槽邊
nNext--;
//判斷找到的右端是否與槽底索引號相同,相同的話表示該水槽只有左邊部分(類似一個木桶只有一半,肯定無法注水)
//反之,若右端索引號與槽底索引號不同,則表示該水槽完整,有左右邊,有底(是一個完整的水槽)。
if (nOneEnd != nNext)
{
//這裏表示更新右端水槽索引號
nOneEnd = nNext;
//nRain計算裝滿水的水槽的總體積。
//由木桶原理我們知道,水槽低的那端(左端或者右端),以及水槽的寬度決定了水槽的容積
int nRain = 0;
//這裏表示如果水槽左端低的話,我們的水槽只能裝到左端水槽高度的水
//同理,若是右端水槽低的話,裝水的最大高度就是右端(低的那端)的水槽的高度
if (vNums[nOneStart] < vNums[nOneEnd])
{
nRain = (nOneEnd - nOneStart + 1)*vNums[nOneStart];
//這裏是計算出空水槽所佔的體積
//因爲我們在計算總體積的時候是按照左右端低的那端的水槽高度來計算的
//因此水槽左右端高的那一端的高度也必須按照低的那端計入體積計算,
//這樣才能保證:裝滿水的水槽的總體積 - 空水槽所佔的體積 = 水槽裝的水的體積
for (int j = nOneStart; j <= nOneEnd; j++)
{
if (vNums[j] <= vNums[nOneStart])
{
nSegSum += vNums[j];
}
else
{
nSegSum += vNums[nOneStart];
}
}
}
else
{
nRain = (nOneEnd - nOneStart + 1)*vNums[nOneEnd];
for (int j = nOneStart; j <= nOneEnd; j++)
{
if (vNums[j] <= vNums[nOneEnd])
{
nSegSum += vNums[j];
}
else
{
nSegSum += vNums[nOneEnd];
}
}
}
//這裏表示將當前水槽裝的水的體積存儲到總水量中
nRainAll += (nRain - nSegSum);
//更新窗口索引值i
i = nOneEnd - 1;
}
else
{
continue;
}
}
}
}