Optimal Binary Search Tree (original) (raw)
Given a sorted array keys[0.. n-1] of search keys and an array freq[0.. n-1] of frequency counts, where freq[i] is the number of searches for keys[i]. Construct a binary search tree of all keys such that the total cost of all the searches is as small as possible.
The cost of a BST node is the level of that node multiplied by its frequency. The level of the root is 1.
**Examples:
**Input: keys[] = [10, 12], freq[]= [34, 50]
**Output: 118
**Explanation: There can be following two possible BSTs
The cost of tree I is 34*1 + 50*2 = 134
The cost of tree II is 50*1 + 34*2 = 118
**Input: keys[] = [10, 12, 20], freq[]= [34, 8, 50]
**Output: 142
**Explanation: There can be many possible BSTs. _Among all possible BSTs, cost of the fifth BST is 1*50 + 2*34 + 3*8 = 142 which is the minimum.
Table of Content
- [Naive Approach] Using Recursion - O(2^n) Time and O(n) Space
- [Expected Approach 1] Using Top - Down Dp (Memoization) - O(n^3) Time and O(n^2) Space
- [Expected Approach 2] Using Bottom - Up Dp (Tabulation) - O(n^3) Time and O(n^2) Space
Intuition:
We would multiply each frequency by its tree level, but keeping track of levels makes the solution complicated.
Instead of that, we use a smarter approach. Whenever we choose a node as the root, we add the sum of the frequencies of all the nodes between the start and end index (i to j). After choosing the root, every node in that group (both left and right subtree) automatically shifts one level deeper. When recursion goes deeper and solves the smaller subtrees, their frequency sum is added again automatically, which has the exact same effect as multiplying each node by its level.
[Naive Approach] Using Recursion - O(2^n) Time and O(n) Space
As discussed in our intuition, we try each key as the root of the tree. For every root choice, we recursively compute the cost of its left and right subtrees. At each step, we add the sum of freq in that range to account for all keys moving one level deeper. We repeat this process for all possible roots and finally pick the configuration that gives the minimum total cost.
C++ `
//Driver Code Starts #include #include using namespace std;
//Driver Code Ends
//function to get sum of // array elements freq[i] to freq[j] int sum(vector &freq, int i, int j) { int s = 0; for (int k = i; k <= j; k++) s += freq[k]; return s; }
// A recursive function to calculate // cost of optimal binary search tree int optCost(vector &freq, int i, int j) {
// no elements in this subarray
if (j < i)
return 0;
// one element in this subarray
if (j == i)
return freq[i];
// Get sum of freq[i], freq[i+1], ... freq[j]
int fsum = sum(freq, i, j);
int minCost = INT_MAX;
// One by one consider all elements
// as root and recursively find cost
// of the BST
for (int r = i; r <= j; ++r) {
int cost = optCost(freq, i, r - 1) + optCost(freq, r + 1, j);
if (cost < minCost)
minCost = cost;
}
// Return minimum value
return minCost + fsum;}
int minCost(vector &keys, vector &freq) {
int n = keys.size();
return optCost(freq, 0, n - 1);}
//Driver Code Starts
int main() {
vector<int> keys = {10, 12, 20};
vector<int> freq = {34, 8, 50};
cout << minCost(keys, freq);
return 0;}
//Driver Code Ends
Java
//Driver Code Starts import java.util.Arrays;
public class GFG {
//Driver Code Ends
// function to get sum of
// array elements freq[i] to freq[j]
static int sum(int[] freq, int i, int j) {
int s = 0;
for (int k = i; k <= j; k++)
s += freq[k];
return s;
}
// A recursive function to calculate
// cost of optimal binary search tree
static int optCost(int[] freq, int i, int j) {
// no elements in this subarray
if (j < i)
return 0;
// one element in this subarray
if (j == i)
return freq[i];
// Get sum of freq[i], freq[i+1], ... freq[j]
int fsum = sum(freq, i, j);
int minCost = Integer.MAX_VALUE;
// One by one consider all elements
// as root and recursively find cost
// of the BST
for (int r = i; r <= j; r++) {
int cost = optCost(freq, i, r - 1) + optCost(freq, r + 1, j);
if (cost < minCost)
minCost = cost;
}
// Return minimum value
return minCost + fsum;
}
static int minCost(int[] keys, int[] freq) {
int n = keys.length;
return optCost(freq, 0, n - 1);
}//Driver Code Starts
public static void main(String[] args) {
int[] keys = {10, 12, 20};
int[] freq = {34, 8, 50};
System.out.println(minCost(keys, freq));
}}
//Driver Code Ends
Python
function to get sum of
array elements freq[i] to freq[j]
def sum(freq, i, j): s = 0 for k in range(i, j + 1): s += freq[k] return s
A recursive function to calculate
cost of optimal binary search tree
def optCost(freq, i, j):
# no elements in this subarray
if j < i:
return 0
# one element in this subarray
if j == i:
return freq[i]
# Get sum of freq[i], freq[i+1], ... freq[j]
fsum = sum(freq, i, j)
minCost = float('inf')
# One by one consider all elements
# as root and recursively find cost
# of the BST
for r in range(i, j + 1):
cost = optCost(freq, i, r - 1) + optCost(freq, r + 1, j)
if cost < minCost:
minCost = cost
# Return minimum value
return minCost + fsumdef minCost(keys, freq): n = len(keys) return optCost(freq, 0, n - 1)
#Driver Code Starts
if name == 'main': keys = [10, 12, 20] freq = [34, 8, 50] print(minCost(keys, freq))
#Driver Code Ends
C#
//Driver Code Starts using System;
class GFG {
//Driver Code Ends
// function to get sum of
// array elements freq[i] to freq[j]
static int sum(int[] freq, int i, int j) {
int s = 0;
for (int k = i; k <= j; k++)
s += freq[k];
return s;
}
// A recursive function to calculate
// cost of optimal binary search tree
static int optCost(int[] freq, int i, int j) {
// no elements in this subarray
if (j < i)
return 0;
// one element in this subarray
if (j == i)
return freq[i];
// Get sum of freq[i], freq[i+1], ... freq[j]
int fsum = sum(freq, i, j);
int minCost = int.MaxValue;
// One by one consider all elements
// as root and recursively find cost
// of the BST
for (int r = i; r <= j; r++) {
int cost = optCost(freq, i, r - 1) + optCost(freq, r + 1, j);
if (cost < minCost)
minCost = cost;
}
// Return minimum value
return minCost + fsum;
}
static int minCost(int[] keys, int[] freq) {
int n = keys.Length;
return optCost(freq, 0, n - 1);
}//Driver Code Starts
static void Main() {
int[] keys = {10, 12, 20};
int[] freq = {34, 8, 50};
Console.WriteLine(minCost(keys, freq));
}}
//Driver Code Ends
JavaScript
// function to get sum of // array elements freq[i] to freq[j] function sum(freq, i, j) { let s = 0; for (let k = i; k <= j; k++) s += freq[k]; return s; }
// A recursive function to calculate // cost of optimal binary search tree function optCost(freq, i, j) {
// no elements in this subarray
if (j < i) return 0;
// one element in this subarray
if (j === i) return freq[i];
// Get sum of freq[i], freq[i+1], ... freq[j]
let fsum = sum(freq, i, j);
let minCost = Number.MAX_SAFE_INTEGER;
// One by one consider all elements
// as root and recursively find cost
// of the BST
for (let r = i; r <= j; r++) {
let cost = optCost(freq, i, r - 1) + optCost(freq, r + 1, j);
if (cost < minCost) minCost = cost;
}
// Return minimum value
return minCost + fsum;}
function minCostKeys(keys, freq) { let n = keys.length; return optCost(freq, 0, n - 1); }
//Driver Code Starts
//Driver Code let keys = [10, 12, 20]; let freq = [34, 8, 50]; console.log(minCostKeys(keys, freq));
//Driver Code Ends
`
[Expected Approach 1] Using Top - Down Dp (Memoization) - O(n^3) Time and O(n^2) Space
If we look at the recursive approach, we notice that many subproblems are solved repeatedly, which leads to exponential time complexity. To avoid this, we use memoization.
We create a 2D DP array of size n x n to store the results of already solved subproblems. Whenever we encounter a subproblem, If the answer is already computed, we simply return it instead of recalculating.
C++ `
//Driver Code Starts #include #include using namespace std;
//Driver Code Ends
//function to get sum of // array elements freq[i] to freq[j] int sum(vector &freq, int i, int j) {
int s = 0;
for (int k = i; k <= j; k++)
s += freq[k];
return s;}
// A recursive function to calculate // cost of optimal binary search tree int optCost(vector &freq, int i, int j, vector<vector> &dp) {
// Base cases
if (j < i)
return 0;
if (j == i)
return freq[i];
if (dp[i][j] != -1)
return dp[i][j];
// Get sum of freq[i], freq[i+1], ... freq[j]
int fsum = sum(freq, i, j);
int minCost = INT_MAX;
// One by one consider all elements
// as root and recursively find cost
// of the BST
for (int r = i; r <= j; ++r) {
int cost = optCost(freq, i, r - 1, dp) + optCost(freq, r + 1, j, dp);
if (cost < minCost)
minCost = cost;
}
// Return minimum value
return dp[i][j] = minCost + fsum;}
// The main function that calculates // minimum cost of a Binary Search Tree. int minCost(vector &keys, vector &freq) { int n = keys.size(); vector<vector> dp(n, vector(n, -1)); return optCost(freq, 0, n - 1, dp); }
//Driver Code Starts
int main() {
vector<int> keys = {10, 12, 20};
vector<int> freq = {34, 8, 50};
cout << minCost(keys, freq);
return 0;}
//Driver Code Ends
Java
//Driver Code Starts import java.util.Arrays;
public class GFG {
//Driver Code Ends
//function to get sum of
// array elements freq[i] to freq[j]
static int sum(int[] freq, int i, int j) {
int s = 0;
for (int k = i; k <= j; k++)
s += freq[k];
return s;
}
// A recursive function to calculate
// cost of optimal binary search tree
static int optCost(int[] freq, int i, int j, int[][] dp) {
// Base cases
if (j < i)
return 0;
if (j == i)
return freq[i];
if (dp[i][j] != -1)
return dp[i][j];
// Get sum of freq[i], freq[i+1], ... freq[j]
int fsum = sum(freq, i, j);
int minCost = Integer.MAX_VALUE;
// One by one consider all elements
// as root and recursively find cost
// of the BST
for (int r = i; r <= j; r++) {
int cost = optCost(freq, i, r - 1, dp) + optCost(freq, r + 1, j, dp);
if (cost < minCost)
minCost = cost;
}
// Return minimum value
return dp[i][j] = minCost + fsum;
}
// The main function that calculates
// minimum cost of a Binary Search Tree.
static int minCost(int[] keys, int[] freq) {
int n = keys.length;
int[][] dp = new int[n][n];
for (int[] row : dp)
Arrays.fill(row, -1);
return optCost(freq, 0, n - 1, dp);
}//Driver Code Starts
public static void main(String[] args) {
int[] keys = {10, 12, 20};
int[] freq = {34, 8, 50};
System.out.println(minCost(keys, freq));
}}
//Driver Code Ends
Python
#function to get sum of
array elements freq[i] to freq[j]
def sum(freq, i, j): s = 0 for k in range(i, j+1): s += freq[k] return s
A recursive function to calculate
cost of optimal binary search tree
def optCost(freq, i, j, dp):
# Base cases
if j < i:
return 0
if j == i:
return freq[i]
if dp[i][j] != -1:
return dp[i][j]
# Get sum of freq[i], freq[i+1], ... freq[j]
fsum = sum(freq, i, j)
minCost = float('inf')
# One by one consider all elements
# as root and recursively find cost
# of the BST
for r in range(i, j+1):
cost = optCost(freq, i, r-1, dp) + optCost(freq, r+1, j, dp)
if cost < minCost:
minCost = cost
# Return minimum value
dp[i][j] = minCost + fsum
return dp[i][j]The main function that calculates
minimum cost of a Binary Search Tree.
def minCost(keys, freq): n = len(keys) dp = [[-1]*n for _ in range(n)] return optCost(freq, 0, n-1, dp)
#Driver Code Starts
if name == 'main': keys = [10, 12, 20] freq = [34, 8, 50] print(minCost(keys, freq))
#Driver Code Ends
C#
//Driver Code Starts using System;
class GFG {
//Driver Code Ends
//function to get sum of
// array elements freq[i] to freq[j]
static int sum(int[] freq, int i, int j) {
int s = 0;
for (int k = i; k <= j; k++)
s += freq[k];
return s;
}
// A recursive function to calculate
// cost of optimal binary search tree
static int optCost(int[] freq, int i, int j, int[,] dp) {
// Base cases
if (j < i)
return 0;
if (j == i)
return freq[i];
if (dp[i, j] != -1)
return dp[i, j];
// Get sum of freq[i], freq[i+1], ... freq[j]
int fsum = sum(freq, i, j);
int minCost = int.MaxValue;
// One by one consider all elements
// as root and recursively find cost
// of the BST
for (int r = i; r <= j; r++) {
int cost = optCost(freq, i, r - 1, dp) + optCost(freq, r + 1, j, dp);
if (cost < minCost)
minCost = cost;
}
// Return minimum value
return dp[i, j] = minCost + fsum;
}
// The main function that calculates
// minimum cost of a Binary Search Tree.
static int minCost(int[] keys, int[] freq) {
int n = keys.Length;
int[,] dp = new int[n, n];
for (int x = 0; x < n; x++)
for (int y = 0; y < n; y++)
dp[x, y] = -1;
return optCost(freq, 0, n - 1, dp);
}//Driver Code Starts
static void Main() {
int[] keys = {10, 12, 20};
int[] freq = {34, 8, 50};
Console.WriteLine(minCost(keys, freq));
}}
//Driver Code Ends
JavaScript
// function to get sum of // array elements freq[i] to freq[j] function sum(freq, i, j) { let s = 0; for (let k = i; k <= j; k++) s += freq[k]; return s; }
// A recursive function to calculate // cost of optimal binary search tree function optCost(freq, i, j, dp) {
// Base cases
if (j < i) return 0;
if (j === i) return freq[i];
if (dp[i][j] !== -1) return dp[i][j];
// Get sum of freq[i], freq[i+1], ... freq[j]
let fsum = sum(freq, i, j);
let minCost = Number.MAX_SAFE_INTEGER;
// One by one consider all elements
// as root and recursively find cost
// of the BST
for (let r = i; r <= j; r++) {
let cost = optCost(freq, i, r-1, dp) + optCost(freq, r+1, j, dp);
if (cost < minCost)
minCost = cost;
}
// Return minimum value
dp[i][j] = minCost + fsum;
return dp[i][j];}
// The main function that calculates // minimum cost of a Binary Search Tree. function minCost(keys, freq) { const n = keys.length; const dp = Array.from({length: n}, () => Array(n).fill(-1)); return optCost(freq, 0, n-1, dp); }
//Driver Code Starts //Driver Code const keys = [10, 12, 20]; const freq = [34, 8, 50]; console.log(minCost(keys, freq));
//Driver Code Ends
`
[Expected Approach 2] Using Bottom - Up Dp (Tabulation) - O(n^3) Time and O(n^2) Space
In the tabulation approach, we solve the problem step by step instead of recursion.
We make a table dp[i][j] where each cell stores the minimum cost of a BST for keys from index i to j. The base case is simple: if there is only one key, the cost is just its frequency. Then, we fill the table for bigger ranges of keys. For each range, we try every key as the root, calculate the cost of the left and right parts using the table, and add the sum of frequencies for that range. We keep the minimum cost in the table. At the end, dp[0][n-1] gives the minimum cost of the BST for all keys.
C++ `
//Driver Code Starts #include #include using namespace std;
//Driver Code Ends
// Function to calculate sum of frequencies from index i to j int sum(vector &freq, int i, int j) { int s = 0; for (int k = i; k <= j; k++) s += freq[k]; return s; }
// Function to calculate minimum cost of a Binary Search Tree using DP (tabulation) int minCost(vector &keys, vector &freq) {
int n = keys.size();
// Create a 2D DP table to store minimum costs for subarrays of keys
vector<vector<int>> dp(n, vector<int>(n, 0));
// Base case: cost of a single key is its frequency
for (int i = 0; i < n; i++) {
dp[i][i] = freq[i];
}
// Consider chains of length 2 to n
for (int l = 2; l <= n; l++) {
for (int i = 0; i <= n - l; i++) {
// j is the ending index of the chain
int j = i + l - 1;
dp[i][j] = INT_MAX;
// Total frequency sum of keys in current range
int fsum = sum(freq, i, j);
// Try each key in range [i..j] as root
for (int r = i; r <= j; r++) {
// Cost when keys[r] is root:
// cost of left subtree + cost of right subtree + sum of frequencies
int c = ((r > i) ? dp[i][r - 1] : 0) + ((r < j) ? dp[r + 1][j] : 0) + fsum;
// Update minimum cost
if (c < dp[i][j]) {
dp[i][j] = c;
}
}
}
}
// dp[0][n-1] stores minimum cost for all keys
return dp[0][n - 1];}
//Driver Code Starts
int main() {
vector<int> keys = {10, 12, 20};
vector<int> freq = {34, 8, 50};
cout << minCost(keys, freq);
return 0;}
//Driver Code Ends
Java
//Driver Code Starts import java.util.Arrays;
public class GFG {
//Driver Code Ends
// Function to calculate sum of frequencies from index i to j
static int sum(int freq[], int i, int j) {
int s = 0;
for (int k = i; k <= j; k++)
s += freq[k];
return s;
}
// Function to calculate minimum cost of a Binary Search Tree using DP (tabulation)
static int minCost(int keys[], int freq[]) {
int n = keys.length;
// Create a 2D DP table to store minimum costs for subarrays of keys
int dp[][] = new int[n][n];
// Base case: cost of a single key is its frequency
for (int i = 0; i < n; i++)
dp[i][i] = freq[i];
// Consider chains of length 2 to n
for (int l = 2; l <= n; l++) {
for (int i = 0; i <= n - l; i++) {
// j is the ending index of the chain
int j = i + l - 1;
dp[i][j] = Integer.MAX_VALUE;
// Total frequency sum of keys in current range
int fsum = sum(freq, i, j);
// Try each key in range [i..j] as root
for (int r = i; r <= j; r++) {
// Cost when keys[r] is root:
// cost of left subtree + cost of right subtree + sum of frequencies
int c = ((r > i) ? dp[i][r - 1] : 0) + ((r < j) ? dp[r + 1][j] : 0) + fsum;
// Update minimum cost
if (c < dp[i][j])
dp[i][j] = c;
}
}
}
// dp[0][n-1] stores minimum cost for all keys
return dp[0][n - 1];
}//Driver Code Starts
public static void main(String[] args) {
int keys[] = {10, 12, 20};
int freq[] = {34, 8, 50};
System.out.println(minCost(keys, freq));
}}
//Driver Code Ends
Python
Function to calculate sum of frequencies from index i to j
def sum(freq, i, j): s = 0 for k in range(i, j + 1): s += freq[k] return s
Function to calculate minimum cost of a Binary Search Tree using DP (tabulation)
def minCost(keys, freq): n = len(keys)
# Create a 2D DP table to store minimum costs for subarrays of keys
dp = [[0 for _ in range(n)] for _ in range(n)]
# Base case: cost of a single key is its frequency
for i in range(n):
dp[i][i] = freq[i]
# Consider chains of length 2 to n
for l in range(2, n + 1):
for i in range(0, n - l + 1):
# j is the ending index of the chain
j = i + l - 1
dp[i][j] = float('inf')
# Total frequency sum of keys in current range
fsum = sum(freq, i, j)
# Try each key in range [i..j] as root
for r in range(i, j + 1):
# Cost when keys[r] is root:
# cost of left subtree + cost of right subtree + sum of frequencies
c = (dp[i][r - 1] if r > i else 0) + (dp[r + 1][j] if r < j else 0) + fsum
# Update minimum cost
if c < dp[i][j]:
dp[i][j] = c
# dp[0][n-1] stores minimum cost for all keys
return dp[0][n - 1]#Driver Code Starts
if name == 'main': keys = [10, 12, 20] freq = [34, 8, 50]
print(minCost(keys, freq))#Driver Code Ends
C#
//Driver Code Starts using System;
class GFG {
//Driver Code Ends
// Function to calculate sum of frequencies from index i to j
static int sum(int[] freq, int i, int j) {
int s = 0;
for (int k = i; k <= j; k++)
s += freq[k];
return s;
}
// Function to calculate minimum cost of a Binary Search Tree using DP (tabulation)
static int minCost(int[] keys, int[] freq) {
int n = keys.Length;
// Create a 2D DP table to store minimum costs for subarrays of keys
int[,] dp = new int[n, n];
// Base case: cost of a single key is its frequency
for (int i = 0; i < n; i++)
dp[i, i] = freq[i];
// Consider chains of length 2 to n
for (int l = 2; l <= n; l++) {
for (int i = 0; i <= n - l; i++) {
// j is the ending index of the chain
int j = i + l - 1;
dp[i, j] = int.MaxValue;
// Total frequency sum of keys in current range
int fsum = sum(freq, i, j);
// Try each key in range [i..j] as root
for (int r = i; r <= j; r++) {
// Cost when keys[r] is root:
// cost of left subtree + cost of right subtree + sum of frequencies
int c = ((r > i) ? dp[i, r - 1] : 0) + ((r < j) ? dp[r + 1, j] : 0) + fsum;
// Update minimum cost
if (c < dp[i, j])
dp[i, j] = c;
}
}
}
// dp[0,n-1] stores minimum cost for all keys
return dp[0, n - 1];
}//Driver Code Starts
static void Main() {
int[] keys = {10, 12, 20};
int[] freq = {34, 8, 50};
Console.WriteLine(minCost(keys, freq));
}}
//Driver Code Ends
JavaScript
// Function to calculate sum of frequencies from index i to j function sum(freq, i, j) { let s = 0; for (let k = i; k <= j; k++) s += freq[k]; return s; }
// Function to calculate minimum cost of a Binary Search Tree using DP (tabulation) function minCost(keys, freq) { const n = keys.length;
// Create a 2D DP table to store minimum costs for subarrays of keys
const dp = Array.from({length: n}, () => Array(n).fill(0));
// Base case: cost of a single key is its frequency
for (let i = 0; i < n; i++)
dp[i][i] = freq[i];
// Consider chains of length 2 to n
for (let l = 2; l <= n; l++) {
for (let i = 0; i <= n - l; i++) {
// j is the ending index of the chain
const j = i + l - 1;
dp[i][j] = Number.MAX_SAFE_INTEGER;
// Total frequency sum of keys in current range
const fsum = sum(freq, i, j);
// Try each key in range [i..j] as root
for (let r = i; r <= j; r++) {
// Cost when keys[r] is root:
// cost of left subtree + cost of right subtree + sum of frequencies
const c = (r > i ? dp[i][r - 1] : 0) + (r < j ? dp[r + 1][j] : 0) + fsum;
// Update minimum cost
if (c < dp[i][j])
dp[i][j] = c;
}
}
}
// dp[0][n-1] stores minimum cost for all keys
return dp[0][n - 1];}
//Driver Code Starts // Driver code const keys = [10, 12, 20]; const freq = [34, 8, 50]; console.log(minCost(keys, freq));
//Driver Code Ends
`

