343. Integer Break
Problem Description
You are given a positive integer n
. Your task is to break it into the sum of at least 2 positive integers and maximize the product of those integers.
For example, if n = 10
, you could break it into:
2 + 8
with product2 × 8 = 16
3 + 7
with product3 × 7 = 21
2 + 3 + 5
with product2 × 3 × 5 = 30
3 + 3 + 4
with product3 × 3 × 4 = 36
Among all possible ways to break n
, you need to find the maximum possible product.
The solution uses dynamic programming where f[i]
represents the maximum product obtainable by breaking integer i
. For each number i
from 2
to n
, we try all possible ways to split it into two parts: j
and i-j
where j
ranges from 1
to i-1
.
For each split, we have two choices:
- Don't further break
i-j
, giving us product(i-j) × j
- Further break
i-j
optimally, giving us productf[i-j] × j
We take the maximum among these options and the current value of f[i]
:
f[i] = max(f[i], f[i-j] × j, (i-j) × j)
The final answer is f[n]
, which gives the maximum product for breaking integer n
.
Intuition
The key insight is that when we break a number n
, we're essentially making a decision about how to split it optimally. This naturally leads us to think about dynamic programming since the optimal solution for n
depends on optimal solutions for smaller numbers.
Let's think about breaking a number i
. We can split it into two parts: some number j
and the remainder i-j
. Now here's the crucial observation - once we take out j
, we have two choices for the remaining part i-j
:
- Keep
i-j
as is (don't break it further) - Break
i-j
optimally (which we might have already computed for smaller values)
Why these two choices? Because sometimes it's better to keep a number whole rather than breaking it. For instance, if we have the number 4
, breaking it into 2 + 2
gives us a product of 4
, but keeping it as 1 + 3
and not breaking the 3
gives us 1 × 3 = 3
. However, breaking 4
into 2 + 2
gives the maximum product of 4
.
This recursive nature suggests we can build up our solution from smaller numbers to larger ones. For each number i
, we try all possible "last splits" (taking out j
where j
goes from 1
to i-1
), and for each split, we consider both options: further breaking or not breaking the remainder.
The formula f[i] = max(f[i], f[i-j] × j, (i-j) × j)
captures this perfectly:
f[i-j] × j
: the product when we optimally breaki-j
and multiply byj
(i-j) × j
: the product when we don't breaki-j
further
By trying all possible values of j
and taking the maximum, we ensure we find the optimal way to break each number, building up from smaller values to finally reach our answer for n
.
Solution Approach
We implement the solution using dynamic programming with a bottom-up approach. Here's how the algorithm works:
Data Structure:
- We create an array
f
of sizen + 1
wheref[i]
stores the maximum product obtainable by breaking integeri
- Initialize all values to
1
since the minimum product for any positive integer is1
Algorithm Steps:
-
Base Case:
f[1] = 1
(already set during initialization) -
Iteration: For each integer
i
from2
ton
:- Try all possible ways to split
i
into two parts - For each possible split position
j
from1
toi-1
:- Calculate the product when we split
i
intoj
andi-j
- We have two options for the remainder
i-j
:- Don't break it further: product =
(i - j) × j
- Break it optimally: product =
f[i - j] × j
- Don't break it further: product =
- Update
f[i]
with the maximum of current value and both options
- Calculate the product when we split
- Try all possible ways to split
-
State Transition Equation:
f[i] = max(f[i], f[i - j] × j, (i - j) × j)
where
j ∈ [1, i)
Implementation Details:
f = [1] * (n + 1) # Initialize DP array
for i in range(2, n + 1): # Build up from 2 to n
for j in range(1, i): # Try all split positions
f[i] = max(f[i], f[i - j] * j, (i - j) * j)
return f[n] # Return the maximum product for n
Time Complexity: O(n²)
- We have two nested loops, outer loop runs n-1
times and inner loop runs up to i-1
times.
Space Complexity: O(n)
- We use an array of size n + 1
to store the DP values.
The algorithm ensures we find the globally optimal solution by considering all possible ways to break each number and building upon previously computed optimal solutions for smaller numbers.
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 algorithm with n = 6
to see how we find the maximum product.
Initialization:
- Create array
f = [1, 1, 1, 1, 1, 1, 1]
(indices 0 to 6) f[i]
will store the maximum product for integeri
Building the DP table:
i = 2:
- j = 1: Split 2 into 1 and 1
- Don't break 1:
(2-1) × 1 = 1 × 1 = 1
- Break 1 optimally:
f[1] × 1 = 1 × 1 = 1
f[2] = max(1, 1, 1) = 1
- Don't break 1:
i = 3:
- j = 1: Split 3 into 1 and 2
- Don't break 2:
(3-1) × 1 = 2 × 1 = 2
- Break 2 optimally:
f[2] × 1 = 1 × 1 = 1
f[3] = max(1, 2, 1) = 2
- Don't break 2:
- j = 2: Split 3 into 2 and 1
- Don't break 1:
(3-2) × 2 = 1 × 2 = 2
- Break 1 optimally:
f[1] × 2 = 1 × 2 = 2
f[3] = max(2, 2, 2) = 2
- Don't break 1:
i = 4:
- j = 1: Split 4 into 1 and 3
- Don't break 3:
(4-1) × 1 = 3 × 1 = 3
- Break 3 optimally:
f[3] × 1 = 2 × 1 = 2
f[4] = max(1, 3, 2) = 3
- Don't break 3:
- j = 2: Split 4 into 2 and 2
- Don't break 2:
(4-2) × 2 = 2 × 2 = 4
- Break 2 optimally:
f[2] × 2 = 1 × 2 = 2
f[4] = max(3, 4, 2) = 4
- Don't break 2:
- j = 3: Split 4 into 3 and 1
- Don't break 1:
(4-3) × 3 = 1 × 3 = 3
- Break 1 optimally:
f[1] × 3 = 1 × 3 = 3
f[4] = max(4, 3, 3) = 4
- Don't break 1:
i = 5:
- j = 1:
f[5] = max(1, 4, 3) = 4
- j = 2:
f[5] = max(4, 6, 4) = 6
- j = 3:
f[5] = max(6, 6, 4) = 6
- j = 4:
f[5] = max(6, 4, 3) = 6
i = 6:
- j = 1:
f[6] = max(1, 5, 6) = 6
- j = 2:
f[6] = max(6, 8, 6) = 8
- j = 3:
f[6] = max(8, 9, 6) = 9
- j = 4:
f[6] = max(9, 8, 6) = 9
- j = 5:
f[6] = max(9, 6, 4) = 9
Final DP array: f = [1, 1, 1, 2, 4, 6, 9]
Answer: f[6] = 9
This corresponds to breaking 6 into 3 + 3
, which gives product 3 × 3 = 9
. The algorithm found this by trying the split at j = 3, where we get (6-3) × 3 = 3 × 3 = 9
.
Solution Implementation
1class Solution:
2 def integerBreak(self, n: int) -> int:
3 # dp[i] represents the maximum product when breaking integer i
4 dp = [1] * (n + 1)
5
6 # Build up the solution for each number from 2 to n
7 for num in range(2, n + 1):
8 # Try breaking num into two parts: j and (num - j)
9 for j in range(1, num):
10 # For each split, we have two choices:
11 # 1. Keep (num - j) as is and multiply by j: (num - j) * j
12 # 2. Further break (num - j) and multiply by j: dp[num - j] * j
13 # Take the maximum among current value and both choices
14 dp[num] = max(
15 dp[num], # Current maximum
16 dp[num - j] * j, # Break (num - j) further
17 (num - j) * j # Keep (num - j) as is
18 )
19
20 # Return the maximum product for breaking n
21 return dp[n]
22
1class Solution {
2 public int integerBreak(int n) {
3 // dp[i] represents the maximum product we can get by breaking integer i
4 int[] dp = new int[n + 1];
5
6 // Base case: when n=1, the maximum product is 1
7 dp[1] = 1;
8
9 // Fill the dp array for each number from 2 to n
10 for (int i = 2; i <= n; ++i) {
11 // Try all possible ways to split number i into two parts: j and (i-j)
12 for (int j = 1; j < i; ++j) {
13 // For each split, we have two choices:
14 // 1. Don't break (i-j) further: product = j * (i-j)
15 // 2. Break (i-j) further: product = j * dp[i-j]
16 // Take the maximum of current value and both choices
17 dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
18 }
19 }
20
21 // Return the maximum product for integer n
22 return dp[n];
23 }
24}
25
1class Solution {
2public:
3 int integerBreak(int n) {
4 // dp[i] represents the maximum product when breaking integer i
5 vector<int> dp(n + 1);
6
7 // Base case: when n=1, the maximum product is 1
8 dp[1] = 1;
9
10 // Fill the dp table for each integer from 2 to n
11 for (int i = 2; i <= n; ++i) {
12 // Try all possible ways to break integer i
13 for (int j = 1; j < i; ++j) {
14 // For integer i, we can break it into j and (i-j)
15 // We have three choices:
16 // 1. dp[i]: keep the current maximum product
17 // 2. dp[i-j] * j: break (i-j) further and multiply by j
18 // 3. (i-j) * j: don't break (i-j) further, directly multiply
19 dp[i] = max({dp[i], dp[i - j] * j, (i - j) * j});
20 }
21 }
22
23 // Return the maximum product for integer n
24 return dp[n];
25 }
26};
27
1/**
2 * Given an integer n, breaks it into the sum of k positive integers (k >= 2)
3 * and maximizes the product of those integers.
4 * Returns the maximum product obtainable.
5 *
6 * @param n - The integer to break (2 <= n <= 58)
7 * @return The maximum product of the broken integers
8 */
9function integerBreak(n: number): number {
10 // Dynamic programming array where dp[i] represents the maximum product
11 // obtainable by breaking integer i into at least two positive integers
12 const dp: number[] = Array(n + 1).fill(1);
13
14 // Start from 3 since breaking 1 or 2 doesn't yield a larger product
15 for (let i = 3; i <= n; i++) {
16 // Try breaking i into j and (i - j) where j ranges from 1 to i-1
17 for (let j = 1; j < i; j++) {
18 // For each split, we have three options:
19 // 1. Keep the current maximum product for i
20 // 2. Don't break (i - j) further: product = j * (i - j)
21 // 3. Break (i - j) further: product = j * dp[i - j]
22 dp[i] = Math.max(
23 dp[i], // Current maximum
24 j * (i - j), // Don't break the second part
25 j * dp[i - j] // Break the second part further
26 );
27 }
28 }
29
30 return dp[n];
31}
32
Time and Space Complexity
The time complexity is O(n²)
because there are two nested loops: the outer loop runs from 2 to n (which is n-1
iterations), and for each iteration i
, the inner loop runs from 1 to i-1
(which is up to i-1
iterations). The total number of operations is approximately 1 + 2 + 3 + ... + (n-1) = (n-1) × n / 2
, which simplifies to O(n²)
.
The space complexity is O(n)
because the algorithm uses a single array f
of size n+1
to store the dynamic programming results. No additional space that scales with input size is used beyond this array.
Common Pitfalls
Pitfall 1: Forgetting to Consider Not Breaking Further
The Problem:
A common mistake is only considering dp[num - j] * j
in the state transition, forgetting that sometimes it's better to keep (num - j)
as a single number rather than breaking it further.
Incorrect Code:
for num in range(2, n + 1):
for j in range(1, num):
dp[num] = max(dp[num], dp[num - j] * j) # Missing (num - j) * j option
Why This Fails:
For n = 4
, the optimal break is 2 + 2
with product 4
. However:
- When computing
dp[4]
withj = 2
:- We need
(4 - 2) * 2 = 2 * 2 = 4
- But
dp[2] * 2 = 1 * 2 = 2
(since breaking 2 gives 1+1 with product 1)
- We need
- Without the
(num - j) * j
option, we'd get the wrong answer
Solution: Always include both options in the max comparison:
dp[num] = max(dp[num], dp[num - j] * j, (num - j) * j)
Pitfall 2: Incorrect Loop Bounds
The Problem:
Using incorrect loop bounds, especially making j
go up to num
instead of num - 1
.
Incorrect Code:
for j in range(1, num + 1): # Wrong: j shouldn't equal num
dp[num] = max(dp[num], dp[num - j] * j, (num - j) * j)
Why This Fails:
When j = num
, we get num - j = 0
, which means:
- We're not actually breaking the number (violating the "at least 2 parts" requirement)
- Accessing
dp[0]
which represents breaking 0, which is meaningless - The product would be
0 * num = 0
Solution:
Ensure j
ranges from 1
to num - 1
:
for j in range(1, num): # Correct: j goes from 1 to num-1
Pitfall 3: Wrong Initialization Value
The Problem: Initializing the dp array with 0 instead of 1, or not initializing at all.
Incorrect Code:
dp = [0] * (n + 1) # Wrong initialization
Why This Fails:
- With 0 initialization, all calculations involving
dp[num - j] * j
would result in 0 - The algorithm would never update from 0, leading to incorrect results
- For small numbers like
n = 2
, the answer should be 1 (1 + 1), not 0
Solution: Initialize with 1, which represents the minimum valid product:
dp = [1] * (n + 1) # Correct initialization
Pitfall 4: Mathematical Optimization Trap
The Problem:
Trying to optimize by only considering splits near the middle (e.g., j
from num//2
to num-1
), thinking symmetric splits are always optimal.
Incorrect Code:
for j in range(num // 2, num): # Wrong: missing smaller j values
dp[num] = max(dp[num], dp[num - j] * j, (num - j) * j)
Why This Fails: For larger numbers, the optimal solution often involves breaking into multiple 3's (and possibly a 2 or 4). For example:
n = 10
: Optimal is3 + 3 + 4 = 3 * 3 * 4 = 36
- But if we only check splits near the middle, we might miss the pattern of using multiple 3's
Solution: Always check all possible splits from 1 to num-1 to ensure correctness:
for j in range(1, num): # Check all possible splits
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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
Want a Structured Path to Master System Design Too? Don’t Miss This!