Facebook Pixel

3637. Trionic Array I

Problem Description

You are given an integer array nums of length n.

An array is called trionic if it can be divided into three parts with the following properties:

  • The first part nums[0...p] is strictly increasing
  • The second part nums[p...q] is strictly decreasing
  • The third part nums[q...n-1] is strictly increasing

where p and q are indices satisfying 0 < p < q < n - 1.

Your task is to determine if the given array is trionic. Return true if nums is trionic, otherwise return false.

Key requirements:

  • The array must have at least 4 elements (to accommodate three non-empty parts with the index constraints)
  • Each part must be strictly monotonic (no equal consecutive elements allowed)
  • The transition points p and q must exist within the valid range
  • The first part ends at index p, the second part starts at p and ends at q, and the third part starts at q and goes to the end

For example:

  • [1, 3, 2, 4] would be trionic with p=1 and q=2: increasing [1,3], decreasing [3,2], increasing [2,4]
  • [1, 2, 3, 4] would not be trionic as there's no decreasing section
  • [4, 3, 2, 1] would not be trionic as there's no initial increasing section or final increasing section
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that a trionic array has a very specific shape: it goes up, then down, then up again. We can think of it like a valley between two hills. To verify this pattern, we need to find the two transition points where the direction changes.

Since we need to check for strict monotonicity in each section, we can traverse the array once and identify where these transitions occur. The natural approach is to:

  1. Start from the beginning and keep moving forward while the array is strictly increasing. This gives us the peak of the first hill (point p).

  2. From that peak, continue moving forward while the array is strictly decreasing. This takes us to the bottom of the valley (point q).

  3. From the valley bottom, check if the remaining elements form a strictly increasing sequence until the end.

