Given an array arr[] and an integer k, partition the array into exactly k non-empty contiguous subarrays such that the GCD of their sums is maximized. Return the maximum possible GCD.
Examples:
Input: k = 4, arr[] = [6, 7, 5, 27, 3]
Output: 3
Explanation: Since k = 4, you need to split the array into 4 subarrays. For optimal splitting, split the array into 4 subarrays as follows: [[6], [7, 5], [27], [3]]. Therefore, s1 = 6, s2 = 7 + 5 = 12, s3 = 27, s4 = 3. Hence, GCD(s1, s2, s3, s4) = GCD(6, 12, 27, 3) = 3 which is the maximum value of GCD that can be obtained.Input: k = 2, arr[] = [1, 4, 5]
Output: 5
Explanation: Since k = 2, you need to split the array into 2 subarrays. For optimal splitting, split the array into 2 subarrays as follows: [[1, 4], [5]]. Therefore, s1 = 1 + 4 = 5, s2 = 5. Hence, GCD(s1, s2) = GCD(5, 5) = 5 which is the maximum value of GCD that can be obtained.
Table of Content
[Naive Approach] Brute Force Approach
The idea is to try every possible way to place the k - 1 cuts, since each unique set of cut positions defines a valid partition of the array into exactly k contiguous subarrays. Use prefix sums to compute each subarray sum in O(1) time and recursively maintain the GCD of the subarray sums, updating the maximum GCD obtained over all possible partitions.
- Compute the prefix sum array of the given array.
- Start from index 0 with k - 1 cuts remaining and an initial GCD of 0.
- Recursively place the next cut at every valid position to generate all possible partitions.
- For each chosen cut, compute the current subarray sum using prefix sums and update the running GCD.
- When no cuts are left, form the last subarray, update the final GCD, and maximize the answer.
- Return the maximum GCD obtained over all possible partitions.
#include <iostream>
using namespace std;
// Recursively try all possible partitions
void backtrack(int idx, int cutsLeft, vector<int> &prefix, int currGCD, int &ans)
{
int n = prefix.size() - 1;
// No more cuts left, form the last subarray
if (cutsLeft == 0)
{
int lastSum = prefix[n] - prefix[idx];
currGCD = (currGCD == 0) ? lastSum : gcd(currGCD, lastSum);
ans = max(ans, currGCD);
return;
}
// Try placing the next cut at every possible position
for (int i = idx + 1; i <= n - cutsLeft; i++)
{
int segmentSum = prefix[i] - prefix[idx];
int newGCD = (currGCD == 0) ? segmentSum : gcd(currGCD, segmentSum);
backtrack(i, cutsLeft - 1, prefix, newGCD, ans);
}
}
int solve(int k, vector<int> &arr)
{
int n = arr.size();
// Compute prefix sums for O(1) subarray sum queries
vector<int> prefix(n + 1, 0);
for (int i = 0; i < n; i++)
prefix[i + 1] = prefix[i] + arr[i];
int ans = 0;
// Start partitioning from index 0
backtrack(0, k - 1, prefix, 0, ans);
return ans;
}
int main()
{
int k = 4;
vector<int> arr = {6, 7, 5, 27, 3};
// Print the maximum possible GCD
cout << solve(k, arr) << endl;
return 0;
}
import java.util.*;
public class GFG {
// Recursively try all possible partitions
static void backtrack(int idx, int cutsLeft,
int[] prefix, int currGCD,
int[] ans)
{
int n = prefix.length - 1;
// No more cuts left, form the last subarray
if (cutsLeft == 0) {
int lastSum = prefix[n] - prefix[idx];
currGCD = (currGCD == 0)
? lastSum
: gcd(currGCD, lastSum);
ans[0] = Math.max(ans[0], currGCD);
return;
}
// Try placing the next cut at every possible
// position
for (int i = idx + 1; i <= n - cutsLeft; i++) {
int segmentSum = prefix[i] - prefix[idx];
int newGCD = (currGCD == 0)
? segmentSum
: gcd(currGCD, segmentSum);
backtrack(i, cutsLeft - 1, prefix, newGCD, ans);
}
}
static int solve(int k, int[] arr)
{
int n = arr.length;
// Compute prefix sums for O(1) subarray sum queries
int[] prefix = new int[n + 1];
for (int i = 0; i < n; i++)
prefix[i + 1] = prefix[i] + arr[i];
int[] ans = { 0 };
// Start partitioning from index 0
backtrack(0, k - 1, prefix, 0, ans);
return ans[0];
}
static int gcd(int a, int b)
{
return (b == 0) ? a : gcd(b, a % b);
}
public static void main(String[] args)
{
int k = 4;
int[] arr = { 6, 7, 5, 27, 3 };
// Print the maximum possible GCD
System.out.println(solve(k, arr));
}
}
from math import gcd
# Recursively try all possible partitions
def backtrack(idx, cuts_left, prefix, curr_gcd, ans):
n = len(prefix) - 1
# No more cuts left, form the last subarray
if cuts_left == 0:
last_sum = prefix[n] - prefix[idx]
curr_gcd = last_sum if curr_gcd == 0 else gcd(curr_gcd, last_sum)
ans[0] = max(ans[0], curr_gcd)
return
# Try placing the next cut at every possible position
for i in range(idx + 1, n - cuts_left + 1):
segment_sum = prefix[i] - prefix[idx]
new_gcd = segment_sum if curr_gcd == 0 else gcd(curr_gcd, segment_sum)
backtrack(i, cuts_left - 1, prefix, new_gcd, ans)
def solve(k, arr):
n = len(arr)
# Compute prefix sums for O(1) subarray sum queries
prefix = [0] * (n + 1)
for i in range(n):
prefix[i + 1] = prefix[i] + arr[i]
ans = [0]
# Start partitioning from index 0
backtrack(0, k - 1, prefix, 0, ans)
return ans[0]
# Driver Code
if __name__ == "__main__":
k = 4
arr = [6, 7, 5, 27, 3]
# Print the maximum possible GCD
print(solve(k, arr))
using System;
class GFG {
// Recursively try all possible partitions
static void Backtrack(int idx, int cutsLeft,
int[] prefix, int currGCD,
ref int ans)
{
int n = prefix.Length - 1;
// No more cuts left, form the last subarray
if (cutsLeft == 0) {
int lastSum = prefix[n] - prefix[idx];
currGCD = (currGCD == 0)
? lastSum
: GCD(currGCD, lastSum);
ans = Math.Max(ans, currGCD);
return;
}
// Try placing the next cut at every possible
// position
for (int i = idx + 1; i <= n - cutsLeft; i++) {
int segmentSum = prefix[i] - prefix[idx];
int newGCD = (currGCD == 0)
? segmentSum
: GCD(currGCD, segmentSum);
Backtrack(i, cutsLeft - 1, prefix, newGCD,
ref ans);
}
}
static int solve(int k, int[] arr)
{
int n = arr.Length;
// Compute prefix sums for O(1) subarray sum
// queries
int[] prefix = new int[n + 1];
for (int i = 0; i < n; i++)
prefix[i + 1] = prefix[i] + arr[i];
int ans = 0;
// Start partitioning from index 0
Backtrack(0, k - 1, prefix, 0, ref ans);
return ans;
}
static int GCD(int a, int b)
{
return (b == 0) ? a : GCD(b, a % b);
}
static void Main()
{
int k = 4;
int[] arr = { 6, 7, 5, 27, 3 };
// Print the maximum possible GCD
Console.WriteLine(solve(k, arr));
}
}
// Compute GCD of two numbers
function gcd(a, b) { return b === 0 ? a : gcd(b, a % b); }
// Recursively try all possible partitions
function backtrack(idx, cutsLeft, prefix, currGCD, ans)
{
const n = prefix.length - 1;
// No more cuts left, form the last subarray
if (cutsLeft === 0) {
const lastSum = prefix[n] - prefix[idx];
currGCD = (currGCD === 0) ? lastSum
: gcd(currGCD, lastSum);
ans.value = Math.max(ans.value, currGCD);
return;
}
// Try placing the next cut at every possible position
for (let i = idx + 1; i <= n - cutsLeft; i++) {
const segmentSum = prefix[i] - prefix[idx];
const newGCD = (currGCD === 0)
? segmentSum
: gcd(currGCD, segmentSum);
backtrack(i, cutsLeft - 1, prefix, newGCD, ans);
}
}
function solve(k, arr)
{
const n = arr.length;
// Compute prefix sums for O(1) subarray sum queries
const prefix = new Array(n + 1).fill(0);
for (let i = 0; i < n; i++)
prefix[i + 1] = prefix[i] + arr[i];
const ans = {value : 0};
// Start partitioning from index 0
backtrack(0, k - 1, prefix, 0, ans);
return ans.value;
}
// Driver Code
const k = 4;
const arr = [ 6, 7, 5, 27, 3 ];
// Print the maximum possible GCD
console.log(solve(k, arr));
Output
3
Time Complexity: O(C(n − 1, k − 1) * k * log(S)), where S is the total sum of the array.
Auxiliary Space: O(n + k)
[Expected Approach] Using Divisors and Prefix Sum
Instead of checking every possible partition, observe that the GCD of the subarray sums must divide the total sum of the array. Therefore, we only need to examine the divisors of the total sum and use prefix sums to efficiently determine whether the array can be partitioned into at least k contiguous subarrays whose sums are all divisible by a chosen divisor.
- Compute the total sum of the array.
- Find all divisors of the total sum and sort them in descending order.
- Compute the prefix sum array.
- For each divisor g, count the number of prefix sums divisible by g.
- If at least k prefix sums are divisible by g, return g as it is the maximum possible GCD.
- If no larger divisor satisfies the condition, return 1.
#include <bits/stdc++.h>
using namespace std;
int solve(int k, vector<int> &arr)
{
int n = arr.size();
int totalSum = 0;
// Compute the total sum of the array
for (int x : arr)
totalSum += x;
vector<int> divisors;
// Find all divisors of the total sum
for (int i = 1; i * i <= totalSum; i++)
{
if (totalSum % i == 0)
{
divisors.push_back(i);
if (i != totalSum / i)
divisors.push_back(totalSum / i);
}
}
// Check larger divisors first
sort(divisors.rbegin(), divisors.rend());
// Compute prefix sums
for (int i = 1; i < n; i++)
arr[i] += arr[i - 1];
// Check each divisor
for (int g : divisors)
{
int cnt = 0;
// Count prefix sums divisible by the current divisor
for (int sum : arr)
{
if (sum % g == 0)
cnt++;
}
// If at least k valid segment endings exist,
// we can partition the array into k subarrays
if (cnt >= k)
return g;
}
return 1;
}
int main()
{
int k = 4;
vector<int> arr = {6, 7, 5, 27, 3};
// Print the maximum possible GCD
cout << solve(k, arr) << endl;
return 0;
}
import java.util.*;
public class GFG {
static int solve(int k, int[] arr)
{
int n = arr.length;
int totalSum = 0;
// Compute the total sum of the array
for (int x : arr)
totalSum += x;
ArrayList<Integer> divisors = new ArrayList<>();
// Find all divisors of the total sum
for (int i = 1; i * i <= totalSum; i++) {
if (totalSum % i == 0) {
divisors.add(i);
if (i != totalSum / i)
divisors.add(totalSum / i);
}
}
// Check larger divisors first
divisors.sort(Collections.reverseOrder());
// Compute prefix sums
for (int i = 1; i < n; i++)
arr[i] += arr[i - 1];
// Check each divisor
for (int g : divisors) {
int cnt = 0;
// Count prefix sums divisible by the current
// divisor
for (int sum : arr) {
if (sum % g == 0)
cnt++;
}
// If at least k valid segment endings exist,
// we can partition the array into k subarrays
if (cnt >= k)
return g;
}
return 1;
}
public static void main(String[] args)
{
int k = 4;
int[] arr = { 6, 7, 5, 27, 3 };
// Print the maximum possible GCD
System.out.println(solve(k, arr));
}
}
def solve(k, arr):
n = len(arr)
total_sum = sum(arr)
divisors = []
# Find all divisors of the total sum
i = 1
while i * i <= total_sum:
if total_sum % i == 0:
divisors.append(i)
if i != total_sum // i:
divisors.append(total_sum // i)
i += 1
# Check larger divisors first
divisors.sort(reverse=True)
# Compute prefix sums
for i in range(1, n):
arr[i] += arr[i - 1]
# Check each divisor
for g in divisors:
cnt = 0
# Count prefix sums divisible by the current divisor
for prefix_sum in arr:
if prefix_sum % g == 0:
cnt += 1
# If at least k valid segment endings exist,
# we can partition the array into k subarrays
if cnt >= k:
return g
return 1
# Driver Code
if __name__ == "__main__":
k = 4
arr = [6, 7, 5, 27, 3]
# Print the maximum possible GCD
print(solve(k, arr))
using System;
using System.Collections.Generic;
class GFG {
static int solve(int k, int[] arr)
{
int n = arr.Length;
int totalSum = 0;
// Compute the total sum of the array
foreach(int x in arr) totalSum += x;
List<int> divisors = new List<int>();
// Find all divisors of the total sum
for (int i = 1; i * i <= totalSum; i++) {
if (totalSum % i == 0) {
divisors.Add(i);
if (i != totalSum / i)
divisors.Add(totalSum / i);
}
}
// Check larger divisors first
divisors.Sort((a, b) => b.CompareTo(a));
// Compute prefix sums
for (int i = 1; i < n; i++)
arr[i] += arr[i - 1];
// Check each divisor
foreach(int g in divisors)
{
int cnt = 0;
// Count prefix sums divisible by the current
// divisor
foreach(int prefixSum in arr)
{
if (prefixSum % g == 0)
cnt++;
}
// If at least k valid segment endings exist,
// we can partition the array into k subarrays
if (cnt >= k)
return g;
}
return 1;
}
static void Main()
{
int k = 4;
int[] arr = { 6, 7, 5, 27, 3 };
// Print the maximum possible GCD
Console.WriteLine(solve(k, arr));
}
}
function solve(k, arr)
{
const n = arr.length;
let totalSum = 0;
// Compute the total sum of the array
for (const x of arr)
totalSum += x;
const divisors = [];
// Find all divisors of the total sum
for (let i = 1; i * i <= totalSum; i++) {
if (totalSum % i === 0) {
divisors.push(i);
if (i !== totalSum / i)
divisors.push(totalSum / i);
}
}
// Check larger divisors first
divisors.sort((a, b) => b - a);
// Compute prefix sums
for (let i = 1; i < n; i++)
arr[i] += arr[i - 1];
// Check each divisor
for (const g of divisors) {
let cnt = 0;
// Count prefix sums divisible by the current
// divisor
for (const prefixSum of arr) {
if (prefixSum % g === 0)
cnt++;
}
// If at least k valid segment endings exist,
// we can partition the array into k subarrays
if (cnt >= k)
return g;
}
return 1;
}
// Driver Code
const k = 4;
const arr = [ 6, 7, 5, 27, 3 ];
// Print the maximum possible GCD
console.log(solve(k, arr));
Output
3
Time Complexity: O(sqrt(S) + d * log(d) + n * d) where n = size of the array, S = total sum of the array, and d = number of divisors of S.
Auxiliary Space: O(d)