Contiguous Subarrays

Feb 7, 202010 mins read

Straight to the Point !

Are you landed to this page and rushing for the immediate solution in javascript. here you go !!

  1. Step 1: goes_here
  2. Step 2: goes_here
  3. Step 3: goes_here
1function countSubarrays(arr) {
2 const fillCS = (arr, LCS = Array(arr.length).fill(1)) => {
3 const stack = [];
4 for(let i=0; i<arr.length; i++) {
5 while(stack.length && arr[stack[stack.length - 1]] < arr[i]) {
6 LCS[i] += LCS[stack.pop()];
7 }
8 stack.push(i);
9 }
10 return LCS;
11 }
12 // Assign Left and Right.
13 const left = fillCS(arr);
14 const right = fillCS(arr.reverse()).reverse();
15 // Result as array length.
16 const result = Array(arr.length);
17 for(let i=0; i<arr.length; i++) {
18 result[i] = left[i] + right[i] - 1;
19 }
20 return result;
21}

On High level note, It worth to read in detail for a better understanding.
This is one of the Facebook interview question and Refer to Similar Leet code problem as well - 525. Contiguous Array

Table of Contents

Problem Statement

You are given an array arr of N integers. For each index i, you are required to determine the number of contiguous subarrays that fulfill the following conditions:

  1. The value at index i must be the maximum element in the contiguous subarrays, and
  2. These contiguous subarrays must either start from or end on index i.
1int[] countSubarrays(int[] arr)

Constraints

  1. Array arr is a non-empty list of unique integers that range between 1 to 1,000,000,000
  2. Size N is between 1 and 1,000,000

Expected

  1. An array where each index i contains an integer denoting the maximum number of contiguous subarrays of arr[i]

Test Cases

  1. Input = [3, 4, 1, 6, 2] Output = [3, 4, 1, 6, 2] Explanation:

  2. For index 0 - [3] is the only contiguous subarray that starts (or ends) with 3, and the maximum value in this subarray is 3.

  3. For index 1 - [4], [3, 4], [4, 1]

  4. For index 2 - [1]

  5. For index 3 - [6], [6, 2], [1, 6], [4, 1, 6], [3, 4, 1, 6]

  6. For index 4 - [2] So, the answer for the above input is [1, 3, 1, 5, 1]

  7. Input = [2, 4, 7, 1, 5, 3] Output = [1, 2, 6, 1, 3, 1]

Foot Note

Complexity 1: Any solution must have time and space complexities of at least O(N) to deal with the array of N integers. A relatively simple solution considering all possible contiguous subarrays, or in fact any solution counting the valid subarrays one-by-one, would require a time complexity of at least O(N^2). However, a number of observations can allow this to be optimized down to the ideal time complexity of O(N). For example, letting L[i] be the number of valid subarrays ending at index i (useful to compute on the way to the final answer), consider how we might efficiently compute L[i] for each i from 1 to N by reusing past information rather than computing it from scratch.
Complexity 2: When analyzing such a solution, note that even if we’re computing N values L[1..N], and computing any single one of those values might take on the order of N steps, the overall time complexity will not necessarily be O(N^2) - we should instead consider how many steps may occur in total across all N of them in the worst case.

Solution Intro

Lets see the list of approaches and their complexities.

  1. Approach 1: Letting L[i] be the number of valid subarrays ending at index i and R[i] be the number of valid subarrays beginning at index i, we’ll have output[i] = L[i] + R[i] - 1. Computing R[1..N] is equivalent to computing L[1..N] if a were reversed, allowing us to reduce the problem to computing L[1..N] for an array of N distinct integers.
  2. Approach 2: We can next observe that the index of the latest element to the left of the ith element which is larger than it determines which subarrays ending at index i are valid - specifically, the ones beginning to the right of that larger element. Letting G[i] be equal to the largest index j such that j < i and a[j] > a[i] (or equal to 0 if there’s no such j), then L[i] = i - G[i]. We’ve now reduced the problem to computing these values G[1..N] for an array of N distinct integers.
  3. Approach 3: Computing G[i] for each i from 1 to N is a promising approach, but we’ll still need to consider how to do so as efficiently as possible. We can observe that it’s not possible to compute G[i] purely based on the values of G[i-1], a[i-1], and a[i]; we may need more information about earlier a values as well, but would like to avoid simply scanning over all of them. Out of earlier indices j (such that j < i), we can consider which indices are worth considering as potential candidates for G[i] - for example, if there exists a pair of indices j and k such that j < k and a[j] < a[k], can index j ever be a candidate for G[i] for any i > k? If we can maintain information about the set of these possible candidate indices as we go through the array, it’s possible to efficiently determine the one that’s actually equal to G[i] for each i.

ApproachTime ComplexitySpace Complexity
1Brute ForceO(n+mO(m+n)
2$Approach 1$O(n)O(n)
3$Approach 2$O(n)O(n)
4Time OptimizedO(n)O(n)
5Memory OptimizedO(n)O(n)

Solutions

With no further due, lets take a example of code solutions.

Brute Force

Description

1Code goes here...

$Approach-1$

Description

1Code goes here...

$Approach-2$

Description

1Code goes here...

$Time-Optimized$

Description

1Code goes here...
### $Space-Optimized$Description
1Code goes here...