LeetCode 3909 - Compare Sums of Bitonic Parts

The problem gives us a bitonic array nums, meaning an array that first strictly increases to a single peak element and then strictly decreases. The task is to split this array into two overlapping parts: 1.

LeetCode Problem 3909

Difficulty: 🟡 Medium
Topics: Array

Solution

Problem Understanding

The problem gives us a bitonic array nums, meaning an array that first strictly increases to a single peak element and then strictly decreases. The task is to split this array into two overlapping parts:

  1. The ascending part, which includes all elements from the start of the array up to and including the peak.
  2. The descending part, which includes the peak and all elements to the end of the array.

We then compute the sum of each part and compare them. The result should be:

  • 0 if the ascending part sum is larger.
  • 1 if the descending part sum is larger.
  • -1 if the sums are equal.

The input array has guaranteed constraints: a length between 3 and 105 elements, all elements positive integers up to 10^9, and it is strictly bitonic. This guarantees there is always a unique peak and no repeated elements at the peak.

Important edge cases include arrays where the peak is at the start or end (though strictly increasing then decreasing prevents this from occurring), and arrays where the sums of the ascending and descending parts are equal. Because all numbers are positive, the sum is always increasing as we traverse a part.

The problem is straightforward conceptually but requires efficient peak detection because the array can have up to 100,000 elements.

Approaches

The brute-force approach is simple: iterate through the array, find the peak by comparing each element with its neighbors, slice the array into ascending and descending parts, compute sums for both parts, and compare. This works correctly but requires multiple passes over the array and slicing, which could be inefficient for large arrays.

The optimal approach leverages a single pass to compute the sums while detecting the peak. By iterating once through the array, we can keep a running sum of the ascending part until the peak, and then compute the descending sum in the remaining iteration. This avoids unnecessary slicing and reduces overhead. Because the array is strictly bitonic, we can detect the peak efficiently: the peak is the element where the next element is smaller.

Approach Time Complexity Space Complexity Notes
Brute Force O(n) O(n) Find peak, slice arrays, sum each part separately
Optimal O(n) O(1) Single pass to find peak and sum parts without extra arrays

Algorithm Walkthrough

  1. Initialize two variables asc_sum and desc_sum to 0. These will store the sums of the ascending and descending parts.
  2. Iterate over the array to find the peak element. At each index i, check if nums[i] > nums[i+1]. When true, nums[i] is the peak.
  3. Sum the ascending part from index 0 to the peak (inclusive) into asc_sum.
  4. Sum the descending part from the peak to the end into desc_sum.
  5. Compare asc_sum and desc_sum:
  • Return 0 if asc_sum > desc_sum.
  • Return 1 if asc_sum < desc_sum.
  • Return -1 if asc_sum == desc_sum.

Why it works: The bitonic property guarantees a unique peak. By summing both parts including the peak, we correctly capture all values for comparison. Using a single pass or minimal passes ensures efficiency for large arrays.

Python Solution

class Solution:
    def compareBitonicSums(self, nums: list[int]) -> int:
        n = len(nums)
        peak_index = 0
        
        # Find peak index
        for i in range(n - 1):
            if nums[i] > nums[i + 1]:
                peak_index = i
                break
        
        asc_sum = sum(nums[:peak_index + 1])
        desc_sum = sum(nums[peak_index:])
        
        if asc_sum > desc_sum:
            return 0
        elif asc_sum < desc_sum:
            return 1
        else:
            return -1

Implementation walkthrough: We first locate the peak using a simple iteration. The peak is defined as the first element that is larger than the next. Then we use Python's built-in sum() function to sum slices for ascending and descending parts. Finally, we return the comparison result. The code handles all valid bitonic arrays efficiently and correctly.

Go Solution

func compareBitonicSums(nums []int) int {
    n := len(nums)
    peakIndex := 0

    // Find peak index
    for i := 0; i < n-1; i++ {
        if nums[i] > nums[i+1] {
            peakIndex = i
            break
        }
    }

    ascSum, descSum := 0, 0

    for i := 0; i <= peakIndex; i++ {
        ascSum += nums[i]
    }
    for i := peakIndex; i < n; i++ {
        descSum += nums[i]
    }

    if ascSum > descSum {
        return 0
    } else if ascSum < descSum {
        return 1
    } else {
        return -1
    }
}

Go-specific notes: We explicitly iterate to sum elements because Go does not have a built-in slice sum function. Variable initialization and for-loop syntax differ slightly from Python, but the logic remains identical.

Worked Examples

Example 1: nums = [1,3,2,1]

Step Peak Detection Asc Sum Desc Sum
Iteration nums[1]=3 > nums[2]=2 → peak_index=1 sum([1,3])=4 sum([3,2,1])=6
Result - 4 6

Example 2: nums = [2,4,5,2]

Step Peak Detection Asc Sum Desc Sum
Iteration nums[2]=5 > nums[3]=2 → peak_index=2 sum([2,4,5])=11 sum([5,2])=7
Result - 11 7

Example 3: nums = [1,2,4,3]

Step Peak Detection Asc Sum Desc Sum
Iteration nums[2]=4 > nums[3]=3 → peak_index=2 sum([1,2,4])=7 sum([4,3])=7
Result - 7 7

Complexity Analysis

Measure Complexity Explanation
Time O(n) Single pass to find peak and sum both parts
Space O(1) No extra arrays, only a few integer variables

We avoid slicing overhead or extra arrays in the optimal approach. Summing slices or iterating with running totals is linear in time and constant in space.

Test Cases

# Provided examples
assert Solution().compareBitonicSums([1,3,2,1]) == 1  # Descending sum greater
assert Solution().compareBitonicSums([2,4,5,2]) == 0  # Ascending sum greater
assert Solution().compareBitonicSums([1,2,4,3]) == -1 # Equal sums

# Edge and boundary cases
assert Solution().compareBitonicSums([1,2,3]) == 0    # Peak at end, ascending > descending
assert Solution().compareBitonicSums([3,2,1]) == 1    # Peak at start, descending > ascending
assert Solution().compareBitonicSums([1,5,1]) == -1   # Smallest array with equal sums
assert Solution().compareBitonicSums([1,1000000000,1]) == 0 # Large numbers
Test Why
[1,3,2,1] Descending sum larger
[2,4,5,2] Ascending sum larger
[1,2,4,3] Equal sums
[1,2,3] Ascending only, edge peak at end
[3,2,1] Descending only, edge peak at start
[1,5,1] Small array, peak in middle
[1,1000000000,1] Test integer overflow with large numbers

Edge Cases

Edge Case 1: Peak at start or end

Although strictly bitonic arrays prevent a peak at the first element for increasing part or last element for decreasing part, the smallest possible array [a, b, c] can simulate these scenarios. The implementation correctly sums parts including the peak, so the comparison holds.

Edge Case 2: Equal sums

Arrays such as [1,2,4,3] or [1,5,1] produce equal sums. Our code explicitly checks for equality and returns -1, avoiding off-by-one mistakes in summation.

Edge Case 3: Large values

With constraints up to 10^9 per element and arrays of length 10^5, summing can reach 10^14. Using integers in Python handles this automatically, while in Go, int64 is assumed to prevent overflow for sums. The algorithm is robust to large inputs.