3010. Divide an Array Into Subarrays With Minimum Cost I
Problem Description
You have an array of integers nums
with length n
.
The cost of an array is defined as the value of its first element. For example:
- The cost of
[1,2,3]
is1
(the first element) - The cost of
[3,4,1]
is3
(the first element)
Your task is to divide the array nums
into exactly 3 disjoint contiguous subarrays. This means:
- You split the array into 3 consecutive parts
- Each part must be non-empty
- Together, these 3 parts contain all elements from the original array
- No element appears in more than one part
The total cost is the sum of the costs of all 3 subarrays (i.e., the sum of the first elements of each subarray).
You need to find the minimum possible sum of these costs.
For example, if nums = [1, 2, 5, 3, 4]
, one way to split it could be:
- First subarray:
[1]
with cost1
- Second subarray:
[2, 5]
with cost2
- Third subarray:
[3, 4]
with cost3
- Total cost:
1 + 2 + 3 = 6
The goal is to find the splitting strategy that minimizes this total cost.
Intuition
Let's think about what happens when we split the array into 3 contiguous subarrays. We need to make two cuts in the array to create three parts.
The key insight is that the first subarray must always start with nums[0]
, since we're splitting the original array into contiguous parts. This means nums[0]
will always contribute to our total cost, no matter how we split the array.
Now, where should we make our two cuts? Each cut creates a new subarray, and each new subarray contributes its first element to the total cost. So after making two cuts:
- The first subarray contributes
nums[0]
(fixed) - The second subarray contributes its first element (depends on where we cut)
- The third subarray contributes its first element (depends on where we cut)
Here's the crucial observation: the first elements of the second and third subarrays can be any elements from nums[1]
onwards (since we can place our cuts anywhere after position 0).
To minimize the total cost, we want the first elements of the second and third subarrays to be as small as possible. This means we should:
- Find the smallest element in
nums[1:]
- this will be the first element of one subarray - Find the second smallest element in
nums[1:]
- this will be the first element of the other subarray
The minimum possible sum is therefore: nums[0] + (smallest element after index 0) + (second smallest element after index 0)
This explains why the solution simply finds the two smallest values among nums[1:]
and adds them to nums[0]
. We can always arrange our cuts to make these two elements the starting points of the second and third subarrays.
Learn more about Sorting patterns.
Solution Approach
Based on our intuition, we need to find the two smallest elements from nums[1:]
and add them to nums[0]
. The implementation uses a simple traversal with three variables:
-
Initialize three variables:
a = nums[0]
: This is the fixed cost from the first subarrayb = inf
: Will store the smallest element fromnums[1:]
c = inf
: Will store the second smallest element fromnums[1:]
-
Traverse through the remaining elements (starting from index 1): For each element
x
innums[1:]
:- If
x < b
: This means we found a new smallest element- Move the current smallest
b
to become the second smallest:c = b
- Update
b
with the new smallest:b = x
- Move the current smallest
- Else if
x < c
: This meansx
is not the smallest but is smaller than the current second smallest- Update
c
with this value:c = x
- Update
- If
-
Return the sum:
a + b + c
This approach efficiently finds the two minimum values in a single pass through the array. The algorithm maintains the invariant that b
is always the smallest element seen so far (after index 0), and c
is the second smallest.
Time Complexity: O(n)
- We traverse the array once
Space Complexity: O(1)
- We only use three extra variables regardless of input size
The elegance of this solution lies in its simplicity - instead of trying all possible ways to split the array (which would be expensive), we directly compute the minimum possible cost by identifying which elements must contribute to it.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [10, 2, 8, 5, 1, 9]
.
Step 1: Initialize variables
a = nums[0] = 10
(first subarray's cost is fixed)b = inf
(will track smallest element after index 0)c = inf
(will track second smallest element after index 0)
Step 2: Process each element from index 1 onwards
Processing nums[1] = 2
:
- Is
2 < b (inf)
? Yes! - Update:
c = b = inf
,b = 2
- Current state:
a=10, b=2, c=inf
Processing nums[2] = 8
:
- Is
8 < b (2)
? No - Is
8 < c (inf)
? Yes! - Update:
c = 8
- Current state:
a=10, b=2, c=8
Processing nums[3] = 5
:
- Is
5 < b (2)
? No - Is
5 < c (8)
? Yes! - Update:
c = 5
- Current state:
a=10, b=2, c=5
Processing nums[4] = 1
:
- Is
1 < b (2)
? Yes! - Update:
c = b = 2
,b = 1
- Current state:
a=10, b=1, c=2
Processing nums[5] = 9
:
- Is
9 < b (1)
? No - Is
9 < c (2)
? No - No updates needed
- Final state:
a=10, b=1, c=2
Step 3: Calculate result
- Minimum sum =
a + b + c = 10 + 1 + 2 = 13
Verification: We found that the two smallest elements after index 0 are 1
(at index 4) and 2
(at index 1). The algorithm correctly identifies these values. One possible split achieving this cost would be:
- First subarray:
[10, 2, 8, 5]
with cost 10 - Second subarray:
[1]
with cost 1 - Third subarray:
[9]
with cost 2 (wait, this would be 9!)
Actually, let me reconsider. We need the split where index 1 and index 4 are starting positions:
- First subarray:
[10]
with cost 10 - Second subarray:
[2, 8, 5]
with cost 2 - Third subarray:
[1, 9]
with cost 1 - Total: 10 + 2 + 1 = 13 β
The algorithm correctly identifies that regardless of how we arrange our cuts, the minimum possible sum is 13 by ensuring the two smallest elements after position 0 become the starting elements of the second and third subarrays.
Solution Implementation
1from typing import List
2from math import inf
3
4class Solution:
5 def minimumCost(self, nums: List[int]) -> int:
6 # First element is fixed as the first part of our sum
7 first_element = nums[0]
8
9 # Initialize two variables to track the two smallest elements from index 1 onwards
10 # smallest: the minimum element found so far
11 # second_smallest: the second minimum element found so far
12 smallest = inf
13 second_smallest = inf
14
15 # Iterate through all elements starting from index 1
16 for current_num in nums[1:]:
17 if current_num < smallest:
18 # If current number is smaller than the smallest,
19 # update both: previous smallest becomes second smallest
20 second_smallest = smallest
21 smallest = current_num
22 elif current_num < second_smallest:
23 # If current number is between smallest and second smallest,
24 # only update second smallest
25 second_smallest = current_num
26
27 # Return sum of first element and two smallest elements from the rest
28 return first_element + smallest + second_smallest
29
1class Solution {
2 public int minimumCost(int[] nums) {
3 // First element is always included in the sum
4 int firstElement = nums[0];
5
6 // Initialize the two smallest values from the remaining elements
7 // Using 100 as initial value (assuming array elements are less than 100)
8 int smallest = 100;
9 int secondSmallest = 100;
10
11 // Find the two smallest elements from index 1 onwards
12 for (int i = 1; i < nums.length; ++i) {
13 if (nums[i] < smallest) {
14 // Current element becomes the new smallest
15 // Previous smallest becomes second smallest
16 secondSmallest = smallest;
17 smallest = nums[i];
18 } else if (nums[i] < secondSmallest) {
19 // Current element is between smallest and second smallest
20 secondSmallest = nums[i];
21 }
22 }
23
24 // Return sum of first element and two smallest from the rest
25 return firstElement + smallest + secondSmallest;
26 }
27}
28
1class Solution {
2public:
3 int minimumCost(vector<int>& nums) {
4 // First element is always included in the sum
5 int firstElement = nums[0];
6
7 // Initialize the two smallest values from the remaining elements
8 // Using 100 as initial value (assuming it's larger than any element in nums)
9 int smallest = 100;
10 int secondSmallest = 100;
11
12 // Find the two smallest elements from index 1 onwards
13 for (int i = 1; i < nums.size(); ++i) {
14 if (nums[i] < smallest) {
15 // Current element becomes the new smallest
16 // Previous smallest becomes second smallest
17 secondSmallest = smallest;
18 smallest = nums[i];
19 } else if (nums[i] < secondSmallest) {
20 // Current element is only smaller than second smallest
21 secondSmallest = nums[i];
22 }
23 }
24
25 // Return the sum of first element and two smallest elements from the rest
26 return firstElement + smallest + secondSmallest;
27 }
28};
29
1/**
2 * Calculates the minimum cost by finding the sum of the first element
3 * and the two smallest elements from the remaining array
4 * @param nums - Array of numbers to process
5 * @returns The sum of first element and two smallest from the rest
6 */
7function minimumCost(nums: number[]): number {
8 // Initialize: first element is fixed, two trackers for smallest values
9 let firstElement: number = nums[0];
10 let smallestValue: number = 100; // Tracks the smallest value found
11 let secondSmallestValue: number = 100; // Tracks the second smallest value found
12
13 // Iterate through all elements except the first one
14 for (const currentValue of nums.slice(1)) {
15 if (currentValue < smallestValue) {
16 // Current value becomes new smallest, previous smallest becomes second smallest
17 secondSmallestValue = smallestValue;
18 smallestValue = currentValue;
19 } else if (currentValue < secondSmallestValue) {
20 // Current value is only smaller than second smallest
21 secondSmallestValue = currentValue;
22 }
23 }
24
25 // Return sum of first element and two smallest values from remaining array
26 return firstElement + smallestValue + secondSmallestValue;
27}
28
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array nums
. This is because the code iterates through the array once starting from index 1 (through nums[1:]
), performing constant-time operations (comparisons and assignments) for each element.
The space complexity is O(1)
. The algorithm only uses three variables (a
, b
, c
) to store values regardless of the input size, requiring constant extra space.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall: Incorrect Update Order When Finding Two Minimum Values
A common mistake when tracking the two smallest elements is updating the variables in the wrong order or forgetting to cascade the updates properly.
Incorrect Implementation:
for current_num in nums[1:]: if current_num < smallest: smallest = current_num # β Lost the previous smallest value! second_smallest = smallest # This assigns the NEW smallest, not the old one elif current_num < second_smallest: second_smallest = current_num
In this buggy version, when we find a new smallest element, we lose track of the previous smallest value that should become the new second smallest. The line second_smallest = smallest
executes AFTER we've already updated smallest
, so both variables end up with the same value.
Another Common Mistake:
for current_num in nums[1:]: if current_num < second_smallest: # β Checking second_smallest first second_smallest = current_num if current_num < smallest: smallest = current_num
This approach uses two independent if
statements instead of if-elif
, which can cause the same element to update both variables. Also, checking second_smallest
before smallest
breaks the logic since an element smaller than smallest
is definitely smaller than second_smallest
.
Correct Solution:
for current_num in nums[1:]: if current_num < smallest: second_smallest = smallest # Save old smallest BEFORE updating smallest = current_num elif current_num < second_smallest: second_smallest = current_num
The key points to remember:
- Always save the old value before overwriting it
- Use
if-elif
structure to ensure each element only updates variables once - Check against
smallest
first, thensecond_smallest
- When updating
smallest
, cascade the old value tosecond_smallest
This pattern of maintaining the top-k minimum (or maximum) values is useful in many problems and getting the update logic right is crucial for correctness.
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80]
is 4
and LIS is
[3, 7, 40, 80]
.
What is the recurrence relation?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!