-
Notifications
You must be signed in to change notification settings - Fork 100
/
Copy pathrod-cutting.ts
138 lines (115 loc) · 4.02 KB
/
rod-cutting.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import { range } from '../../utils';
/**
* Problem: Find the best way to cut a rod of length n, assuming that each length
* has a price.
*
* Find best set of cuts to get maximum price. Each cut is integer length and can
* use any number of cuts, from 0 to n−1.
*
* The solution uses dynamic-programming and both strategies (top-down & bottom-up)
* are provided.
* @url https://en.wikipedia.org/wiki/Dynamic_programming
*/
export interface BestResults {
bestPrices: number[];
bestFirstCuts: number[];
}
//
// ─── TOP-DOWN MEMOIZED SOLUTION ──────────────────────────────────────────────────
//
/**
* Return the maximum prices and best first cuts up to then given rod length
* Time complexity: O(n^2). It has worse constants than the following `bottomUpCutRod`
* but it's more elegant and it's recursive.
* @param prices List of prices for each rod length
* @param length Length of the full rod
*/
export function topDownCutRod(prices: number[], length: number): BestResults {
const bestPrices: number[] = new Array(length + 1);
bestPrices.fill(-Infinity);
const bestFirstCuts: number[] = new Array(length + 1);
return topDownCutRodAux(prices, length, bestPrices, bestFirstCuts);
}
function topDownCutRodAux(
prices: number[],
length: number,
bestPrices: number[],
bestFirstCuts: number[]
): BestResults {
if (bestPrices[length] >= 0) return { bestPrices, bestFirstCuts };
let maxCutPrice = -Infinity;
let bestFirstCut: number = -1;
if (length === 0) maxCutPrice = 0;
else {
range(1, length).forEach(firstCut => {
const remainingRod = topDownCutRodAux(prices, length - firstCut, bestPrices, bestFirstCuts);
const cutPrice = prices[firstCut] + remainingRod.bestPrices[length - firstCut];
if (cutPrice > maxCutPrice) {
maxCutPrice = cutPrice;
bestFirstCut = firstCut;
}
});
}
bestPrices[length] = maxCutPrice;
bestFirstCuts[length] = bestFirstCut;
return {
bestPrices,
bestFirstCuts,
};
}
//
// ─── BOTTOM-UP MEMOIZED SOLUTION ─────────────────────────────────────────────────
//
/**
* Return the maximum prices and best first cuts up to then given rod length
* Time complexity: O(n^2) but has better constants than memoizedCutRod, which is
* recursive.
* @param prices List of prices for each rod length
* @param length Length of the full rod
*/
export function bottomUpCutRod(prices: number[], length: number): BestResults {
const bestPrices: number[] = new Array(length + 1);
const bestFirstCuts: number[] = new Array(length + 1);
bestPrices.fill(-Infinity);
// The price of rod of length 0 is 0
bestPrices[0] = 0;
range(1, length).forEach(subLength => {
let maxCutPrice = -Infinity;
range(1, subLength).forEach(firstCut => {
const cutPrice = prices[firstCut] + bestPrices[subLength - firstCut];
if (cutPrice > maxCutPrice) {
maxCutPrice = cutPrice;
bestPrices[subLength] = maxCutPrice;
bestFirstCuts[subLength] = firstCut;
}
});
});
return {
bestPrices,
bestFirstCuts,
};
}
/**
* Return best cuts lengths for a rod of given length
* Time complexity: O(n^2). It has better constants than memoizedCutRod, which is
* recursive.
* @param prices List of prices for each rod length
* @param length Length of the full rod
* @param strategy Whether to use 'bottomUp' or 'topDown' strategy. Usually the
* former is better.
*/
export function cutRod(
prices: number[],
length: number,
strategy: 'bottomUp' | 'topDown' = 'bottomUp'
): number[] {
const { bestFirstCuts } =
strategy === 'bottomUp' ? bottomUpCutRod(prices, length) : topDownCutRod(prices, length);
let remainingRod = length;
const bestCuts: number[] = [];
while (remainingRod) {
bestCuts.push(bestFirstCuts[remainingRod]);
remainingRod -= bestFirstCuts[remainingRod];
}
return bestCuts;
}