<p>You are given <code>n</code> balloons, indexed from <code>0</code> to <code>n - 1</code>. Each balloon is painted with a number on it represented by an array <code>nums</code>. You are asked to burst all the balloons.</p>

<p>If you burst the <code>i<sup>th</sup></code> balloon, you will get <code>nums[i - 1] * nums[i] * nums[i + 1]</code> coins. If <code>i - 1</code> or <code>i + 1</code> goes out of bounds of the array, then treat it as if there is a balloon with a <code>1</code> painted on it.</p>

<p>Return <em>the maximum coins you can collect by bursting the balloons wisely</em>.</p>

<p>&nbsp;</p>
<p><strong class="example">Example 1:</strong></p>

<pre>
<strong>Input:</strong> nums = [3,1,5,8]
<strong>Output:</strong> 167
<strong>Explanation:</strong>
nums = [3,1,5,8] --&gt; [3,5,8] --&gt; [3,8] --&gt; [8] --&gt; []
coins =  3*1*5    +   3*5*8   +  1*3*8  + 1*8*1 = 167</pre>

<p><strong class="example">Example 2:</strong></p>

<pre>
<strong>Input:</strong> nums = [1,5]
<strong>Output:</strong> 10
</pre>

<p>&nbsp;</p>
<p><strong>Constraints:</strong></p>

<ul>
	<li><code>n == nums.length</code></li>
	<li><code>1 &lt;= n &lt;= 300</code></li>
	<li><code>0 &lt;= nums[i] &lt;= 100</code></li>
</ul>

class Solution {
public:
    // Main idea is to start from last removed element
    //  It allows us reuse left and right parts in a recursive way
    //  For example, if we have the folliwng ballons [2,3,4,5] - if we remore 2,3 last in any order,
    //  the first 3[4,5]1 always produce the same result
    //  Or if we try to remove 3 last, then our formula will be the following:
    //  1[2]3 + 1[3]1 + 3[4,5]1. First and second operands are reused for other operations
    // The common formula is the following
    // f[a,b,c.. d]l = max(f[a]l + a[b,c..d]l, f[b]l + f[a]b + b[c..d]l)
    // Let's try to solve via bottom up approach - initally calculate values for single elements array, than for
    //  double arrays, and so on
    // Time complexity is O(N^3)
    // Space complexity is O(N^2)
    int maxCoins(vector<int>& nums) { // [2,3,4]
        // element [a][b] represents a max sum of the subarray that starts at b_th element with length a
        std::vector<int> subarrays_max_table (nums.size() * nums.size(), 0);

        for (auto subarray_len = 1; subarray_len <= nums.size(); ++subarray_len) { // 3 <= 3
            for (auto subarray_start = 0; subarray_start <= (nums.size() - subarray_len); ++subarray_start) { // 0 < 0
                const int left_border_id = subarray_start - 1; // -1
                const int right_border_id = subarray_start + subarray_len; // 3

                const int left_border = 0 <= left_border_id ? nums.at(left_border_id) : 1; // 1
                const int right_border = right_border_id < nums.size() ? nums.at(right_border_id) : 1; // 1

                auto& subarray_max = subarrays_max_table.at((subarray_len - 1) * nums.size() + subarray_start); // 36
                for (auto last_remove = 0; last_remove < subarray_len; ++last_remove) { // 2 < 3
                    // Calculate last remove operation coins
                    auto branch_coins = left_border * nums.at(subarray_start + last_remove) * right_border; // 36

                    // Calculate left subarray
                    if (last_remove != 0) {
                        const auto left_subarray_size = last_remove; // 2
                        const auto left_subarray_start = subarray_start; // 0
                        branch_coins += subarrays_max_table.at((left_subarray_size - 1) * nums.size() + left_subarray_start); // 4 += 32
                    }

                    // Calculate right subarray
                    if (last_remove != (subarray_len - 1)) {
                        const auto right_subarray_size = subarray_len - 1 - last_remove; // 1
                        const auto right_subarray_start = subarray_start + last_remove + 1; // 2
                        branch_coins += subarrays_max_table.at((right_subarray_size - 1) * nums.size() + right_subarray_start); // 9 += 12
                    }
                    subarray_max = std::max(subarray_max, branch_coins);
                }
            }
        }

        return subarrays_max_table.at((nums.size() - 1) * nums.size()); // 36
    }
};
11500