Given are N boards of with length of each given in the form of array, and K painters, such that each painter takes 1 unit of time to paint 1 unit of the board. The task is to find the minimum time to paint all boards under the constraints that any painter will only paint continuous sections of boards, say board {2, 3, 4} or only board {1} or nothing but not board {2, 4, 5}.
Examples:
Input: N = 4, A = {10, 10, 10, 10}, K = 2
Output : 20
Explanation: Here we can divide the boards into 2 equal sized partitions (Painter 1 will paint boards A1 and A2, and Painter 2 will paint boards A3 and A4). So each painter gets 20 units of board and the total time taken is 20.
Input: N = 4, A = {10, 20, 30, 40}, K = 2
Output : 60
Explanation: Since there are only 2 painters, therefore divide first 3 boards to painter 1 (A1, A2 and A3) with time = 60, and last board to painter 2 (A4) with time = 40. Therefore total time taken = 60, which is the minimum possible.
Please note the combination A1 and A4 to Painter 1 with time 50, and A2 and A3 to Painter 2 with time 50, will yield a smaller time (50 units). But this cant be considered due to the constraint that a painter cannot paint non-continuos series of boards.
Naive Approach for Painter’s Problem:
A brute force solution is to consider all possible sets of contiguous partitions and calculate the maximum sum partition in each case and return the minimum of all these cases.
Dynamic Programming Approach for Painter’s Problem
The above approach can be further optimised using Dynamic Programming approach.
From the above examples, it is obvious that the strategy of dividing the boards into k equal partitions won’t work for all cases. We can observe that the problem can be broken down into: Given an array A of non-negative integers and a positive integer k, we have to divide A into k of fewer partitions such that the maximum sum of the elements in a partition, overall partitions is minimized. So for the second example above, possible divisions are:
- One partition: so time is 100.
- Two partitions: (10) & (20, 30, 40), so time is 90. Similarly, we can put the first divider
after 20 (=> time 70) or 30 (=> time 60); so this means the minimum time: (100, 90, 70, 60) is 60.
Optimal Substructure Approach for Painter’s Problem using DP
We can implement the naive solution using recursion with the following optimal substructure property:
- Assuming that we already have k-1 partitions in place (using k-2 dividers), we now have to put the k-1 th divider to get k partitions.
- We can put the k-1 th divider between the i th and i+1 th element where i = 1 to n.
- Please note that putting it before the first element is the same as putting it after the last element.
- The total cost of this arrangement can be calculated as the maximum of the following:
- The cost of the last partition: sum(Ai…..An), where the k-1 th divider is before element i.
- This can be found out using a simple helper function to calculate sum of elements between two indices in the array.
- The maximum cost of any partition already formed to the left of the k-1 th divider.
- We can observe that this is actually to place the k-2 separators as fairly as possible, so it is a subproblem of the given problem.
- Thus we can write the optimal substructure property as the following recurrence relation:

