<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:

    //O(N*2^N)
    int maxCoinsHelper(const vector<int> &nums, map<vector<int>, int> &dpMemo)
    {
        if(dpMemo.find(nums) != dpMemo.end())
            return dpMemo[nums];

        int numsCount = nums.size();
        int maxVal = 0;
        vector<int> numsTruncated;
        for(int i=0; i<numsCount; i++)
        {
            int currVal=((i>0)?nums[i-1]:1) * nums[i] * ((i+1<numsCount)?nums[i+1]:1);
            if(numsCount>1)
            {
                numsTruncated.resize(numsCount-1);
                for(int j=0, k=0; j<numsCount; j++)
                {
                    if(i!=j)
                        numsTruncated[k++]=nums[j];
                }
                currVal+= maxCoinsHelper(numsTruncated, dpMemo);
            }
            maxVal=max(currVal, maxVal);
        }
        dpMemo[nums]=maxVal;
        return maxVal;
    }


    int maxCoinsHelperOpt(const vector<int> &nums, const int &numsCount, vector<int> &dpMemo, int startIndex, int endIndex)
    {
        if(startIndex>endIndex)
            return 0;
        
        int index = startIndex * (numsCount) + endIndex;
        //cout<<startIndex<<" "<<endIndex<<" "<<index<<endl;

        if(dpMemo[index]>=0)
            return dpMemo[index];     

        int maxVal = 0;
        for(int i=startIndex; i<=endIndex; i++)
        {
            //int currVal = nums[i-1] * nums[i] * nums[i+1];
            int currVal = nums[startIndex-1] * nums[i] * nums[endIndex+1];  //these index are important. We are going backward
            currVal+= maxCoinsHelperOpt(nums, numsCount, dpMemo, startIndex, i-1);
            currVal+= maxCoinsHelperOpt(nums, numsCount, dpMemo, i+1, endIndex);
            maxVal=max(currVal, maxVal);
        }
        dpMemo[index]=maxVal;
        return maxVal;
    }

    int maxCoins(vector<int>& nums) 
    {
        //map<vector<int>, int> dpMemo;        
        //return maxCoinsHelper(nums, dpMemo);

        nums.push_back(1);
        nums.insert(nums.begin(), 1);
        int numsCount = nums.size();
        vector<int> dpMemo(numsCount*numsCount,-1);
         return maxCoinsHelperOpt(nums, numsCount, dpMemo, 1, numsCount-2);
        
    }
};
11600