Maximum Width Ramp, an O(NlogN) solution

Problem is here: https://leetcode.com/problems/maximum-width-ramp/description/:

Given an array A of integers, a ramp is a tuple (i, j) for which i < j and A[i] <= A[j].  The width of such a ramp is j - i.
Find the maximum width of a ramp in A.  If one doesn't exist, return 0.

Example 1:
Input: [6,0,8,2,1,5]
Output: 4
Explanation: 
The maximum width ramp is achieved at (i, j) = (1, 5): A[1] = 0 and A[5] = 5.
Example 2:
Input: [9,8,1,0,1,9,4,0,4,1]
Output: 7
Explanation: 
The maximum width ramp is achieved at (i, j) = (2, 9): A[2] = 1 and A[9] = 1.

Note:
  1. 2 <= A.length <= 50000
  2. 0 <= A[i] <= 50000
There must be an O(N) solution to this problem, but I got an NLogN one (sorry). The idea is the following:

  • Create a class called "Pair" which will have the value and index of the array
  • Implement an IComparable interface (to be used in the Sort algorithm) in such a way that if the two values are the same, compare based on the index, otherwise compare based on the value
  • After that do a linear scan keeping track of the smallest index all the way from 0..i-1, and check at element i whether the best width is greater than the max (using the smallest index var)
Not very fast by any stretch of the imagination. Would love to hear about an O(N) solution. Cheers, Marcelo



public class Solution
{
 public int MaxWidthRamp(int[] A)
 {
  Pair[] pairs = new Pair[A.Length];

  for (int i = 0; i < A.Length; i++)
  {
   pairs[i] = new Pair(A[i], i);
  }

  Array.Sort(pairs);

  int maxWidth = 0;
  int smallestIndex = pairs[0].index;

  for (int i = 1; i < pairs.Length; i++)
  {
   maxWidth = Math.Max(maxWidth, pairs[i].index - smallestIndex);
   smallestIndex = Math.Min(smallestIndex, pairs[i].index);
  }

  return maxWidth;
 }
}

public class Pair : IComparable
{
 public int val;
 public int index;

 public Pair(int val, int index)
 {
  this.val = val;
  this.index = index;
 }

 public int CompareTo(Object obj)
 {
  Pair p = obj as Pair;
  if (this.val == p.val) return this.index.CompareTo(p.index);
  return this.val.CompareTo(p.val);
 }
}

Comments

  1. Very clever! I've decided to use a balanced binary search tree to keep track of the indices where I've seen the largest number starting from the end and using a binary search to find an index of the first element that is greater or equal to the current element.

    class Solution {
    public:
    int maxWidthRamp(vector& A) {
    map largest;
    largest[A.back()] = A.size()-1;
    int max_width = 0;
    for (int i = A.size()-2; i >= 0; --i) {
    auto found = largest.lower_bound(A[i]);
    if (found != largest.cend()) {
    max_width = max(max_width, found->second - i);
    }
    if (A[i] > largest.rbegin()->first) {
    largest[A[i]] = i;
    }
    }
    return max_width;
    }
    };

    https://gist.github.com/ttsugriy/81744e62aed3f84bdd58a0ffe9c79fcb

    This implementation has the same O(N*log(N)) complexity, but I have a gut feeling that it can be done in linear time...

    ReplyDelete
    Replies
    1. yep, my gut feeling was correct - we don't actually need a map to maintain the largest index seen so far when scanning from the end - we can use a stack/list to maintain it. Another important observation is that we don't need to do a binary search to find the first appropriate index if we do a second scan from the beginning, since we can discard all elements that are greater or equal to this the current value, since the only other elements they would be greater or equal to them would be to the right of the current element if we don't take the current element. The implementation of this idea in Rust is below:

      impl Solution {
      pub fn max_width_ramp(a: Vec) -> i32 {
      let mut largest: Vec = vec![(a.len()-1) as usize];
      for (i, val) in a.iter().enumerate().rev().skip(1) {
      if *val > a[*largest.last().unwrap()] {
      largest.push(i);
      }
      }
      let mut max_width = 0;
      for (i, val) in a.iter().enumerate() {
      while !largest.is_empty() && *val <= a[*largest.last().unwrap()] {
      max_width = std::cmp::max(max_width, largest.pop().unwrap() - i);
      }
      }
      max_width as i32
      }
      }

      https://gist.github.com/ttsugriy/d106f66e30bd8db704ef96d41b4ae6d4

      Delete
    2. btw, the Rust solution above takes 4ms :)

      Delete

Post a Comment

Popular posts from this blog

Golang vs. C#: performance characteristics (simple case study)

Claude vs ChatGPT: A Coder's Perspective on LLM Performance

ProjectEuler Problem 719 (some hints, but no spoilers)