Google Interview Question
Software EngineersCountry: India
Interview Type: Phone Interview
C(i, j) = MIN/k {C(i,k) + C(k, j) + L(j - i)}, k = i+1, …,j-1
What the above line says is basically, if I cut at position k between i and j, then to minimum cost of this cut, is going to be the minimum cost of further cutting up rods ranging {i, k} and {k, j}. This is as per the optimal substructure rule. In addition, I need to add the cost of making this cut at k, which is L(j - i).
L(j - i) is the length of the rod from marking i to marking j (remember, the markings aren't always uniform! :) )
Also, an edit: j ranges from {i, i+1, ..., m+1}.
Can't we optimize here by collapsing the 2D array down to a 1D array minCosts where minCosts[i] = minimum cost of cutting a log of length m*i ?
Basically, what I'm suggesting is that the minimum cost of cutting a certain length of log, say C[i,j] is the same as C[i-1,j-1] since the log segments are the same length.
If we compute the minimum cost just purely on length of the log, I think the n-square computation inside the outer loop can be collapsed down to n, making the total running time O(n-square).
Somehow, I can't convince myself that we need O(n-cube).
I don't think there is a viable greedy solution here (pick the median cut point each time? the cut point closest to the middle?), so we'll have to use dynamic programming and memoization:
def cut_log(arr):
"""Returns minimum cost of cutting the log."""
if len(arr) <= 2:
return 0
optimal_cut_point = argmin_i(cut_log(arr[:i+1]) + cut_log(arr[i:]))
return max(arr) - min(arr) + cut_log(arr[:i+1]) + cut_log(arr[i:]))
Surprisingly there is no need for a program to calculate. This is more like a brain teasing question. The lease cost is n - max(di), which di represents the length of log between neighbor markings. Each time cut off the shorter log on the sides of log and the log with longest length remains in the hard at the end.
Follow this blog, cpluspluslearning-petert.blogspot.co.uk/2015/04/minimize-cost-of-cutting-log-google.html, to see the induction.
I will consider the most optimal case which is the marking are equally spaced.
So let L be the distance between two consecutive marking. N will then be equal to (M+1)*L.
The best way to cut in this case is to cut at the marking M/2, then M/4, etc...until we reach L
This cutting path costs Log(N), since there are M similar paths. The total cost will be M*Log(N).
This solution is better than cutting the markings in sequence starting with the 1st, then 2nd, then 3rd until Mth. Which results in a cost of (M+1)*N/2
However since we don't know if the markings are equally spaced, the best solution is to try to get as close as possible to the optimal one (explained above) by cutting at the marking which is the closest to the middle of the segment.
Here's working code for this problem, along with a test case (input expected to be clean):
public class LogCuttingProblem {
public static void main(String[] args) {
System.out.println(calculateLogCuttingCost(100, new int[]{23, 34, 35, 68, 71, 88, 93}));
}
public static int calculateLogCuttingCost(int n, int[] markings) {
int m = markings.length;
int[] newMarkings = new int[m+2];
newMarkings[0] = 0;
newMarkings[m+1] = n;
for(int j=1; j<m+1; j++) {
newMarkings[j] = markings[j-1];
}
m = newMarkings.length;
int[][] cmatrix = new int[m][m];
cmatrix[0][0] = 0;
cmatrix[0][1] = newMarkings[1];
cmatrix[m-2][m-1] = n - newMarkings[m-2];
for(int i=1; i<m-2; i++) {
cmatrix[i][i+1] = newMarkings[i+1] - newMarkings[i]; // sub-logs with no markings
}
for(int windowSize=1; windowSize<m; windowSize++) {
for(int i=0; i+windowSize<m; i++) {
int j = i + windowSize;
int minCost = Integer.MAX_VALUE;
for(int k=i+1; k<j; k++) {
int currCost = cmatrix[i][k] + cmatrix[k][j] + newMarkings[j] - newMarkings[i];
if(currCost < minCost) minCost = currCost;
}
if(j != i+1)cmatrix[i][j] = minCost;
}
}
return cmatrix[0][m-1];
}
}
You have a bug: System.out.println(calculateLogCuttingCost(100, new int[]{}));
Cutting a rod with length 100 without markings costs 0.
Here is my proposal:
/**
The plan
======
M markings => M+2 markings, including the beginning and the end of the log
dp[i][j] = optimum cost cutting up log between marking i and marking j
dp[i][j] = if |j-i| < 2 then 0, same marking or no inbetween markings
dp[i][j] = dp[i][k]+dp[k][j]+logLen(i,j) over all k:i+1..j-1
O(M**2) different (i,j) pairs:
maximum O(M) different cuts between i and j:
1 sum calculation, 1 length calculation -> time:O(1), space:O(M)
dp time: O((M**2)*M), space: O(M**2)
**/
public class CarerrCup_5188262471663616{
public static void main(String[]args){
System.out.println(findMinCost(new int[]{1, 3, 7, 10}, 15));
System.out.println(findMinCost(new int[]{}, 15));
System.out.println(findMinCost(new int[]{1}, 15));
System.out.println(findMinCost(new int[]{23, 34, 35, 68, 71, 88, 93}, 100));
}
private static int findMinCost(int[] markings, int len){
int[] logLen = logLen(markings, len);
int[][] dp = new int[markings.length+2][markings.length+2];
for(int betweenMarkCount = 1;
betweenMarkCount <= markings.length;
++betweenMarkCount){
for(int i = 0;; ++i){
int j = i+betweenMarkCount+1;
if (j >= dp.length) break;
int min = Integer.MAX_VALUE;
int cost = logLen[j]-logLen[i];
for(int k = i+1; k <= j-1; ++k){
min = Math.min(min, dp[i][k]+dp[k][j]+cost);
}
dp[i][j] = min;
}
}
return dp[0][dp.length-1];
}
private static int[] logLen(int[] markings, int len){
int[] ret = new int[markings.length+2];
for(int i = 1; i <= markings.length; ++i){
ret[i] = markings[i-1];
}
ret[ret.length-1] = len;
return ret;
}
}
This is my solution using modified binary search to find the closest possible cut that is in the middle of the log:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int find_price(vector<int> &cuts, int from, int to) {
if (from >= to-1) return 0;
int dist=cuts[to]-cuts[from], m=(from+to)/2, cut = -1;
int target=cuts[from]+dist/2, curdiff = dist, tmpdiff = abs(target - cuts[m]);
while (m>from && m<to && curdiff>tmpdiff) {
curdiff = tmpdiff;
cut = m;
// Left or right?
if (abs(target-cuts[m-1]) < abs(target-cuts[m+1])) {
tmpdiff = abs(target-cuts[--m]);
} else {
tmpdiff = abs(target-cuts[++m]);
}
}
if (cut > -1) {
dist += find_price(cuts, from, cut),
dist += find_price(cuts, cut, to);
}
return dist;
}
int main() {
const int len = 80;
vector<int> cuts = {30,41,67,5,10,15,20,25};
cuts.push_back(0);
cuts.push_back(len);
sort(cuts.begin(), cuts.end());
cout << find_price(cuts, 0, cuts.size()-1);
return 0;
}
We'd have to use dynamic programming to solve this one, since there are 2^(m) combinations of making even the first cut.
My algorithm is listed below in pseudocode:
- Killedsteel April 13, 2015