Skip to content

410. Split Array Largest Sum

from typing import Callable


class Solution:
    """✂️ Split Array Largest Sum.

    Problem:
    --------
    Given an integer array `nums` and an integer `k`, split `nums` into `k` non-empty
    contiguous subarrays so that the largest subarray sum is as small as possible.
    Return that minimized largest sum.

    Key Insight:
    ------------
    Search on the answer instead of on the split. Let `P(t)` be "nums can be split into
    at most k subarrays, each with sum <= t". `P` is monotone in `t`: if a cap `t` works,
    any larger cap works too.

        t:    max(nums) ... ... ... sum(nums)
        P(t): F  F  F  T  T  T  T  T

    The answer is the first `t` where `P(t)` is True.

    Approach:
    ---------
    - Bounds: `t >= max(nums)`, because every element ends up in some subarray, and
      `t = sum(nums)` (one subarray) is always feasible.
    - Feasibility: go left to right and keep adding to the current subarray until the
      next element would push it past `t`, then start a new one. This greedy gives the
      fewest subarrays possible under cap `t`.
    - "At most k" is enough: since `n >= k`, a split with fewer pieces can be cut further
      until it has exactly `k`, and cutting never makes a sum larger.
    - Use the `first_true` template over [max(nums), sum(nums) + 1).

    Complexity:
    -----------
    - Time: O(n log S), where S = sum(nums) - max(nums) is the size of the search range;
      each of the O(log S) checks is O(n).
    - Space: O(1)
    """

    def splitArray(self, nums: list[int], k: int) -> int:

        def check_feasibility(max_allowed_sum: int) -> bool:
            curr_sum = 0
            num_subarrays = 1
            for num in nums:
                if curr_sum + num > max_allowed_sum:
                    num_subarrays += 1
                    curr_sum = 0
                curr_sum += num
            return num_subarrays <= k

        def first_true(low: int, high: int, pred: Callable[[int], bool]) -> int:
            while low < high:
                mid = (low + high) // 2
                if pred(mid):
                    high = mid
                else:
                    low = mid + 1
            return low

        return first_true(max(nums), sum(nums) + 1, check_feasibility)