Problem Link: C - Mandarin Orange
The problem can be converted to as such: Given an array A of positive integers, find out the the maximum product value Len * Min{L, R}. Len is the length of subarray A[L, R] and Min{L, R} is the minimum value in this subarray.
Solution 1. O(N^3) brute force
Obviously we can just check all O(N^2) subarrays, each check takes O(N) time to find a subarray min value.
Solution 2. O(N^2 * logN), optimizing min value check using segment tree
static class SegmentTree { int leftMost, rightMost; SegmentTree lChild, rChild; int min; SegmentTree(int leftMost, int rightMost, int[] a) { this.leftMost = leftMost; this.rightMost = rightMost; if(leftMost == rightMost) { min = a[leftMost]; } else { int mid = leftMost + (rightMost - leftMost) / 2; lChild = new SegmentTree(leftMost, mid, a); rChild = new SegmentTree(mid + 1, rightMost, a); recalc(); } } void recalc() { if(leftMost == rightMost) return; min = Math.min(lChild.min, rChild.min); } void pointUpdate(int index, int newVal) { if(leftMost == rightMost) { min = newVal; return; } if(index <= lChild.rightMost) lChild.pointUpdate(index, newVal); else rChild.pointUpdate(index, newVal); recalc(); } int minQuery(int l, int r) { if(l > rightMost || r < leftMost) return Integer.MAX_VALUE; if(l <= leftMost && r >= rightMost) return min; return Math.min(lChild.minQuery(l, r), rChild.minQuery(l, r)); } } static void solve(int testCnt) { for (int testNumber = 0; testNumber < testCnt; testNumber++) { int n = in.nextInt(); int[] a = in.nextIntArrayPrimitive(n); SegmentTree st = new SegmentTree(0, n - 1, a); int ans = 0; for(int l = 0; l < n; l++) { for(int r = l; r < n; r++) { ans = Math.max(ans, st.minQuery(l, r) * (r - l + 1)); } } out.println(ans); } out.close(); }
Solution 3. O(N^2), optimizing min value check by pre-computing the range min query sparse table in O(N * logN) time and space.
static class RangeMinQuery_SparseTable { int n, k; int[] log; int[][] rangeMin; public RangeMinQuery_SparseTable(int[] a) { n = a.length; log = new int[n + 1]; //log[i]: 2^log[i] = i; for i that is not 2^j, log[i] rounds down to the closest such 2^j that 2^j < i log[1] = 0; //2^0 = 1 //precompute log for (int i = 2; i <= n; i++) { log[i] = log[i / 2] + 1; } k = log[n]; //if n is not 2^j, k is rounded down, so when initializing rangeMin we need to use k + 1 rangeMin = new int[n][k + 1]; for(int i = 0; i < n; i++) { rangeMin[i][0] = a[i]; } //rangeMin[i][j] is the min in range[i, i + 2^j - 1] of length 2^j for(int j = 1; j <= k; j++) { for(int i = 0; i + (1 << j) <= n; i++) { rangeMin[i][j] = Math.min(rangeMin[i][j - 1], rangeMin[i + (1 << (j - 1))][j - 1]); } } } public int query(int L, int R) { //2^j is at least half the size of range [L, R], at most the entire size of range[L, R] int j = log[R - L + 1]; return Math.min(rangeMin[L][j], rangeMin[R - (1 << j) + 1][j]); } } static void solve(int testCnt) { for (int testNumber = 0; testNumber < testCnt; testNumber++) { int n = in.nextInt(); int[] a = in.nextIntArrayPrimitive(n); RangeMinQuery_SparseTable rmst = new RangeMinQuery_SparseTable(a); int ans = 0; for(int l = 0; l < n; l++) { for(int r = l; r < n; r++) { ans = Math.max(ans, rmst.query(l, r) * (r - l + 1)); } } out.println(ans); } out.close(); }
Solution 4. O(N^2), fixing L, then update min value while increasing R one by one. (Keep a running min)
static void solve(int testCnt) { for (int testNumber = 0; testNumber < testCnt; testNumber++) { int n = in.nextInt(); int[] a = in.nextIntArrayPrimitive(n); int ans = 0; for(int l = 0; l < n; l++) { int currMin = a[l]; for(int r = l; r < n; r++) { currMin = Math.min(currMin, a[r]); ans = Math.max(ans, currMin * (r - l + 1)); } } out.println(ans); } out.close(); }
Solution 5. O((maxV + N) * logN).
Using the fact that max of A's element is only up to O(10^5), we can iterate over all min value candidates and find the max answer. This is done as following.
1. Create a tree mapping from A[i] to all of its indices in A, call it tm.
2. Create a tree set that stores all the smaller values' indices, call it prevIdx.
3. From 1 to max possible value in A, do the following:
(a). if the current value exists in A, iterate over all of its indices and for each index i find the largest index l such that l < i and the smallest index r such that r > i. l and r are the left and right boundaries of the the current value at index i being the min value.
(b). after (a), add of the indices of the current value to prevIdx for bigger value checks.
The runtime is O((maxV + N) * logN). We need to iterare from 1 to maxV. And there are N total indices to all to prevIdx. For each of these N indices, we do 2 logN operations to get the left and right boundaries. Lastly, the tree map has at most N entries, we do maxV existence check, each costs O(logN) time.
static void solve(int testCnt) { for (int testNumber = 0; testNumber < testCnt; testNumber++) { int n = in.nextInt(); int[] a = in.nextIntArrayPrimitive(n); int maxV = 0; for(int v : a) { maxV = Math.max(maxV, v); } TreeMap<Integer, List<Integer>> tm = new TreeMap<>(); for(int i = 0; i < n; i++) { tm.computeIfAbsent(a[i], k -> new ArrayList<>()).add(i); } TreeSet<Integer> prevIdx = new TreeSet<>(); int ans = 0; for(int x = 1; x <= maxV; x++) { if(tm.containsKey(x)) { List<Integer> idx = tm.get(x); for(int i : idx) { Integer l = prevIdx.lower(i); Integer r = prevIdx.higher(i); if(l == null) { l = -1; } if(r == null) { r = n; } ans = Math.max(ans, (r - l - 1) * x); } prevIdx.addAll(idx); } } out.println(ans); } out.close(); }
Solution 6. O(N), this problem is the same with finding the largest rectangle in histogram. Treat each A[i] as a histogram height at index i.
Keep a non-decreasing stack, each time the current a[i] is smaller than the top of the stack j, we just find the right bound of the rectangle of height h[j]. Its left bound is just beneath itself in the stack. After iterating over all a[i] and if there are still unprocessed heights in the stack, repeat the same popping process using N as their right bound.
static void solve(int testCnt) { for (int testNumber = 0; testNumber < testCnt; testNumber++) { int n = in.nextInt(); int[] a = in.nextIntArrayPrimitive(n); ArrayDeque<Integer> q = new ArrayDeque<>(); q.addFirst(-1); int ans = 0; for(int i = 0; i < n; i++) { while(q.peekFirst() >= 0 && a[q.peekFirst()] > a[i]) { int j = q.removeFirst(); ans = Math.max(ans, (i - q.peekFirst() - 1) * a[j]); } q.addFirst(i); } while(q.peekFirst() >= 0) { int idx = q.removeFirst(); ans = Math.max(ans, (n - q.peekFirst() - 1) * a[idx]); } out.println(ans); } out.close(); }
Related Problems: