Wine Selling
Introduction
Today, we will have a walkthrough of a classical interview problem named Wine Selling Problem. This question has come up in several coding interviews of big productbased companies like Amazon, Microsoft, Adobe etc.
Let’s dig deeper into the problem statement first and then the solutions ranging from brute force to optimal.
Problem Statement
Given a row of n wines, with integers representing the cost of each wine. You can sell the first or last wine in a row every year. The cost of wine, on the other hand, rises over time. Let the initial profits from the wines be P1, P2, P3…Pn. In the Yth year, the profit from the ith wine will be Y*Pi.
The goal is to calculate the maximum profit that can be made by selling all of the wines.
Input: WinePrice: 2 4 6 2 5 Output: 64 
Explanation:
price = 2*1 = 2, remaining WinePrice = [ 4, 6, 2, 5 ], Profit = 2 price = 5*2 = 10, remaining WinePrice = [ 4, 6, 2 ], Profit = 12 price = 2*3 = 6, remaining WinePrice = [ 4, 6], Profit = 18 price = 4*4 = 16, remaining WinePrice = [ 6 ], Profit = 34 price = 6*5 = 30, remaining WinePrice = [ ], Profit = 64 
Approach 1 Brute Force
After reading the explanation above, your first thought might be to use a greedy approach, that is, to check both ends and always sell the cheaper wine.
Ok, let’s try the greedy approach!
So let’s again consider the same array WinePrice: [2,4,6,2,5]
And if proceed greedily 
price = 1*2 = 2, remaining WinePrice = [ 4, 6, 2, 5 ], Profit = 2 price = 2*4 = 8, remaining WinePrice = [ 6, 2, 5 ], Profit = 10 price = 3*5 = 15, remaining WinePrice = [ 6, 2 ], Profit = 25 price = 4*2 = 8, remaining WinePrice = [ 6 ], Profit = 33 price = 5*6 = 30, remaining WinePrice = [ ], Profit = 63 
As a result, the total profit obtained after selling all the wines is 63.
Well, Do you think it’s the correct answer?
To the above example, the actual profit is 64 as already explained in the problem statement.
Why does the greedy approach fail?
In a greedy approach, we choose the best solution at each step. The greedy solution does not guarantee a globally optimal solution. As a result, by selecting the cheaper wine from each end every time, we may be able to miss the profit from the remaining unsold wines.
Now, if we pay close attention to each step, we have two options:
choose the first wine or choose the last wine.
We can use Recursion to accomplish this goal, as we need to consider all the possibilities.
Let's examine the recursion tree below.
Source:medium
 Here each level represents the year of selling the wine.
 Suppose we selected the WinePrice[0], so the profit will be profit = level*WinePrice[0].
 After selecting WinePrice[0], we are left with WinePrice[1..4]. We again have two choices, either select WinePrice[1] or select WinePrice[4].
 The same recursion will happen for the remaining wine bottles.
 Thus, in each recursion, we will select the subtree that will give us the maximum profit.
C++ Implementation
#include <bits/stdc++.h> using namespace std; int solve(int WinePrice[], int i, int j, int year) { if(i>j) return 0; if(i==j) return WinePrice[i]*year; int left = WinePrice[i]*year + solve(WinePrice,i+1,j,year+1); int right = WinePrice[j]*year + solve(WinePrice,i,j1,year+1); return max(left,right); } int findmaxProfit(int WinePrice[], int n) { return solve(WinePrice,0,n1,1); } // Driver code int main() { // Price array int WinePrice[] = { 2, 4, 6, 2, 5 }; int n = sizeof(WinePrice) / sizeof(WinePrice[0]); int ans = findmaxProfit(WinePrice, n); cout << ans << endl; return 0; } 
Output:
64 
Complexity Analysis
Time Complexity  O(2^{n})
Reason: Because we either take the endpoint in the solution or do not take the endpoint, giving us two options in all cases.
Space Complexity  O(N)
The space is occupied by recursion stack and at max we we have n call in the recursion stack.
Can we optimise it further?
Yes!
The recursion tree shows that there are many overlapping subproblems.
Next, we will use the memoization technique to solve this problem because it allows us to reduce time complexity.
Let's see how things go.
Approach 2 Memoization
 Create a 2D array of size(N*N) where N is the length of the WinePrice array.
 This array will store the maximum profit for selling the wines.
 Assign the 2D array with initial values of 1, which means the profit is not calculated yet.
 The idea is to save the optimal action for each state and then use it to navigate through the optimal states beginning with the initial state.
