單調棧、動態規劃、雙指針
LeetCode42.接雨水
【題目】
給定 n 個非負整數表示每個寬度爲 1 的柱子的高度圖,計算按此排列的柱子,下雨之後能接多少雨水。
【輸入】: [0,1,0,2,1,0,1,3,2,1,2,1]
【輸出】: 6
【解法一】:暴力解法
思路:對於數組中的每個元素,我們找出下雨後水能達到的最高位置,等於兩邊最大高度的較小值減去當前高度的值。每次計算的當前元素能存儲的雨水,最後累計即可
- 首尾元素無須計算
- 當左右無大於當前元素的值值,當前位置無法存儲雨水
public int method1(int[] arr) {
if(arr.length==0 || arr==null) return 0;
int i,j,res,left,right,leftMax,rightMax;
res = 0;
for (i=1; i<arr.length-1; i++) {
left = i;
right = i;
leftMax = rightMax = 0;
// 向左尋找大於當前元素的最大值
while (left>=0) {
if (arr[left] > arr[i] && leftMax < arr[left]) leftMax = arr[left];
left--;
}
// 向右尋找大於當前元素的最大值
while (right<arr.length) {
if (arr[right] > arr[i] && rightMax < arr[right]) rightMax = arr[right];
right++;
}
if (leftMax != 0 && rightMax != 0) {
res += Math.min(leftMax,rightMax) - arr[i];
}
}
return res;
}
【解法二】動態規劃
在暴力方法中,我們僅僅爲了找到最大值每次都要向左和向右掃描一次。但是我們可以用動態規劃的方式提前存儲這個值。
/**
* 在暴力方法中,我們僅僅爲了找到最大值每次都要向左和向右掃描一次。
* 但是我們可以提前存儲這個值。因此,可以通過動態編程解決。
*
* dp1[x] 從0達到x的時候,已有的最大值
* dp2[x] 從n-1到達x的時候,已有的最大值
* */
public int method2(int[] a) {
if(a.length==0 || a==null) return 0;
int i,res;
int[] dp1,dp2;
dp1 = new int[a.length];
dp2 = new int[a.length];
dp1[0] = a[0];
dp2[a.length-1] = a[a.length-1];
for (i=1; i<a.length; i++) {
dp1[i] = Math.max(dp1[i-1],a[i]);
}
for (i=a.length-2; i>=0; i--) {
dp2[i] = Math.max(dp2[i+1],a[i]);
}
res = 0;
for (i=1; i<a.length-1; i++) {
if (a[i]<dp1[i] && a[i]<dp2[i]) {
res += Math.min(dp1[i],dp2[i]) - a[i];
}
}
return res;
}
【解法三】單調棧
/**
* 利用單調棧
* 棧頂元素大於當前元素,當前元素入棧
* 等於和小於要進行相應的計算
*
* 單調棧存下標要比存內容的意義更大,因爲可以通過下標定位到元素
* */
public int method3(int[] a) {
if (a==null || a.length==0) return 0;
Stack<Integer> stack = new Stack<>();
int i,res;
res = 0;
for (i=0; i<a.length; i++) {
while (!stack.empty() && a[stack.peek()] < a[i]) {
int curIndex = stack.pop();
// 需要把相同的元素全部彈出棧
while (!stack.empty() && a[curIndex] == a[stack.peek()]) {
curIndex = stack.pop();
}
if (!stack.empty()) {
int leftIndex = stack.peek();
int height = Math.min(a[i],a[leftIndex])-a[curIndex];
int width = i - leftIndex - 1;
res += height * width;
}
}
stack.add(i);
}
return res;
}
注意:單調棧存下標要比存內容的意義更大,因爲可以通過下標定位到元素
LeetCode11. 盛最多水的容器
【題目】
給你 n 個非負整數 a1,a2,…,an,每個數代表座標中的一個點 (i, ai) 。在座標內畫 n 條垂直線,垂直線 i 的兩個端點分別爲 (i, ai) 和 (i, 0)。找出其中的兩條線,使得它們與 x 軸共同構成的容器可以容納最多的水。
說明:你不能傾斜容器,且 n 的值至少爲 2。
【示例】
輸入:[1,8,6,2,5,4,8,3,7]
輸出:49
【思考】
方法一:動態規劃
dp1[x] 從0到x-1,第一個大於等於a[x]的下標,沒有則爲-1
dp2[x] 從n-1到x+1,第一個大於等於a[x]的下標,沒有則爲-1
從兩個方向來討論,可以避免單方向的很多情況的討論
然後遍歷,以當前高度爲高,以左右兩邊最大值到當前的距離爲寬,計算最大值
時間複雜度:O(n2)
方法二:雙指針
左右兩個指針放在隊首和隊尾,計算出當前雙指針的面積
然後移動較低指針,重新計算面經,並記錄最大面積值(爲什麼移動較低,思考一下移動較低和較高到下一個柱子,與原有面積的關係就可以得出了)
【代碼】
package LeetCode;
import java.util.Arrays;
public class LeetCode11 {
/**
* dp1[x] 從0到x-1,第一個大於等於a[x]的下標,沒有則爲-1
* dp2[x] 從n-1到x+1,第一個大於等於a[x]的下標,沒有則爲-1
* 從兩個方向來討論,可以避免單方向的很多情況的討論
*
* dp[x] = dp[x-1] {a[x]<=a[x-1]}
* */
public int maxArea1(int[] height) {
if (height==null || height.length==0)
return 0;
int n,max,i,j;
n = height.length;
int[] dp1 = new int[n];
int[] dp2 = new int[n];
Arrays.fill(dp1,-1);
Arrays.fill(dp2,-1);
dp1[0] = 0;
for (i=1; i<n; i++) {
if (height[i]<=height[i-1] && dp2[i-1]!=-1) {
dp1[i] = dp1[i-1];
} else {
for (j=0; j<i; j++) {
if (height[i]<=height[j]) {
dp1[i] = j;
break;
}
}
}
}
dp2[n-1] = n-1;
for (i=n-2; i>=0; i--) {
if (height[i]<=height[i+1] && dp2[i+1]!=-1) {
dp2[i] = dp2[i+1];
} else {
for (j=n-1; j>i; j--) {
if (height[i]<=height[j]) {
dp2[i] = j;
break;
}
}
}
}
int[] res = new int[n];
max = 0;
for (i=0; i<n; i++) {
if (dp1[i]!=-1 && dp2[i]!=-1) {
res[i] = Math.max((i-dp1[i])*height[i],(dp2[i]-i)*height[i]);
} else if (dp1[i]!=-1 && dp2[i]==-1) {
res[i] = (i-dp1[i])*height[i];
} else if (dp1[i]==-1 && dp2[i]!=-1) {
res[i] = (dp2[i]-i)*height[i];
}
if (max < res[i]) max = res[i];
}
return max;
}
/**
* 雙指針法
* 左右兩個指針放在隊首和隊尾,計算出當前雙指針的面積
* 然後移動較低指針,重新計算面經,並記錄最大面積值
* */
public int maxArea(int[] height) {
if (height==null || height.length==0)
return 0;
int l,r,len,max,min;
len = height.length;
l = 0;
r = len-1;
max = 0;
while (l<r) {
min = Math.min(height[l],height[r]);
max = Math.max(min*(r-l),max);
if (height[l]==min) {
l++;
} else {
r--;
}
}
return max;
}
public static void main(String[] args) {
int[] a = {1,8,1};
System.out.println(new LeetCode11().maxArea(a));
}
}
【Java 面試那點事】
這裏致力於分享 Java 面試路上的各種知識,無論是技術還是經驗,你需要的這裏都有!
這裏可以讓你【快速瞭解 Java 相關知識】,並且【短時間在面試方面有跨越式提升】
面試路上,你不孤單!