Wine Problem

l
package com.thealgorithms.dynamicprogramming;

/**
 * The WineProblem class provides a solution to the wine selling problem.
 * Given a collection of N wines with different prices, the objective is to maximize profit by selling
 * one wine each year, considering the constraint that only the leftmost or rightmost wine can be sold
 * at any given time.
 *
 * The price of the ith wine is pi, and the selling price increases by a factor of the year in which
 * it is sold. This class implements three approaches to solve the problem:
 *
 * 1. **Recursion**: A straightforward recursive method that computes the maximum profit.
 *    - Time Complexity: O(2^N)
 *    - Space Complexity: O(N) due to recursive calls.
 *
 * 2. **Top-Down Dynamic Programming (Memoization)**: This approach caches the results of subproblems
 *    to avoid redundant computations.
 *    - Time Complexity: O(N^2)
 *    - Space Complexity: O(N^2) for the storage of results and O(N) for recursion stack.
 *
 * 3. **Bottom-Up Dynamic Programming (Tabulation)**: This method builds a table iteratively to
 *    compute the maximum profit for all possible subproblems.
 *    - Time Complexity: O(N^2)
 *    - Space Complexity: O(N^2) for the table.
 */
public final class WineProblem {
    private WineProblem() {
    }

    /**
     * Calculate maximum profit using recursion.
     *
     * @param arr Array of wine prices.
     * @param si  Start index of the wine to consider.
     * @param ei  End index of the wine to consider.
     * @return Maximum profit obtainable by selling the wines.
     */
    public static int wpRecursion(int[] arr, int si, int ei) {
        int n = arr.length;
        int year = (n - (ei - si + 1)) + 1;
        if (si == ei) {
            return arr[si] * year;
        }

        int start = wpRecursion(arr, si + 1, ei) + arr[si] * year;
        int end = wpRecursion(arr, si, ei - 1) + arr[ei] * year;

        return Math.max(start, end);
    }

    /**
     * Calculate maximum profit using top-down dynamic programming with memoization.
     *
     * @param arr  Array of wine prices.
     * @param si   Start index of the wine to consider.
     * @param ei   End index of the wine to consider.
     * @param strg 2D array to store results of subproblems.
     * @return Maximum profit obtainable by selling the wines.
     */
    public static int wptd(int[] arr, int si, int ei, int[][] strg) {
        int n = arr.length;
        int year = (n - (ei - si + 1)) + 1;
        if (si == ei) {
            return arr[si] * year;
        }

        if (strg[si][ei] != 0) {
            return strg[si][ei];
        }
        int start = wptd(arr, si + 1, ei, strg) + arr[si] * year;
        int end = wptd(arr, si, ei - 1, strg) + arr[ei] * year;

        int ans = Math.max(start, end);
        strg[si][ei] = ans;

        return ans;
    }

    /**
     * Calculate maximum profit using bottom-up dynamic programming with tabulation.
     *
     * @param arr Array of wine prices.
     * @throws IllegalArgumentException if the input array is null or empty.
     * @return Maximum profit obtainable by selling the wines.
     */
    public static int wpbu(int[] arr) {
        if (arr == null || arr.length == 0) {
            throw new IllegalArgumentException("Input array cannot be null or empty.");
        }
        int n = arr.length;
        int[][] strg = new int[n][n];

        for (int slide = 0; slide <= n - 1; slide++) {
            for (int si = 0; si <= n - slide - 1; si++) {
                int ei = si + slide;
                int year = (n - (ei - si + 1)) + 1;
                if (si == ei) {
                    strg[si][ei] = arr[si] * year;
                } else {
                    int start = strg[si + 1][ei] + arr[si] * year;
                    int end = strg[si][ei - 1] + arr[ei] * year;

                    strg[si][ei] = Math.max(start, end);
                }
            }
        }
        return strg[0][n - 1];
    }
}