C++ Implementation
#include <bits/stdc++.h>

Output:
64 
Complexity Analysis
Time Complexity  O(N^{2})
Reason: Because, using the memoization technique, when we calculate the profit, we save it in the array so that if we need to calculate it again the next time, we can reuse it and save time and space.
Space Complexity  O(N^{2})
Reason: Because we created a 2D array of size(N*N) where N is the length of the WinePrice array. This array will store the maximum profit for selling the wines.
Approach 3: Dynamic Programming
 Create a 2D array of size(N*N) where N is the length of the WinePrice array.
 This array will store the maximum profit for selling the wines.
 Assign the 2D array with initial values of WinePrice.length * WinePrice[i], i.e dp[i][i] = WinePrice.length * WinePrice[i] which means if we sold the wine at the end of Yth year.
 Now start iterating from backwards i.e WinePrice.length2 and for every index, i run an inner loop say j from i+1 to WinePrice.length.
 Why so?
 Remember you can sell the first or last wine in a row every year.
 So suppose you picked any wine on index i, you have the choice only to sell it in the upcoming years i.e. from i+1, you can’t go in the past years.
 The year for index i would be WinePrice.length  (ji).
 Since we know in dynamic programming, the idea is to save the optimal action for each state and then use it to navigate through the optimal states beginning with the initial state.
 Here we have to perform three steps as follows:
 left = y*arr[i] + dp[i+1][j]
 right = y*arr[j] + dp[i][j1]
 dp[i][j] = Math.max(left, right)
 Finally return dp[0][WinePrice.length1]
C++ Implementation
#include <bits/stdc++.h> using namespace std; int findmaxProfit(int winePrice[], int n) { int dp[n][n]; for(int i = 0; i < n; i++){ dp[i][i] = n * winePrice[i]; } for(int i = n2; i >= 0; i){ for(int j = i+1; j < n; j++){ int y = n (ji); int left = y * winePrice[i] + dp[i+1][j]; int right = y * winePrice[j] + dp[i][j1]; dp[i][j] = max(left, right); } } return dp[0][n1]; } // Driver code int main() { // Price array int winePrice[] = { 2, 4, 6, 2, 5 }; int n = sizeof(winePrice) / sizeof(winePrice[0]); int ans = findmaxProfit(winePrice, n); cout << ans << endl; return 0; } 
Output:
64 
Complexity Analysis
Time Complexity  O(N^{2})
Reason: Because we are using two nested loops for finding the maximum profit for selling the wines.
Space Complexity  O(N^{2})
Reason: Because we created a 2D array of size(N*N) where N is the length of the WinePrice array. This array will store the maximum profit for selling the wines.
Now, if you are looking for a RoadMap to Master Dynamic Programming  Watch this video by Parikh Jain, cofounder of Coding Ninjas.
Frequently asked questions
1. What are different ways to solve the Wine Selling problem?
The Wine Selling problem can be solved using two methods Recursion and Dynamic Programming.
2. What are the features of dynamic programming?
The main features of dynamic programming are:
 To solve a problem optimally if it has overlapping subproblems and optimal substructure.
 To explore all the possibilities to achieve an optimal solution.
 To reduce time complexity.
3. Is math dynamic programming?
Dynamic programming is both a mathematical optimization method and a computer programming method.
4. What is the basic principle of dynamic programming?
Dynamic Programming relies on the principle of optimality. It explores all the possibilities, finds the base cases, starts with the small solutions, and then works up to reach the answer we want.
5. Is dynamic programming hard?
Dynamic programming is considered mysterious and counterintuitive among programmers, but practising many questions can make it easy for you to develop a framework for approaching dynamic programming questions.
Key takeaways
Here we learned one of the most important and frequently asked questions in Amazon, Microsoft, and other productbased companies, i.e. Wine Selling Problem. We discussed various approaches ranging from brute force to optimal along with the C++ code for your better understanding.
Some other interesting array problems frequently encountered in interviews are Longest Increasing Subsequence, Minimum Jumps To Reach End, Loot Houses, Min Cost path, 01 Knapsackand many more.
Give it a shot yourself and try out these problems here on CodeStudio.
Perfection comes from consistent and deliberate practice,
So don’t stop practising and If you feel the need for expert guidance to learn more concepts, check out our DSA courses by starting your free trial today.
Comments
No comments yet
Be the first to share what you think