Following is the implementation of the above recursive equation:
C++
#include <climits>
#include <iostream>
using namespace std;
int sum( int arr[], int from, int to)
{
int total = 0;
for ( int i = from; i <= to; i++)
total += arr[i];
return total;
}
int partition( int arr[], int n, int k)
{
if (k == 1)
return sum(arr, 0, n - 1);
if (n == 1)
return arr[0];
int best = INT_MAX;
for ( int i = 1; i <= n; i++)
best = min(best, max(partition(arr, i, k - 1),
sum(arr, i, n - 1)));
return best;
}
int main()
{
int arr[] = { 10, 20, 60, 50, 30, 40 };
int n = sizeof (arr) / sizeof (arr[0]);
int k = 3;
cout << partition(arr, n, k) << endl;
return 0;
}
|
Java
import java.io.*;
import java.util.*;
class GFG {
static int sum( int arr[], int from, int to)
{
int total = 0 ;
for ( int i = from; i <= to; i++)
total += arr[i];
return total;
}
static int partition( int arr[], int n, int k)
{
if (k == 1 )
return sum(arr, 0 , n - 1 );
if (n == 1 )
return arr[ 0 ];
int best = Integer.MAX_VALUE;
for ( int i = 1 ; i <= n; i++)
best = Math.min(
best, Math.max(partition(arr, i, k - 1 ),
sum(arr, i, n - 1 )));
return best;
}
public static void main(String args[])
{
int arr[] = { 10 , 20 , 60 , 50 , 30 , 40 };
int n = arr.length;
int k = 3 ;
System.out.println(partition(arr, n, k));
}
}
|
Python3
def sum (arr, frm, to):
total = 0
for i in range (frm, to + 1 ):
total + = arr[i]
return total
def partition(arr, n, k):
if k = = 1 :
return sum (arr, 0 , n - 1 )
if n = = 1 :
return arr[ 0 ]
best = 100000000
for i in range ( 1 , n + 1 ):
best = min (best,
max (partition(arr, i, k - 1 ),
sum (arr, i, n - 1 )))
return best
arr = [ 10 , 20 , 60 , 50 , 30 , 40 ]
n = len (arr)
k = 3
print (partition(arr, n, k))
|
C#
using System;
class GFG {
static int sum( int [] arr, int from , int to)
{
int total = 0;
for ( int i = from ; i <= to; i++)
total += arr[i];
return total;
}
static int partition( int [] arr, int n, int k)
{
if (k == 1)
return sum(arr, 0, n - 1);
if (n == 1)
return arr[0];
int best = int .MaxValue;
for ( int i = 1; i <= n; i++)
best = Math.Min(
best, Math.Max(partition(arr, i, k - 1),
sum(arr, i, n - 1)));
return best;
}
public static void Main()
{
int [] arr = { 10, 20, 60, 50, 30, 40 };
int n = arr.Length;
int k = 3;
Console.WriteLine(partition(arr, n, k));
}
}
|
PHP
<?php
function sum( $arr , $from , $to )
{
$total = 0;
for ( $i = $from ; $i <= $to ; $i ++)
$total += $arr [ $i ];
return $total ;
}
function partition( $arr , $n , $k )
{
if ( $k == 1)
return sum( $arr , 0, $n - 1);
if ( $n == 1)
return $arr [0];
$best = PHP_INT_MAX;
for ( $i = 1; $i <= $n ; $i ++)
$best = min( $best ,
max(partition( $arr , $i , $k - 1),
sum( $arr , $i , $n - 1)));
return $best ;
}
$arr = array (10, 20, 60,
50, 30, 40);
$n = sizeof( $arr );
$k = 3;
echo partition( $arr , $n , $k ), "\n" ;
?>
|
Javascript
<script>
function sum(arr, from, to)
{
let total = 0;
for (let i = from; i <= to; i++)
total += arr[i];
return total;
}
function partition(arr, n, k)
{
if (k == 1)
return sum(arr, 0, n - 1);
if (n == 1)
return arr[0];
let best = Number.MAX_VALUE;
for (let i = 1; i <= n; i++)
best = Math.min(best,
Math.max(partition(arr, i, k - 1),
sum(arr, i, n - 1)));
return best;
}
let arr = [ 10, 20, 60, 50, 30, 40 ];
let n = arr.length;
let k = 3;
document.write(partition(arr, n, k));
</script>
|
Time complexity: The time complexity of the above solution is exponential.
Auxiliary Space: O(1)
Overlapping sub-problems Approach for Painter’s Problem using DP
Following is the partial recursion tree for T(4, 3) in the above equation.
T(4, 3)
/ / \ ..
T(1, 2) T(2, 2) T(3, 2)
/.. /..
T(1, 1) T(1, 1)
We can observe that many subproblems like T(1, 1) in the above problem are being solved again and again. Because of these two properties of this problem, we can solve it using dynamic programming, either by top-down memoized method or bottom-up tabular method.
Following is the implementation of Bottom-Up Tabular approach for Painter’s Problem using DP:
C++
#include <climits>
#include <iostream>
using namespace std;
int sum( int arr[], int from, int to)
{
int total = 0;
for ( int i = from; i <= to; i++)
total += arr[i];
return total;
}
int findMax( int arr[], int n, int k)
{
int dp[k + 1][n + 1] = { 0 };
for ( int i = 1; i <= n; i++)
dp[1][i] = sum(arr, 0, i - 1);
for ( int i = 1; i <= k; i++)
dp[i][1] = arr[0];
for ( int i = 2; i <= k; i++) {
for ( int j = 2; j <= n; j++) {
int best = INT_MAX;
for ( int p = 1; p <= j; p++)
best = min(best, max(dp[i - 1][p],
sum(arr, p, j - 1)));
dp[i][j] = best;
}
}
return dp[k][n];
}
int main()
{
int arr[] = { 10, 20, 60, 50, 30, 40 };
int n = sizeof (arr) / sizeof (arr[0]);
int k = 3;
cout << findMax(arr, n, k) << endl;
return 0;
}
|
Java
import java.io.*;
import java.util.*;
class GFG {
static int sum( int arr[], int from, int to)
{
int total = 0 ;
for ( int i = from; i <= to; i++)
total += arr[i];
return total;
}
static int findMax( int arr[], int n, int k)
{
int dp[][] = new int [k + 1 ][n + 1 ];
for ( int i = 1 ; i <= n; i++)
dp[ 1 ][i] = sum(arr, 0 , i - 1 );
for ( int i = 1 ; i <= k; i++)
dp[i][ 1 ] = arr[ 0 ];
for ( int i = 2 ; i <= k; i++) {
for ( int j = 2 ; j <= n; j++) {
int best = Integer.MAX_VALUE;
for ( int p = 1 ; p <= j; p++)
best = Math.min(
best, Math.max(dp[i - 1 ][p],
sum(arr, p, j - 1 )));
dp[i][j] = best;
}
}
return dp[k][n];
}
public static void main(String args[])
{
int arr[] = { 10 , 20 , 60 , 50 , 30 , 40 };
int n = arr.length;
int k = 3 ;
System.out.println(findMax(arr, n, k));
}
}
|
Python3
def sum (arr, start, to):
total = 0
for i in range (start, to + 1 ):
total + = arr[i]
return total
def findMax(arr, n, k):
dp = [[ 0 for i in range (n + 1 )]
for j in range (k + 1 )]
for i in range ( 1 , n + 1 ):
dp[ 1 ][i] = sum (arr, 0 , i - 1 )
for i in range ( 1 , k + 1 ):
dp[i][ 1 ] = arr[ 0 ]
for i in range ( 2 , k + 1 ):
for j in range ( 2 , n + 1 ):
best = 100000000
for p in range ( 1 , j + 1 ):
best = min (best, max (dp[i - 1 ][p],
sum (arr, p, j - 1 )))
dp[i][j] = best
return dp[k][n]
arr = [ 10 , 20 , 60 , 50 , 30 , 40 ]
n = len (arr)
k = 3
print (findMax(arr, n, k))
|
C#
using System;
class GFG {
static int sum( int [] arr, int from , int to)
{
int total = 0;
for ( int i = from ; i <= to; i++)
total += arr[i];
return total;
}
static int findMax( int [] arr, int n, int k)
{
int [, ] dp = new int [k + 1, n + 1];
for ( int i = 1; i <= n; i++)
dp[1, i] = sum(arr, 0, i - 1);
for ( int i = 1; i <= k; i++)
dp[i, 1] = arr[0];
for ( int i = 2; i <= k; i++) {
for ( int j = 2; j <= n; j++) {
int best = int .MaxValue;
for ( int p = 1; p <= j; p++)
best = Math.Min(
best, Math.Max(dp[i - 1, p],
sum(arr, p, j - 1)));
dp[i, j] = best;
}
}
return dp[k, n];
}
public static void Main()
{
int [] arr = { 10, 20, 60, 50, 30, 40 };
int n = arr.Length;
int k = 3;
Console.WriteLine(findMax(arr, n, k));
}
}
|
PHP
<?php
function sum( $arr , $from , $to )
{
$total = 0;
for ( $i = $from ; $i <= $to ; $i ++)
$total += $arr [ $i ];
return $total ;
}
function findMax( $arr , $n , $k )
{
$dp [ $k + 1][ $n + 1] = array ( 0 );
for ( $i = 1; $i <= $n ; $i ++)
$dp [1][ $i ] = sum( $arr , 0,
$i - 1);
for ( $i = 1; $i <= $k ; $i ++)
$dp [ $i ][1] = $arr [0];
for ( $i = 2; $i <= $k ; $i ++)
{
for ( $j = 2; $j <= $n ; $j ++)
{
$best = PHP_INT_MAX;
for ( $p = 1; $p <= $j ; $p ++)
$best = min( $best , max( $dp [ $i - 1][ $p ],
sum( $arr , $p , $j - 1)));
$dp [ $i ][ $j ] = $best ;
}
}
return $dp [ $k ][ $n ];
}
$arr = array (10, 20, 60,
50, 30, 40 );
$n = sizeof( $arr );
$k = 3;
echo findMax( $arr , $n , $k ) , "\n" ;
?>
|
Javascript
<script>
function sum(arr, from, to)
{
let total = 0;
for (let i = from; i <= to; i++)
total += arr[i];
return total;
}
function findMax(arr, n, k)
{
let dp = new Array(k+1);
for (let i = 0; i < k + 1; i++)
{
dp[i] = new Array(n + 1);
}
for (let i = 1; i <= n; i++)
dp[1][i] = sum(arr, 0, i - 1);
for (let i = 1; i <= k; i++)
dp[i][1] = arr[0];
for (let i = 2; i <= k; i++) {
for (let j = 2; j <= n; j++) {
let best = Number.MAX_VALUE;
for (let p = 1; p <= j; p++)
best = Math.min(best, Math.max(dp[i - 1][p],
sum(arr, p, j - 1)));
dp[i][j] = best;
}
}
return dp[k][n];
}
let arr = [10, 20, 60, 50, 30, 40];
let n = arr.length;
let k = 3;
document.write(findMax(arr, n, k));
</script>
|
Time complexity: O(k∗N3)
Auxiliary Space: O(k*N)
Optimising Dynamic Programming Approach for Painter’s Problem using Precomputation
The time complexity of the above program is O(k∗N3). It can be easily brought down to O(k∗N2) by pre-computing the cumulative sums in an array, thus avoiding repeated calls to the sum function.
Below is the how we can achieve this optimisation:
C++
int sum[n + 1] = { 0 };
for ( int i = 1; i <= n; i++)
sum[i] = sum[i - 1] + arr[i - 1];
for ( int i = 1; i <= n; i++)
dp[1][i] = sum[i];
best = min(best, max(dp[i - 1][p], sum[j] - sum[p]));
|
Java
int sum[] = new int [n + 1 ];
for ( int i = 1 ; i <= n; i++)
sum[i] = sum[i - 1 ] + arr[i - 1 ];
for ( int i = 1 ; i <= n; i++)
dp[ 1 ][i] = sum[i];
best = Math.min(best,
Math.max(dp[i - 1 ][p], sum[j] - sum[p]));
|
Python3
sum = [ 0 ] * (n + 1 )
for i in range ( 1 , n + 1 ):
sum [i] = sum [i - 1 ] + arr[i - 1 ]
for i in range ( 1 , n + 1 ):
dp[ 1 ][i] = sum [i]
best = min (best, max (dp[i - 1 ][p], sum [j] - sum [p]))
|
C#
int [] sum = new int [n + 1];
for ( int i = 1; i <= n; i++)
sum[i] = sum[i - 1] + arr[i - 1];
for ( int i = 1; i <= n; i++)
dp[1, i] = sum[i];
best = Math.Min(best,
Math.Max(dp[i - 1][p], sum[j] - sum[p]));
|
Javascript
<script>
let sum = new Array(n+1);
for (let i = 1; i <= n; i++)
sum[i] = sum[i-1] + arr[i-1];
for (let i = 1; i <= n; i++)
dp[1][i] = sum[i];
best = Math.min(best, Math.max(dp[i-1][p], sum[j] - sum[p]));
</script>
|
Note: Though here we consider dividing A into k or fewer partitions, we can observe that the optimal case always occurs when we divide A into exactly k partitions.
So we can use:
C++
for ( int i = k - 1; i <= n; i++)
best = min(best, max(partition(arr, i, k - 1),
sum(arr, i, n - 1)));
|
Java
for ( int i = k - 1 ; i <= n; i++)
best = Math.min(best, Math.max(partition(arr, i, k - 1 ),
sum(arr, i, n - 1 )));
|
Python3
for i range (k - 1 , n + 1 ):
best = min (best, max (partition(arr, i, k - 1 ), sum (arr, i, n - 1 )))
|
C#
for ( int i = k - 1; i <= n; i++)
best = Math.Min(best, Math.Max(partition(arr, i, k - 1),
sum(arr, i, n - 1)));
|
Javascript
for ( var i = k - 1; i <= n; i++)
best = Math.min(best, Math.max(partition(arr, i, k - 1),
sum(arr, i, n - 1)));
|
Exercise:
Can you come up with a solution to this Painter’s Problem using Binary Search?
Feeling lost in the world of random DSA topics, wasting time without progress? It's time for a change! Join our DSA course, where we'll guide you on an exciting journey to master DSA efficiently and on schedule.
Ready to dive in? Explore our Free Demo Content and join our DSA course, trusted by over 100,000 geeks!
Last Updated :
09 Jun, 2023
Like Article
Save Article