The elegance of this approach lies in its simplicity - we're essentially following the "shape" of the array. If at any point we can't find the expected pattern (like if there's no initial increase, or no decrease after the first peak, or no final increase), we know the array isn't trionic.

We also need to be careful about edge cases:

  • If p = 0, it means we never found an increasing part at the start
  • If q = p, it means we never found a decreasing part after the first peak
  • If q = n - 1, it means there's no room for a final increasing part
  • If we can't reach the end with the final increasing check, the third part isn't fully increasing

This linear scan approach naturally validates all the constraints while finding the transition points in a single pass through the array.

Solution Approach

The implementation uses a single-pass algorithm with pointer manipulation to identify the three required sections of a trionic array.

Step 1: Find the end of the strictly increasing section

We initialize a pointer p = 0 and move it rightward as long as nums[p] < nums[p + 1]. This loop continues while we have a strictly increasing sequence. The loop stops when we either reach the position n - 2 (to avoid index out of bounds) or find an element that doesn't satisfy the strict increase condition.

while p < n - 2 and nums[p] < nums[p + 1]:
    p += 1

After this loop, p points to the last element of the first increasing section. We check if p = 0, which would mean no increasing section was found at the beginning, and return false immediately.

Step 2: Find the end of the strictly decreasing section

We initialize q = p (starting from where the increasing section ended) and move it rightward as long as nums[q] > nums[q + 1]. This identifies the strictly decreasing section.

while q < n - 1 and nums[q] > nums[q + 1]:
    q += 1

After this loop, q points to the last element of the decreasing section. We check two conditions:

  • If q = p: No decreasing section was found
  • If q = n - 1: The decreasing section extends to the end, leaving no room for the third increasing section

If either condition is true, we return false.

Step 3: Verify the final strictly increasing section

Starting from the current position of q, we check if the remaining elements form a strictly increasing sequence:

while q < n - 1 and nums[q] < nums[q + 1]:
    q += 1

Step 4: Final validation

If q = n - 1 after the third loop, it means we successfully traversed the entire array following the trionic pattern (increase → decrease → increase). We return true. Otherwise, the third section didn't extend to the end of the array as required, so we return false.

The time complexity is O(n) since we traverse the array at most once, and the space complexity is O(1) as we only use two pointers.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the algorithm with the array nums = [2, 5, 3, 1, 4, 6].

Initial Setup:

  • Array length n = 6
  • Initialize pointer p = 0

Step 1: Find the first increasing section

  • Start at p = 0: Check if nums[0] < nums[1]2 < 5 ✓, move p to 1
  • At p = 1: Check if nums[1] < nums[2]5 < 3 ✗, stop
  • First increasing section: [2, 5] (indices 0 to 1)
  • p = 1 (last index of increasing section)

Step 2: Find the decreasing section

  • Initialize q = p = 1
  • At q = 1: Check if nums[1] > nums[2]5 > 3 ✓, move q to 2
  • At q = 2: Check if nums[2] > nums[3]3 > 1 ✓, move q to 3
  • At q = 3: Check if nums[3] > nums[4]1 > 4 ✗, stop
  • Decreasing section: [5, 3, 1] (indices 1 to 3)
  • q = 3 (last index of decreasing section)

Step 3: Verify the final increasing section

  • At q = 3: Check if nums[3] < nums[4]1 < 4 ✓, move q to 4
  • At q = 4: Check if nums[4] < nums[5]4 < 6 ✓, move q to 5
  • At q = 5: We've reached n - 1, stop
  • Final increasing section: [1, 4, 6] (indices 3 to 5)

Step 4: Final validation

  • Check if q == n - 15 == 5
  • The array successfully forms the pattern: increase → decrease → increase
  • Return true

The array [2, 5, 3, 1, 4, 6] is trionic with transition points at p = 1 and q = 3.

Solution Implementation

1class Solution:
2    def isTrionic(self, nums: List[int]) -> bool:
3        """
4        Check if the array follows a trionic pattern:
5        1. First phase: strictly increasing
6        2. Second phase: strictly decreasing  
7        3. Third phase: strictly increasing
8        Each phase must contain at least one transition.
9        """
10        n = len(nums)
11      
12        # Phase 1: Find the end of the first increasing sequence
13        first_peak_index = 0
14        while first_peak_index < n - 2 and nums[first_peak_index] < nums[first_peak_index + 1]:
15            first_peak_index += 1
16      
17        # If no increasing phase found at the start, pattern is invalid
18        if first_peak_index == 0:
19            return False
20      
21        # Phase 2: Find the end of the decreasing sequence (valley point)
22        valley_index = first_peak_index
23        while valley_index < n - 1 and nums[valley_index] > nums[valley_index + 1]:
24            valley_index += 1
25      
26        # Check if decreasing phase exists and doesn't reach the end
27        # valley_index == first_peak_index means no decreasing phase
28        # valley_index == n - 1 means no room for final increasing phase
29        if valley_index == first_peak_index or valley_index == n - 1:
30            return False
31      
32        # Phase 3: Find the end of the final increasing sequence
33        final_index = valley_index
34        while final_index < n - 1 and nums[final_index] < nums[final_index + 1]:
35            final_index += 1
36      
37        # Valid trionic pattern if the final increasing phase reaches the end
38        return final_index == n - 1
39
1class Solution {
2    /**
3     * Checks if the array follows a trionic pattern:
4     * 1. Strictly increasing sequence
5     * 2. Followed by strictly decreasing sequence  
6     * 3. Followed by strictly increasing sequence
7     * All three segments must exist with at least one element each.
8     * 
9     * @param nums input array to check
10     * @return true if array follows trionic pattern, false otherwise
11     */
12    public boolean isTrionic(int[] nums) {
13        int arrayLength = nums.length;
14        int currentIndex = 0;
15      
16        // Phase 1: Find the first strictly increasing segment
17        // Move forward while elements are strictly increasing
18        while (currentIndex < arrayLength - 2 && nums[currentIndex] < nums[currentIndex + 1]) {
19            currentIndex++;
20        }
21      
22        // If no increasing segment found at the beginning, pattern is invalid
23        if (currentIndex == 0) {
24            return false;
25        }
26      
27        // Phase 2: Find the strictly decreasing segment
28        // Save the peak position where decreasing starts
29        int peakIndex = currentIndex;
30      
31        // Move forward while elements are strictly decreasing
32        while (currentIndex < arrayLength - 1 && nums[currentIndex] > nums[currentIndex + 1]) {
33            currentIndex++;
34        }
35      
36        // Check if decreasing segment exists and doesn't reach the end
37        // (we need room for the final increasing segment)
38        if (currentIndex == peakIndex || currentIndex == arrayLength - 1) {
39            return false;
40        }
41      
42        // Phase 3: Find the final strictly increasing segment
43        // Move forward while elements are strictly increasing
44        while (currentIndex < arrayLength - 1 && nums[currentIndex] < nums[currentIndex + 1]) {
45            currentIndex++;
46        }
47      
48        // Pattern is valid only if we've reached the end of the array
49        return currentIndex == arrayLength - 1;
50    }
51}
52
1class Solution {
2public:
3    bool isTrionic(vector<int>& nums) {
4        int n = nums.size();
5      
6        // Phase 1: Find the end of the first increasing segment
7        // Start from index 0 and move right while values are increasing
8        int firstPeakIndex = 0;
9        while (firstPeakIndex < n - 2 && nums[firstPeakIndex] < nums[firstPeakIndex + 1]) {
10            firstPeakIndex++;
11        }
12      
13        // If no increasing segment found at the beginning, pattern is invalid
14        if (firstPeakIndex == 0) {
15            return false;
16        }
17      
18        // Phase 2: Find the end of the decreasing segment (valley)
19        // Start from the peak and move right while values are decreasing
20        int valleyIndex = firstPeakIndex;
21        while (valleyIndex < n - 1 && nums[valleyIndex] > nums[valleyIndex + 1]) {
22            valleyIndex++;
23        }
24      
25        // If no decreasing segment found or it reaches the end, pattern is invalid
26        if (valleyIndex == firstPeakIndex || valleyIndex == n - 1) {
27            return false;
28        }
29      
30        // Phase 3: Verify the final increasing segment
31        // Start from the valley and check if values increase until the end
32        int currentIndex = valleyIndex;
33        while (currentIndex < n - 1 && nums[currentIndex] < nums[currentIndex + 1]) {
34            currentIndex++;
35        }
36      
37        // Valid trionic pattern if the final increasing segment reaches the last element
38        return currentIndex == n - 1;
39    }
40};
41
1/**
2 * Checks if an array forms a trionic pattern (mountain-valley-mountain).
3 * A trionic pattern consists of:
4 * 1. An increasing sequence (ascending)
5 * 2. Followed by a decreasing sequence (descending)
6 * 3. Followed by another increasing sequence (ascending)
7 * 
8 * @param nums - The input array of numbers to check
9 * @returns true if the array forms a trionic pattern, false otherwise
10 */
11function isTrionic(nums: number[]): boolean {
12    const arrayLength: number = nums.length;
13    let currentIndex: number = 0;
14  
15    // Phase 1: Find the first ascending sequence
16    // Move forward while numbers are increasing
17    while (currentIndex < arrayLength - 2 && nums[currentIndex] < nums[currentIndex + 1]) {
18        currentIndex++;
19    }
20  
21    // If no ascending sequence found at the beginning, not trionic
22    if (currentIndex === 0) {
23        return false;
24    }
25  
26    // Store the peak position of the first mountain
27    let peakIndex: number = currentIndex;
28  
29    // Phase 2: Find the descending sequence (valley)
30    // Move forward while numbers are decreasing
31    while (peakIndex < arrayLength - 1 && nums[peakIndex] > nums[peakIndex + 1]) {
32        peakIndex++;
33    }
34  
35    // If no descending sequence found or it reaches the end, not trionic
36    if (peakIndex === currentIndex || peakIndex === arrayLength - 1) {
37        return false;
38    }
39  
40    // Phase 3: Find the second ascending sequence
41    // Move forward while numbers are increasing again
42    while (peakIndex < arrayLength - 1 && nums[peakIndex] < nums[peakIndex + 1]) {
43        peakIndex++;
44    }
45  
46    // Valid trionic pattern if we've reached the end of the array
47    return peakIndex === arrayLength - 1;
48}
49

Time and Space Complexity

The time complexity is O(n), where n is the length of the array. The algorithm makes a single pass through the array using three sequential while loops. In the worst case, each element is visited at most once across all three loops combined. The first loop iterates through elements while they're increasing, the second loop continues from where the first stopped and iterates while elements are decreasing, and the third loop continues from where the second stopped and iterates while elements are increasing again. Since each element is processed at most once, the total time complexity is linear.

The space complexity is O(1), using only constant extra space. The algorithm uses only a fixed number of variables (n, p, and q) regardless of the input size. No additional data structures like arrays, lists, or recursive call stacks are created, so the memory usage remains constant.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Off-by-One Errors with Index Boundaries

A critical mistake occurs when developers misunderstand how the indices p and q relate to the array segments. After the first while loop, p points to the last element of the increasing section, not the first element of the decreasing section. This confusion often leads to incorrect boundary checks.

Incorrect approach:

# Wrong: Treating p as the start of decreasing section
if p >= n - 2:  # This check is too restrictive
    return False

Correct approach:

# Right: p is the last element of increasing section
# We need at least 2 more elements after p for valid trionic
if p == 0:  # No increasing section found
    return False

Pitfall 2: Not Handling Adjacent Duplicate Values

The problem requires strictly monotonic sequences, meaning consecutive equal values invalidate the pattern. Developers often use <= or >= comparisons instead of strict < or >.

Incorrect approach:

# Wrong: Allows equal consecutive values
while p < n - 2 and nums[p] <= nums[p + 1]:  # Should be strict <
    p += 1

Correct approach:

# Right: Strict inequality ensures no duplicates
while p < n - 2 and nums[p] < nums[p + 1]:
    p += 1

Pitfall 3: Incorrect Minimum Array Length Validation

Some developers add explicit length checks that are either unnecessary or incorrect. The algorithm naturally handles the minimum length requirement through its logic.

Incorrect approach:

def isTrionic(self, nums):
    n = len(nums)
    if n < 3:  # Wrong threshold - should be 4 minimum
        return False
    # ... rest of code

Better approach: The current solution implicitly handles this through the while loop conditions. The first loop requires p < n - 2, ensuring at least 3 elements. The subsequent checks ensure we need at least 4 elements total for a valid trionic pattern.

Pitfall 4: Misunderstanding the Shared Boundary Elements

A subtle but important detail: the element at index p belongs to both the first increasing section (as its last element) and the second decreasing section (as its first element). Similarly for index q.

Example with [1, 3, 2, 4]:

  • First section: indices 0 to 1, values [1, 3]
  • Second section: indices 1 to 2, values [3, 2]
  • Third section: indices 2 to 3, values [2, 4]

The boundaries overlap - this is correct and intended behavior. Developers sometimes try to "fix" this by adjusting indices unnecessarily, breaking the algorithm.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What is the best way of checking if an element exists in a sorted array once in terms of time complexity? Select the best that applies.


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More