Given an array arr[] of integers, where each element represents the number of coins in a pile. You are also given an integer k.
Your task is to remove the minimum number of coins such that the absolute difference between the number of coins in any two updated piles is at most k.
Note: You can also remove a pile by removing all the coins of that pile.
Examples:
Input: arr[] = [2, 2, 2, 2], k= 0
Output: 0
Explanation: For all piles the difference in the number of coins is = 0. So, no need to remove any coins.Input: arr[] = [1, 5, 1, 2, 5, 1], k = 3
Output: 2
Explanation: If we remove one coin each from both the piles containing 5 coins, then for any two piles the absolute difference in the number of coins is <= 3.
[Approach]: Binary Search and Prefix Sum
We first sort the array to handle piles in order, then for each pile, we assume it to be the smallest remaining pile and compute the cost to remove all smaller piles entirely and trim larger ones to ensure no pile exceeds
arr[i] + k. For each index, we efficiently calculate the coins to remove and select the minimum among them.
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
int minimumCoins(vector<int>& arr, int k) {
// Sort the array to handle values in increasing order
sort(arr.begin(), arr.end());
int n = arr.size();
// Prefix sum array to compute total coins quickly
vector<int> prefix(n, 0);
prefix[0] = arr[0];
for (int i = 1; i < n; i++)
prefix[i] = prefix[i - 1] + arr[i];
int ans = INT_MAX, prev = 0;
for (int i = 0; i < n; i++) {
// Update `prev` only when
// arr[i] is different from arr[i - 1] else continue
// This represents total coins from piles smaller than arr[i]
if (i > 0 && arr[i] == arr[i - 1])
continue;
if(i > 0){
prev = prefix[i-1];
}
// Find the first index where arr[pos] > arr[i] + k
int pos = upper_bound(arr.begin() + i, arr.end(), arr[i] + k) - arr.begin();
// Total coins to remove:
// - `prev` for coins from smaller piles (before i)
// - `(prefix[n - 1] - prefix[pos - 1])` is the total
// coins after pos index
// - coins that is allowed to stay in the range : [arr[i], arr[i] + k]
int totalToRemove = prev + prefix[n - 1] - prefix[pos - 1] - (n - pos) * (arr[i] + k);
// Update answer with minimum coins removed
ans = min(ans, totalToRemove);
}
return ans;
}
int main(){
vector<int> arr = { 1, 5, 1, 2, 5, 1 };
int k = 3;
cout << minimumCoins(arr, k);
return 0;
}
import java.util.*;
class GfG {
public static int minimumCoins(int[] arr, int k) {
// Sort the array to handle values in increasing order
Arrays.sort(arr);
int n = arr.length;
// Prefix sum array to compute total coins quickly
int[] prefix = new int[n];
prefix[0] = arr[0];
for (int i = 1; i < n; i++)
prefix[i] = prefix[i - 1] + arr[i];
int ans = Integer.MAX_VALUE, prev = 0;
for (int i = 0; i < n; i++) {
// Update `prev` only when
// arr[i] is different from arr[i - 1] else continue
// This represents total coins from piles smaller than arr[i]
if (i > 0 && arr[i] == arr[i - 1])
continue;
if (i > 0) {
prev = prefix[i - 1];
}
// Find the first index where arr[pos] > arr[i] + k
int pos = upperBound(arr, arr[i] + k, i, n);
// Total coins to remove:
// - `prev` for coins from smaller piles (before i)
// - `(prefix[n - 1] - prefix[pos - 1])` is the total
// coins after pos index
// - coins that is allowed to stay in the range : [arr[i], arr[i] + k]
int totalToRemove = prev;
if (pos < n) {
totalToRemove += prefix[n - 1] - prefix[pos - 1] - (n - pos) * (arr[i] + k);
}
// Update answer with minimum coins removed
ans = Math.min(ans, totalToRemove);
}
return ans;
}
private static int upperBound(int[] arr, int key, int low, int high) {
while (low < high) {
int mid = (low + high) / 2;
if (arr[mid] <= key) {
low = mid + 1;
} else {
high = mid;
}
}
return low;
}
public static void main(String[] args) {
int[] arr = {1, 5, 1, 2, 5, 1};
int k = 3;
System.out.println(minimumCoins(arr, k));
}
}
import bisect
def minimumCoins(arr, k):
# Sort the array to handle values in increasing order
arr.sort()
n = len(arr)
# Prefix sum array to compute total coins quickly
prefix = [0] * n
prefix[0] = arr[0]
for i in range(1, n):
prefix[i] = prefix[i - 1] + arr[i]
ans = float('inf')
prev = 0
for i in range(n):
# Update `prev` only when
# arr[i] is different from arr[i - 1] else continue
# This represents total coins from piles smaller than arr[i]
if i > 0 and arr[i] == arr[i - 1]:
continue
if i > 0:
prev = prefix[i - 1]
# Find the first index where arr[pos] > arr[i] + k
pos = bisect.bisect_right(arr, arr[i] + k, i, n)
# Total coins to remove:
# - `prev` for coins from smaller piles (before i)
# - `(prefix[n - 1] - prefix[pos - 1])` is the total
# coins after pos index
# - coins that is allowed to stay in the range : [arr[i], arr[i] + k]
totalToRemove = prev
if pos < n:
totalToRemove += prefix[n - 1] - prefix[pos - 1] - (n - pos) * (arr[i] + k)
# Update answer with minimum coins removed
ans = min(ans, totalToRemove)
return ans
arr = [1, 5, 1, 2, 5, 1]
k = 3
print(minimumCoins(arr, k))
using System;
class GfG
{
public static int MinimumCoins(int[] arr, int k)
{
// Sort the array to handle values in increasing order
Array.Sort(arr);
int n = arr.Length;
// Prefix sum array to compute total coins quickly
int[] prefix = new int[n];
prefix[0] = arr[0];
for (int i = 1; i < n; i++)
prefix[i] = prefix[i - 1] + arr[i];
int ans = int.MaxValue, prev = 0;
for (int i = 0; i < n; i++)
{
// Update `prev` only when
// arr[i] is different from arr[i - 1] else continue
// This represents total coins from piles smaller than arr[i]
if (i > 0 && arr[i] == arr[i - 1])
continue;
if (i > 0)
{
prev = prefix[i - 1];
}
// Find the first index where arr[pos] > arr[i] + k
int pos = UpperBound(arr, arr[i] + k, i, n);
// Total coins to remove:
// - `prev` for coins from smaller piles (before i)
// - `(prefix[n - 1] - prefix[pos - 1])` is the total
// coins after pos index
// - coins that is allowed to stay in the range : [arr[i], arr[i] + k]
int totalToRemove = prev;
if (pos < n)
{
totalToRemove += prefix[n - 1] - prefix[pos - 1] - (n - pos) * (arr[i] + k);
}
// Update answer with minimum coins removed
ans = Math.Min(ans, totalToRemove);
}
return ans;
}
public static int UpperBound(int[] arr, int key, int low, int high)
{
while (low < high)
{
int mid = (low + high) / 2;
if (arr[mid] <= key)
low = mid + 1;
else
high = mid;
}
return low;
}
static void Main()
{
int[] arr = { 1, 5, 1, 2, 5, 1 };
int k = 3;
Console.WriteLine(MinimumCoins(arr, k));
}
}
function minimumCoins(arr, k) {
// Sort the array to handle values in increasing order
arr.sort((a, b) => a - b);
const n = arr.length;
// Prefix sum array to compute total coins quickly
const prefix = new Array(n).fill(0);
prefix[0] = arr[0];
for (let i = 1; i < n; i++)
prefix[i] = prefix[i - 1] + arr[i];
let ans = Number.MAX_SAFE_INTEGER;
let prev = 0;
for (let i = 0; i < n; i++) {
// Update `prev` only when
// arr[i] is different from arr[i - 1] else continue
// This represents total coins from piles smaller than arr[i]
if (i > 0 && arr[i] === arr[i - 1])
continue;
if (i > 0) {
prev = prefix[i - 1];
}
// Find the first index where arr[pos] > arr[i] + k
const pos = upperBound(arr, arr[i] + k, i, n);
// Total coins to remove:
// - `prev` for coins from smaller piles (before i)
// - `(prefix[n - 1] - prefix[pos - 1])` is the total
// coins after pos index
// - coins that is allowed to stay in the range : [arr[i], arr[i] + k]
let totalToRemove = prev;
if (pos < n) {
totalToRemove += prefix[n - 1] - prefix[pos - 1] - (n - pos) * (arr[i] + k);
}
// Update answer with minimum coins removed
ans = Math.min(ans, totalToRemove);
}
return ans;
}
function upperBound(arr, key, low, high) {
while (low < high) {
const mid = Math.floor((low + high) / 2);
if (arr[mid] <= key) {
low = mid + 1;
} else {
high = mid;
}
}
return low;
}
const arr = [1, 5, 1, 2, 5, 1];
const k = 3;
console.log(minimumCoins(arr, k));
Output
2
Time Complexity: O(n*log(n)) due to sorting and performing binary search for each of the n elements.
Space complexity: O(n) due to the prefix sum array used to store cumulative sums of the sorted array.
[Expected Approach] Two Pointer -O(n*log(n)) Time and O(1) Space
After sorting the array, we use a sliding window to try to keep the largest group of consecutive piles where the difference between the maximum and minimum is at most
k. For each such window, we calculate how many coins need to be removed: we remove all coins from the piles before the window entirely, and from the piles after the window, we remove only the excess above the allowed maximum. By sliding this window across the array and checking each case, we keep track of the minimum total number of coins removed. This way, we ensure that we find the optimal result.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int minimumCoins(vector<int>& arr, int k) {
int n = arr.size();
sort(arr.begin(), arr.end());
int total = 0;
for (int x : arr)
total += x;
int minRemoved = total;
int windowSum = 0;
int prefix = 0;
int end = 0;
for (int start = 0; start < n; start++) {
// Expand the window to include elements
// within k difference from arr[start]
while (end < n && arr[end] - arr[start] <= k) {
windowSum += arr[end];
end++;
}
// Calculate the upper limit allowed
// for pile values in the window
int upper = arr[start] + k;
int rightCount = n - end;
// Calculate how many coins need
// to be removed from right-side piles
int removeRight = (total - prefix - windowSum) - rightCount * upper;
int removed = prefix + removeRight;
minRemoved = min(minRemoved, removed);
if (end == start) {
end++;
} else {
windowSum -= arr[start];
}
prefix += arr[start];
}
return minRemoved;
}
int main() {
vector<int> arr = { 1, 5, 1, 2, 5, 1 };
int k = 3;
cout << minimumCoins(arr, k);
return 0;
}
import java.util.*;
class GfG {
public static int minimumCoins(int[] arr, int k) {
int n = arr.length;
Arrays.sort(arr);
int total = 0;
for (int x : arr)
total += x;
int minRemoved = total;
int windowSum = 0;
int prefix = 0;
int end = 0;
for (int start = 0; start < n; start++) {
// Expand the window to include elements
// within k difference from arr[start]
while (end < n && arr[end] - arr[start] <= k) {
windowSum += arr[end];
end++;
}
// Calculate the upper limit allowed
// for pile values in the window
int upper = arr[start] + k;
int rightCount = n - end;
// Calculate how many coins need
// to be removed from right-side piles
int removeRight = (total - prefix - windowSum) - rightCount * upper;
int removed = prefix + removeRight;
minRemoved = Math.min(minRemoved, removed);
if (end == start) {
end++;
} else {
windowSum -= arr[start];
}
prefix += arr[start];
}
return minRemoved;
}
public static void main(String[] args) {
int[] arr = { 1, 5, 1, 2, 5, 1 };
int k = 3;
System.out.println(minimumCoins(arr, k));
}
}
def minimumCoins(arr, k):
n = len(arr)
arr.sort()
total = sum(arr)
min_removed = total
window_sum = 0
prefix = 0
end = 0
for start in range(n):
# Expand the window to include elements
# within k difference from arr[start]
while end < n and arr[end] - arr[start] <= k:
window_sum += arr[end]
end += 1
# Calculate the upper limit allowed
# for pile values in the window
upper = arr[start] + k
right_count = n - end
# Calculate how many coins need
# to be removed from right-side piles
remove_right = (total - prefix - window_sum) - right_count * upper
removed = prefix + remove_right
min_removed = min(min_removed, removed)
if end == start:
end += 1
else:
window_sum -= arr[start]
prefix += arr[start]
return min_removed
if __name__ == "__main__":
arr = [1, 5, 1, 2, 5, 1]
k = 3
print(minimumCoins(arr, k))
using System;
using System.Linq;
class GfG
{
public static int MinimumCoins(int[] arr, int k)
{
int n = arr.Length;
Array.Sort(arr);
int total = arr.Sum();
int minRemoved = total;
int windowSum = 0;
int prefix = 0;
int end = 0;
for (int start = 0; start < n; start++)
{
// Expand the window to include elements
// within k difference from arr[start]
while (end < n && arr[end] - arr[start] <= k)
{
windowSum += arr[end];
end++;
}
// Calculate the upper limit allowed
// for pile values in the window
int upper = arr[start] + k;
int rightCount = n - end;
// Calculate how many coins need
// to be removed from right-side piles
int removeRight = (total - prefix - windowSum) - rightCount * upper;
int removed = prefix + removeRight;
minRemoved = Math.Min(minRemoved, removed);
if (end == start)
{
end++;
}
else
{
windowSum -= arr[start];
}
prefix += arr[start];
}
return minRemoved;
}
static void Main()
{
int[] arr = { 1, 5, 1, 2, 5, 1 };
int k = 3;
Console.WriteLine(MinimumCoins(arr, k));
}
}
function minimumCoins(arr, k) {
const n = arr.length;
arr.sort((a, b) => a - b);
let total = arr.reduce((sum, val) => sum + val, 0);
let minRemoved = total;
let windowSum = 0;
let prefix = 0;
let end = 0;
for (let start = 0; start < n; start++) {
// Expand the window to include elements
// within k difference from arr[start]
while (end < n && arr[end] - arr[start] <= k) {
windowSum += arr[end];
end++;
}
// Calculate the upper limit allowed
// for pile values in the window
let upper = arr[start] + k;
let rightCount = n - end;
// Calculate how many coins need
// to be removed from right-side piles
let removeRight = (total - prefix - windowSum) - rightCount * upper;
let removed = prefix + removeRight;
minRemoved = Math.min(minRemoved, removed);
if (end === start) {
end++;
} else {
windowSum -= arr[start];
}
prefix += arr[start];
}
return minRemoved;
}
const arr = [1, 5, 1, 2, 5, 1];
const k = 3;
console.log(minimumCoins(arr, k));
Output
2