LeetCode 1262 - Greatest Sum Divisible by Three
The problem asks us to find the maximum sum of elements from an integer array nums such that the sum is divisible by thr
Difficulty: 🟡 Medium
Topics: Array, Dynamic Programming, Greedy, Sorting
Solution
Problem Understanding
The problem asks us to find the maximum sum of elements from an integer array nums such that the sum is divisible by three. In other words, we are allowed to pick any subset of numbers from the array, and our goal is to maximize the sum of that subset under the condition that the sum modulo 3 equals 0. The array nums has elements ranging from 1 to 10,000 and can be as large as 40,000 elements, so any brute-force approach that tries all subsets will be computationally infeasible.
The constraints imply that a naive solution that checks every subset is not practical due to exponential complexity. The problem guarantees that all elements are positive integers, so the sum is always non-negative. Edge cases to consider include arrays where no combination of numbers is divisible by three, arrays with a single element, and arrays where all elements are already divisible by three.
Approaches
A brute-force approach would involve generating all possible subsets of nums, computing the sum of each subset, and checking if it is divisible by three. While this guarantees the correct answer, the time complexity is O(2^n), which is far too slow for the input constraints (up to 40,000 elements).
The key observation for a more optimal solution is based on modular arithmetic. Any number modulo 3 can be 0, 1, or 2. To maximize a sum divisible by three, we can maintain the maximum sums with remainders 0, 1, and 2 while iterating through the array. For each number, we consider adding it to the existing sums for each remainder and update the maximum possible sums for each remainder. This approach leverages dynamic programming and ensures we only store three variables, giving us a linear-time solution.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^n) | O(n) | Generate all subsets, sum them, and pick the maximum divisible by 3 |
| Optimal | O(n) | O(1) | Use dynamic programming with three variables to track max sums modulo 3 |
Algorithm Walkthrough
- Initialize an array
dpof size 3 withdp[0] = 0anddp[1] = dp[2] = -inf. This array will store the maximum sums for remainders 0, 1, and 2. - Iterate through each number
numinnums. - For each number, create a temporary copy of the current
dparray callednew_dp. - For each remainder
rin {0, 1, 2}, calculate a potential new sumdp[r] + num. - Determine the remainder of this new sum modulo 3.
- Update
new_dp[new_remainder]to be the maximum of its current value and the new sum. - After processing all remainders, set
dp = new_dp. - After iterating through all numbers,
dp[0]will contain the maximum sum divisible by three.
Why it works: This works because at each step we consider all possibilities of adding the current number to previously computed sums for each remainder. By keeping only the maximum sum for each remainder, we ensure optimality and avoid redundant calculations. The modulo 3 operation guarantees we track sums divisible by three correctly.
Python Solution
from typing import List
class Solution:
def maxSumDivThree(self, nums: List[int]) -> int:
dp = [0, float('-inf'), float('-inf')]
for num in nums:
new_dp = dp[:]
for r in range(3):
new_sum = dp[r] + num
new_dp[new_sum % 3] = max(new_dp[new_sum % 3], new_sum)
dp = new_dp
return dp[0]
The Python implementation initializes a dp array to track maximum sums for each remainder modulo 3. For each number in nums, it calculates potential new sums and updates the maximum for each remainder. At the end, dp[0] contains the maximum sum divisible by 3. Using float('-inf') ensures we correctly handle sums that cannot be formed for certain remainders initially.
Go Solution
func maxSumDivThree(nums []int) int {
dp := [3]int{0, -1 << 60, -1 << 60}
for _, num := range nums {
newDp := dp
for r := 0; r < 3; r++ {
newSum := dp[r] + num
if newSum > newDp[newSum%3] {
newDp[newSum%3] = newSum
}
}
dp = newDp
}
return dp[0]
}
In Go, we use an array of size 3 to track the maximum sums for each remainder. Negative infinity is represented by -1 << 60 to safely handle large negative sums. The logic mirrors Python's approach but uses Go-specific syntax and slice handling.
Worked Examples
Example 1: nums = [3,6,5,1,8]
| Step | dp[0] | dp[1] | dp[2] |
|---|---|---|---|
| Initial | 0 | -inf | -inf |
| num=3 | 3 | -inf | -inf |
| num=6 | 9 | -inf | -inf |
| num=5 | 9 | 14 | 5 |
| num=1 | 10 | 15 | 9 |
| num=8 | 18 | 23 | 19 |
Return dp[0] = 18.
Example 2: nums = [4]
| Step | dp[0] | dp[1] | dp[2] |
|---|---|---|---|
| Initial | 0 | -inf | -inf |
| num=4 | 0 | 4 | -inf |
Return dp[0] = 0.
Example 3: nums = [1,2,3,4,4]
| Step | dp[0] | dp[1] | dp[2] |
|---|---|---|---|
| Initial | 0 | -inf | -inf |
| num=1 | 0 | 1 | -inf |
| num=2 | 3 | 1 | 2 |
| num=3 | 6 | 4 | 5 |
| num=4 | 10 | 9 | 9 |
| num=4 | 12 | 13 | 13 |
Return dp[0] = 12.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each number is processed once and we update three remainders |
| Space | O(1) | Only three values are stored regardless of input size |
The linear time complexity arises because we iterate through the array once and perform constant work for each element. The space complexity is constant since we only track three maximum sums.
Test Cases
# test cases
assert Solution().maxSumDivThree([3,6,5,1,8]) == 18 # example 1
assert Solution().maxSumDivThree([4]) == 0 # example 2
assert Solution().maxSumDivThree([1,2,3,4,4]) == 12 # example 3
assert Solution().maxSumDivThree([1,1,1,1]) == 3 # all ones
assert Solution().maxSumDivThree([3,3,3]) == 9 # all divisible by 3
assert Solution().maxSumDivThree([1,2]) == 0 # cannot form divisible by 3
assert Solution().maxSumDivThree([1,2,2,2,2]) == 9 # mix to reach max divisible
| Test | Why |
|---|---|
[3,6,5,1,8] |
Typical case with multiple choices |
[4] |
Single element not divisible by 3 |
[1,2,3,4,4] |
Mix of numbers with different remainders |
[1,1,1,1] |
All numbers same remainder 1 |
[3,3,3] |
All numbers divisible by 3 |
[1,2] |
Cannot form sum divisible by 3 |
[1,2,2,2,2] |
Testing combination for maximum sum |
Edge Cases
One important edge case is when the array has only one element. If this element is divisible by 3, the result is the element itself; otherwise, it is zero. Another edge case occurs when all elements have the same remainder modulo 3. The algorithm correctly combines or skips elements to form sums divisible by 3, leveraging the dynamic programming update. A third edge case involves large arrays with high numbers close to 10,000. The algorithm handles this efficiently because it only maintains three sums and avoids building any large subsets or storing all possible sums.