Find the Longest Valid Obstacle Course at Each Position

Updated on 28 May, 2025
Find the Longest Valid Obstacle Course at Each Position header image

Problem Statement

You are tasked with constructing a series of obstacle courses, each detailed by an array named obstacles. Each index i of this array represents a unique obstacle with a specific height. From this setup, you need to determine the length of the longest successive sequence up to each index i where every selected obstacle's height is the same or greater than the obstacle immediately before it in sequence. These sequences must always end at the obstacle i and follow the original order found in obstacles. The output should be an array ans where each element ans[i] signifies the length of the longest feasible obstacle course sequence that culminates at index i.

Examples

Example 1

Input:

obstacles = [1,2,3,2]

Output:

[1,2,3,3]

Explanation:

The longest valid obstacle course at each position is:
- i = 0: [1], [1] has length 1.
- i = 1: [1,2], [1,2] has length 2.
- i = 2: [1,2,3], [1,2,3] has length 3.
- i = 3: [1,2,3,2], [1,2,2] has length 3.

Example 2

Input:

obstacles = [2,2,1]

Output:

[1,2,1]

Explanation:

The longest valid obstacle course at each position is:
- i = 0: [2], [2] has length 1.
- i = 1: [2,2], [2,2] has length 2.
- i = 2: [2,2,1], [1] has length 1.

Example 3

Input:

obstacles = [3,1,5,6,4,2]

Output:

[1,1,2,3,2,2]

Explanation:

The longest valid obstacle course at each position is:
- i = 0: [3], [3] has length 1.
- i = 1: [3,1], [1] has length 1.
- i = 2: [3,1,5], [3,5] has length 2. [1,5] is also valid.
- i = 3: [3,1,5,6], [3,5,6] has length 3. [1,5,6] is also valid.
- i = 4: [3,1,5,6,4], [3,4] has length 2. [1,4] is also valid.
- i = 5: [3,1,5,6,4,2], [1,2] has length 2.

Constraints

  • n == obstacles.length
  • 1 <= n <= 10^5
  • 1 <= obstacles[i] <= 10^7

Approach and Intuition

  1. Understanding the Requirements:

    • The sequence must be non-decreasing in height.
    • It can start from any earlier or the same index and must include the obstacle at the current index i.
  2. Initial Thoughts:

    • This problem is reminiscent of finding the longest non-decreasing subsequence up to each index in an array.
    • Unlike a general longest increasing subsequence problem which is global, here you need local sequences up to each index.
  3. Detailed Approach:

    • Naive Approach:

      • You could check all subarrays ending at each index but this would be inefficient for large arrays because it results in a complexity of O(n^2).
    • Efficient Approach:

      • Use a dynamic programming approach with a data structure like a Binary Indexed Tree or a Segment Tree to perform prefix maximum queries.
      • Alternatively, maintain a monotonic list and perform binary search to determine the insertion point, enabling an O(n log n) solution.
  4. Optimizing the Sequence Search:

    • Leverage binary search to find the rightmost obstacle height ≤ obstacles[i] in the active list and update accordingly.
  5. Handling Edge Cases:

    • Single Element: Sequence is always length 1.
    • All Equal/Increasing/Decreasing: Ensure logic handles repeated values or strictly increasing/decreasing sequences correctly.

This strategy effectively balances between understanding individual sequence constraints at each index and global state management throughout the array traversal, which is critical given the potential size of the input based on the constraints provided.

Solutions

  • C++
  • Java
  • Python
cpp
class Solution {
public:
    vector<int> computeLongestObstacleCourse(vector<int>& obs) {
        int len = obs.size();
        
        vector<int> courseLengths(len, 1), longestSequence;
        
        for (int j = 0; j < len; ++j) {
            int position = upper_bound(longestSequence.begin(), longestSequence.end(), obs[j]) - longestSequence.begin();
            if (position == longestSequence.size())
                longestSequence.push_back(obs[j]);
            else
                longestSequence[position] = obs[j];
            courseLengths[j] = position + 1;
        }
        return courseLengths;
    }
};

The solution involves calculating the longest obstacle course sequence that can be formed at each position using values from the given vector obs. This is implemented in C++ using the dynamic programming technique along with binary search for optimization. Here's how the provided solution achieves this:

  • A vector courseLengths is initialized to store the length of the longest valid sequence ended at each position, initialized with all ones to account for each element being the trivial starting of a sequence.
  • Another vector, longestSequence, is used to efficiently manage and retrieve the dynamically increasing subsequence lengths using binary search.
  • Iterate through the obs vector. For each value at index j, determine its position in longestSequence using the upper_bound, which effectively finds an optimal position to replace in longestSequence for maintaining the longest increasing subsequence.
  • Modify the longestSequence based on whether the current value extends the sequence or replaces an existing value.
  • Store the size of the valid sequence up to the current position in courseLengths.
  • Finally, the method returns the courseLengths vector which contains the length of the longest obstacle course valid at every position.

