左神算法:可见的山峰对数量(有重复值的情况)(Java版)
生活随笔
收集整理的這篇文章主要介紹了
左神算法:可见的山峰对数量(有重复值的情况)(Java版)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
本題來自左神《程序員面試代碼指南》“可見的山峰對數量”題目。
題目
牛客在線OJ:可見的山峰對數量(進階)
一個不含有負數的數組可以代表一圈環形山,每個位置的值代表山的高度。
比如,{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 能夠相互看見的條件為:
問題如下:
給定一個含有負數 可能有重復值 的數組 arr,請問有多少對山峰能夠相互看見?
要求
對于可能有重復值的數組 arr[N],要求時間復雜度達到 O(N)
解答
利用 單調棧思想,找每個元素兩邊最近且大于該元素的值。詳細的手工操作過程本文不再詳述,可參見《程序員代碼面試指南》原書。
下面給出思路:
- 首先找到一個最大值的位置(數組下標)。如果存在多個最大值,任意找一個即可。
- 運用單調棧的思想(棧從頂到底要求值從小到大)
- 從最大值位置開始遍歷環。設當前遍歷到位置 i ,用一個 record 記錄 (value=arr[i], times=1),times=1 表示 arr[i] 這個值目前只出現一次,沒有重復值。
- 當 arr[i] 大于棧頂的 value,則不斷彈出棧頂,直到棧頂 value 大于等于 arr[i]。每次彈出時,計算被彈出的 record 產生的可見山峰對的數量。被彈出的 record 和其他高度的山峰產生的可見山峰對 為 times*2 , record 內部即相同高度的山峰對之間的可見山峰對數目為 C(2,times)。
- 如果 arr[i] 等于棧頂的 value,則棧頂的 record 的 times 值 +1
- 如果 arr[i] 小于棧頂的 value,則將 arr[i] 對應的 record 壓入單調棧
- 遍歷結束,開始清算階段
- 第一小階段(準備彈出的 record 不是棧底或棧底的上一個 record):被彈出的 record 和其他高度的山峰產生的可見山峰對 為 times*2 , record 內部即相同高度的山峰對之間的可見山峰對數目為 C(2,times)。
- 第二小階段(準備彈出的 record 是棧底的上一個 record):當前被彈出的 record 內部即相同高度的山峰對之間的可見山峰對數目為 C(2,times)。若棧底的 times = 1,則 被彈出的 record 和其他高度的山峰產生的可見山峰對 為 times(因為左右都是同一個最大值山峰,算同一對);若棧底的 times > 1, 則 被彈出的 record 和其他高度的山峰產生的可見山峰對 為 times*2(左右找到的最近且大于自身的都是最大值,但是是不同的山峰,所以有兩對)。
- 第三小階段(準備彈出的 record 是棧底):棧底是最大值,所以左右不可能找到最近且高度大于自身的山峰,因此只有最大值內部的可見山峰對,數量為 C(2,times)。
- 從最大值位置開始遍歷環。設當前遍歷到位置 i ,用一個 record 記錄 (value=arr[i], times=1),times=1 表示 arr[i] 這個值目前只出現一次,沒有重復值。
以上通過每一座山 小找大,找到的山峰對數目相加,就是最終的可見山峰對數量。
以上之所以要從最大值的位置開始遍歷,是因為在最后的清算階段,需要使最大值在棧底。
過程草稿
無重復值的情況:
有重復值的情況:
代碼
import java.util.*;public class Main {// 棧中存放的數據結構public static class Record {public int value;public int times;public Record(int value) {this.value = value;this.times = 1;}}// for testpublic static void main(String[] args) {int[] arr = {5, 4, 3, 5, 4, 2, 4, 4, 5, 3, 2}; // 答案:22int num = getVisibleNum(arr);System.out.println(num);}public static int getVisibleNum(int[] arr) {if (arr == null || arr.length < 2) {return 0;}int size = arr.length;int maxIndex = 0;// 先在環中找到一個最大值的位置,哪一個都可以for (int i = 0; i < size; i++) {maxIndex = arr[maxIndex] < arr[i] ? i : maxIndex;}Stack<Record> stack = new Stack<>();// 先把(最大值,1)這個記錄放入stack中stack.push(new Record(arr[maxIndex]));// 從最大值位置的下一個位置開始沿next方向遍歷int index = nextIndex(maxIndex, size);// 用“小找大”的方式統計所有可見山峰對int res = 0;// 遍歷階段開始,當index再次回到maxIndex的時候,說明轉了一圈,遍歷階段就結束while (index != maxIndex) {// 當前數字arr[index]要進棧,判斷會不會破壞第一維度的數字從頂到底依次變大// 如果破壞了,就依次彈出棧頂記錄,并計算山峰對數量while (stack.peek().value < arr[index]) {int k = stack.pop().times;// 彈出記錄為(X,K),如果K==1,產生2對// 如果K>1,產生2*K+C(2,K)對res += getInternalSum(k) + 2 * k;}// 當前數字arr[index]進棧,如果和棧頂數字一樣就合并,不一樣就把記錄(arr[index],1)放入棧中if (stack.peek().value == arr[index]) {stack.peek().times++;} else {stack.push(new Record(arr[index]));}index = nextIndex(index, size);}// 清算階段// 清算階段第1小階段while (stack.size() > 2) {int times = stack.pop().times;res += getInternalSum(times) + 2 * times;}//清算階段第2小階段if (stack.size() == 2) {int times = stack.pop().times;res += getInternalSum(times) + (stack.peek().times == 1 ? times : 2 * times);}//清算階段第3小階段res += getInternalSum(stack.pop().times);return res;}//如果k==1,返回0;如果k>1,返回C(2,k)public static int getInternalSum(int k) {return k == 1 ? 0 : (k * (k - 1) / 2);}//環形數組中當前位置為i,數組長度為size,返回i的下一個位置public static int nextIndex(int i, int size) {return i < (size - 1) ? (i + 1) : 0;}}總結
以上是生活随笔為你收集整理的左神算法:可见的山峰对数量(有重复值的情况)(Java版)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 左神算法:最大值减去最小值小于或等于nu
- 下一篇: 左神算法:环形单链表的约瑟夫问题(Jav