Essential Concepts Employed:

  • Dynamic Programming: By tracking the length of the sequence that ends with each element.
  • Binary Search: Utilizes upper_bound to efficiently find positions in the dynamic sequence, ensuring the approach remains efficient even for larger input sizes.

This solution effectively balances clarity and efficiency, ensuring a deep understanding of how dynamic sequences are manipulated in competitive programming problems.

java
class Solution {
    List<Integer> resultArray;

    // Binary search to find the insertion point in the list
    private int binarySearchInsertionIndex(int[] array, int val, int upperBound) {
        if (upperBound == 0)
            return 0;
        int lowerBound = 0;
        while (lowerBound < upperBound) {
            int mid = lowerBound + (upperBound - lowerBound) / 2;
            if (array[mid] <= val)
                lowerBound = mid + 1;
            else
                upperBound = mid;
        }
        return lowerBound;
    }
    
    public int[] calculateLongestCourse(int[] obstaclesEachPoint) {
        int totalObstacles = obstaclesEachPoint.length, end = 0;
        
        int[] results = new int[totalObstacles], leastIncrements = new int[totalObstacles];

        for (int i = 0; i < totalObstacles; ++i) {
            int pointHeight = obstaclesEachPoint[i];
            
            // Determine correct insertion index.
            int insertionIndex = binarySearchInsertionIndex(leastIncrements, pointHeight, end);
            if (insertionIndex == end)
                end++;

            leastIncrements[insertionIndex] = pointHeight;
            results[i] = insertionIndex + 1;
        }
        return results;
    }
}

The provided Java code defines a solution to compute the longest valid obstacle course at each position given an array of obstacle heights. The solution utilizes a technique similar to the Longest Increasing Subsequence (LIS) problem, but adapted to accommodate the specifics of the problem scenario. Here's a breakdown of how the code accomplishes this:

  • An extra array, leastIncrements, keeps track of the minimum last value of the longest valid sequences so far. This array helps in determining the length of the longest sequence up to each point in the input array.

  • The method calculateLongestCourse iterates through each obstacle in the obstaclesEachPoint array. For each obstacle:

    • It uses a modified binary search (binarySearchInsertionIndex) to find the correct position to place the current obstacle in the leastIncrements array. This ensures that the array remains sorted and helps in determining the position where the current obstacle can extend the previous sequences or start a new sequence.
    • If the insertion position (insertionIndex) is at the end of the current sequence length (end), it increments end, thus extending the length of the valid sequences.
    • It updates the leastIncrements array with the current height at the found insertion index.
    • It stores the length of the longest valid sequence up to the current point in the results array, by assigning insertionIndex + 1 to results[i].
  • Finally, the updated results array is returned, which represents the length of the longest valid obstacle course at each position in the original array.

This solution is effective in that it efficiently utilizes binary search within the main loop to maintain an ordered sequence, thereby optimizing the process to a time complexity closer to ( O(n \log n) ) where ( n ) is the length of the input array, as opposed to a naive ( O(n^2) ) solution.

python
def find_longest_path_at_each_position(obstacles: List[int]) -> List[int]:
    length = len(obstacles)
    result = [1] * length
    
    # Store the minimum end value of increasing subsequences of each length.
    seq = []

    for index, obstacle in enumerate(obstacles):
        # Find insertion point in the sorted sequence.
        pos = bisect.bisect_right(seq, obstacle)
        
        if pos == len(seq):
            seq.append(obstacle)
        else:
            seq[pos] = obstacle
            
        result[index] = pos + 1
        
    return result

The problem "Find the Longest Valid Obstacle Course at Each Position" is about calculating the longest increasing subsequence up to each position in a given list of obstacles. This Python solution implements a dynamic programming approach using binary search to efficiently solve the problem. Here's a summary of how the solution works:

  • Initialize an array result, where each position starts with a value of 1, indicating the minimum length of any subsequence.
  • Use a helper list seq to maintain the smallest possible ending value for all subsequences of varying lengths found during the iteration over the obstacles.
  • Iterate through each obstacle in the array:
    • Use bisect_right from the bisect module to determine the position in seq where the current obstacle can either extend a subsequence or replace an existing value to maintain the minimal possible ending values.
    • Update seq accordingly—if the position matches the length of seq, append the obstacle, otherwise replace the value at pos in seq.
    • Update the result at the current index to reflect the length of the longest valid subsequence ending at that position (position in seq + 1).

Upon completion, result holds the length of the longest increasing subsequence up to each position in the original list, effectively providing a solution to the given problem. This approach ensures efficiency by using binary search, which keeps the sequence update task to logarithmic time complexity.

This solution highlights the practical application of dynamic programming combined with binary search for problems related to subsequences, and handles varying sizes and values of obstacles adeptly.

Comments

No comments yet.