Showing posts with label Recursion. Show all posts
Showing posts with label Recursion. Show all posts

Sunday, September 13, 2020

Leetcode 1448: Count Good Nodes in Binary Tree

 https://leetcode.com/problems/count-good-nodes-in-binary-tree/description/



Notes:

This question is quick straightforward: top-down recursion.

And we need to pass down the maximum value so far during recursion.

Please see the code below:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int goodNodes(TreeNode* root) {
        return !root ? 0 : dfs(root, root->val);
    }
private:
    int dfs(TreeNode* r, int val) {
        return !r ? 0 : (r->val >= val ? 1 : 0) + dfs(r->left, max(r->val, val)) + dfs(r->right, max(r->val,val));
    }
};

Saturday, September 12, 2020

Leetcode 1585: Check If String Is Transformable With Substring Sort Operations

 https://leetcode.com/problems/check-if-string-is-transformable-with-substring-sort-operations/description/


Notes:

There are only 10 types of letters, so one way is that we can handle them one by one.

One of the key observations for sorting sub-string (or any type of ascending sorting): the id of the largest element will no way get smaller (or the position cannot be moved forward in an ascending sort).

Based on this, we can just start with the largest letter, then the second largest one, ... until all done.

Let take '9' as an example, 

1): say there are 3 '9's in s and also 3 in t (if not, return false directly);

2): then the id of the 1st '9' in s should be <= the id of the 1st '9' in t (if not, return false. Why? since there is no way the id of the 1st '9' in s becomes smaller due to sorting (or there is no bigger letter than '9'));

3): then the id of the 2nd '9' in s should be <= the id of the 2nd '9' in t (if not, return false);

...

If all satisfy, then we can remove all '9' from s and t, then recursively run the same procedure but with a smaller size.

After removing all '9's, the next one is '8', ...

If all can be removed, return true.

[update: 2024/11/27] 
As commented @Kaisar below, there is a case ("891", "198") cannot pass.

We can just apply the same logic but with the reversed order: starting from '0', then the id of the smallest element will no way get larger.

When the results from both directions are true, return true. 

See the code below:

class Solution {
public:
    bool isTransformable(string s, string t) {
        string s1 = s, t1 = t;
        // check ids of elements from largest to smallest;
        for(char c='9'; c>='0'; --c) {
            vector<int> ids1, ids2;
            for(int i=0; i<s1.size(); ++i) {
                if(s1[i] == c) ids1.push_back(i);
                if(t1[i] == c) ids2.push_back(i);
            }
            if((int)ids1.size() != (int)ids2.size()) return false;
            for(int i=0; i<ids1.size(); ++i) {
                if(ids1[i] > ids2[i]) return false;
            }
            string s2 = "", t2 = "";
            for(auto &a : s1) {
                if(a < c) s2.push_back(a);
            }
            for(auto &a : t1) {
                if(a < c) t2.push_back(a);
            }
            s1 = s2;
            t1 = t2;
        }
        // check ids of elements from smallest to the largest;
        s1 = s, t1 = t;
        for(char c='0'; c<='9'; ++c) {
            vector<int> ids1, ids2;
            for(int i=0; i<s1.size(); ++i) {
                if(s1[i] == c) ids1.push_back(i);
                if(t1[i] == c) ids2.push_back(i);
            }
            if((int)ids1.size() != (int)ids2.size()) return false;
            for(int i=0; i<ids1.size(); ++i) {
                if(ids1[i] < ids2[i]) return false;
            }
            string s2 = "", t2 = "";
            for(auto &a : s1) {
                if(a > c) s2.push_back(a);
            }
            for(auto &a : t1) {
                if(a > c) t2.push_back(a);
            }
            s1 = s2;
            t1 = t2;
        }
        return true;
    }
};

Sunday, September 6, 2020

Leetcode 756: Pyramid Transition Matrix

 https://leetcode.com/problems/pyramid-transition-matrix/description/


Notes:

This question can be solved by recursion. 

The first step is to convert the bottom to a next layer which has a smaller size  than the bottom layer by 1. The we have to solve the same question but with a size smaller by 1. So it is a typical recursion question.

However, when we convert the bottom to the next layer, there may be more than one possibilities. So we need to check all of them until find a valid one (only one element left).

Once a valid one is found, return true.

If we go through all the possibilities, but still do not find a valid one, return false.

See the code below:

class Solution {
public:
    bool pyramidTransition(string bottom, vector<string>& allowed) {
        unordered_map<string, vector<char>> mp;
        for(auto &a : allowed) {
            string s = "";
            s.push_back(a[0]);
            s.push_back(a[1]);
            mp[s].push_back(a[2]);
        }
        bool find = false;
        return dfs(bottom, mp, find);
    }
private:
    bool dfs(string& bs, unordered_map<string, vector<char>>& mp, bool& find) {
        if(find || bs.size() == 1) return true;
        vector<string> ns;
        dfs1(bs, 0, "", ns, mp);
        for(auto &s : ns) {
            if(s.size() + 1 != bs.size()) return false;
            if(dfs(s, mp, find)) {
                find = true;
                return true;
            }
        }
        return false;
    }
    void dfs1(string&bs, int id, string temp, vector<string>& ns, unordered_map<string, vector<char>>& mp) {
        if(id+1== bs.size()) {
            if(temp.size() + 1 == bs.size()) ns.push_back(temp);
            return;
        }
        string s = "";
        s.push_back(bs[id]);
        s.push_back(bs[id+1]);
        if(!mp.count(s)) return;
        for(auto &c : mp[s]) {
            //temp.push_back(c);
            dfs1(bs, id+1, temp+c, ns, mp);
            //temp.pop_back();
        }
    }
};

Here is another way to code, which is much shorter (and faster, and also easy to understand...):

class Solution {
public:
    bool pyramidTransition(string bottom, vector<string>& allowed) {
        unordered_map<string, vector<char>> mp;
        for(auto &a : allowed) mp[a.substr(0, 2)].push_back(a[2]);
        return dfs(bottom, mp, 0, "");
    }
private:
    bool dfs(string& bs, unordered_map<string, vector<char>>& mp, int id, string next) {
        if(bs.size() == 1) return true;
        if(id + 1 == bs.size()) return dfs(next, mp, 0, "");
        for(auto &a : mp[bs.substr(id, 2)]) {
            if(dfs(bs, mp, id+1, next + a)) return true;
        }
        return false;
    }
};

Leetcode 257: Binary Tree Paths

 https://leetcode.com/problems/binary-tree-paths/description/



Notes:

This question is a typical warm-up question in the interview. So the key is to get it done clearly and quickly. (and bug-free)

Since it is a tree, so recursion is the first choice. 

And it requests to print the path, so dfs is a better choice (than bfs). 

Then ending condition (base case) is when the node is a leaf (both left and right are null).

A hidden ending condition is when the current node is null.

See the code below:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> res;
        dfs(root, "", res);
        return res;
    }
private:
    void dfs(TreeNode* r, string s, vector<string>& res) {
        if(!r) return;
        if(!r->left && !r->right) res.push_back(s + to_string(r->val));
        dfs(r->right, s + to_string(r->val) + "->", res);
        dfs(r->left, s + to_string(r->val) + "->", res);
    }
};

Saturday, September 5, 2020

Leetcode 1104: Path In Zigzag Labelled Binary Tree

https://leetcode.com/problems/path-in-zigzag-labelled-binary-tree/description/



Notes:

This question is mainly for deriving a formula from observation, so it is not on the capability for coding...

Here are some of the key facts:

1): each layer has 2^n elements, where n is the layer number starting with 0;
2): if we have a label with value as val, then its layer is log2(val);
3): for each layer, the label range from [2^n, 2^(n+1) - 1];

The key is to find the parent element given the val.

4): at the same layer, the number of smaller elements are: rd = val - 2^n;
5): on the parent layer (if it has parent layer), there are (rd/2) more smaller elements smaller than its parent element.

Thus its parent value is:  val - rd - rd/2 - 1.

After having this information, we solve this question with recursion: the base case is that when the val == 0, we should stop.

See the code below:

class Solution {
public:
	vector<int> pathInZigZagTree(int label) {
        vector<int> res;
        dfs(res, label);
        reverse(res.begin(), res.end());
        return res;
    }
private:
    void dfs(vector<int> &r, int v) {
        if(v==0) return;
        r.push_back(v);
        int n = floor(log2(v)), rd = v - pow(2, n);
        dfs(r, v-rd-rd/2-1);
    }
};

Sunday, August 30, 2020

Leetcode 1569. Number of Ways to Reorder Array to Get Same BST

 https://leetcode.com/problems/number-of-ways-to-reorder-array-to-get-same-bst/description/


Notes:

If I say if you know how to solve the walking robot question, then you should know how to solve this question, you will not believe it. Will you? 

If you do know how to solve the robot question, but still cannot solve this one, no worry, let start this question step by step.

This question can be transformed to a similar question with a smaller size. Thus it can be solved recursively or using dp for optimization.

Let us look at an example: [3, 4, 5, 1, 2]
in the first step: the left sub tree = [1, 2], and the right sub tree = [4, 5]
then in order to solve the original question, we need to solve the same question for these two sub trees.

Now let us work on the relation from the sub tree to the root tree. If we know there are x ways for the left sub tree, and y ways for the right sub tree. Then the question becomes how many ways for the root tree?

The first response is that: if left sub tree and right sub tree cannot be mixed, then there will be x*y ways for root tree. However, the left sub tree can mix with the right sub tree. And this is why this question is labeled as hard.

Let us continue to use the above example: there are 2 elements in the left sub tree and 2 as well in the right. As an initial try, we can place 1 from the left to three different positions: before 4, before 5, after 5, which are listed below:
1): [1, 4, 5]
2): [4, 1, 5]
3): [4, 5, 1]

Then we need to place 2 from left to each of the above three cases: the rule is that 2 has to be placed after 1
Thus, 
for 1): there are 3 places for 2; 
for 2): there are 2 places for 2;
for 3): there are 1 place for 2.

In total, there are 3 + 2 + 1 = 6 ways. Based on the question example, we need to remove the original one, thus the answer is 5.

So one of an important observations for mixing is that: for a fixed left and right array (or serialized tree sequence), the relative order of the left and right needs to be maintained. Then this question becomes a pure combination/permutation problem!

Let say we have m elements in the left, n elements in the right, and we are going to mix them, and we need to keep the relative orders of the elements in the left and right for the mixing. Then the answer is: (m+n)!/(m!*n!). So basically it is the full permutation of all these elements, divided by the full permutation of m and full permutation of n (since we have to keep the relative orders in the left and right.)

So you can use mathematical formula directly to calculate this one, but for fun, let us look at a different way to solve it using programming.

If you by any chance be aware of another question: walking robot (from most left up corner to the most right down corner, and can only walk down or right one step a time), then the ways of mixing is the same as the ways of the robot can go from the most left up corner to the most right down corner!!!

So we can use dp to solve the ways of mixing: dp[i][j] = dp[i][j-1] + dp[i-1][j]

With mixing solved, we need to look at back to the original question: there are x*y ways for the left and right without mixing; if the mixing is allowed, then there are x*y*dp[i][j], where i and j are the element numbers in the left sub tree and the right sub tree, respectively.

If you still have question, please leave it below. Any comment/discussion is welcome!

See the code below:

class Solution {
public:
    int numOfWays(vector<int>& nums) {
        int n = nums.size();
        vector<vector<long>> dp(n, vector<long>(n, 1));
        for(int i=1; i<n; ++i) {
            for(int j=1; j<n; ++j) {
                dp[i][j] = dp[i][j-1] + dp[i-1][j];
                dp[i][j] %= mod;
            }
        }
        return (dfs(nums, dp) - 1)%mod;
    }
private:
    int mod = 1e9+7;
    long dfs(vector<int>& nums, vector<vector<long>>& dp) {
        if(!nums.size()) return 1;
        vector<int> left, right;
        for(int i=1; i<nums.size(); ++i) {
            if(nums[i]>nums[0]) right.push_back(nums[i]);
            else left.push_back(nums[i]);
        }
        long lt = dfs(left, dp)%mod, rt = dfs(right, dp)%mod;
        long res =lt*rt%mod;
        return res*dp[left.size()][right.size()]%mod;
    }
};
 

Sunday, December 8, 2019

LeetCode 1273: Delete Tree Nodes


https://leetcode.com › problems › delete-tree-nodes


Notes:

This is a good question.

Similar to the questions of binary search tree, we need to go through every sub-tree, to check the sum of it. If it equals to 0, all the node on that sub-tree can be deleted; otherwise, we need to return the number of nodes.

So the question becomes to calculate the sum and count the node number of every sub-tree. And this can be done by bottom-up recursion.

Since it is not a binary tree, we need to re-construct the tree first by using the information from parents and values.

The key information is: for each node (root of the sub-tree), we need to save all its children nodes.

After having this information, we can run a bottom-up recursion, and return two parameters: one is the sum, and the other is the node number.

See the code below:

class Solution {
public:
    int deleteTreeNodes(int nodes, vector<int>& parent, vector<int>& value) {
        int n = parent.size();
        map<int, vector<int>> mp;//<root, <kids>>
        for(int i=0; i<n; ++i) {
            mp[parent[i]].push_back(i);
        }
        auto res = dfs(-1, mp, value);
        return res.second;
    }

private:
    pair<int, int> dfs(int p, map<int, vector<int>>& mp, vector<int>& val) {
        int sum = p>=0 ? val[p] : 0, ct = p>=0 ? 1 : 0;
 if(mp.count(p)) {
            for(auto &a : mp[p]) {
                auto sub = dfs(a, mp, val);
                if(sub.first != 0) {
                    sum += sub.first;
                    ct += sub.second;
         }
            }
        }
        if(sum == 0) return {0, 0};
        return {sum, ct};
    }
};

Wednesday, December 4, 2019

Leetcode 508: Most Frequent Subtree Sum

https://leetcode.com/problems/most-frequent-subtree-sum/description/



Notes:

We need a  hash table to count the frequencies of each possible sum. Once we have this information, then we can easily find the one(s) with the maximum frequency.

So the problem becomes how to effectively find the sum of all the subtrees.

Bottom-up recursion method would be a good choice: if we have calculated the sums of both the left and the right parts, then we just need to add them with the value of the root to get the total sum of the this sub-tree. Bottom-up recursion naturally gives the sums of all the subtrees in the tree.

The T-C is O(N), and the S-C is O(N) too. N is the number of nodes in the tree.

See the code below:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */

class Solution {
public:
    vector<int> findFrequentTreeSum(TreeNode* root) {
        vector<int> res;
        ct = 0;
        dfs(root);
   //     cout<<ct<<endl;
        for(auto &a : mp) {
            if(a.second == ct) res.push_back(a.first);
        }
        return res;
    }

private:
    unordered_map<int, int> mp; //<sum, count>
    int ct;
    int dfs(TreeNode* r) {
        if(!r) return 0;
        int a = dfs(r->left), b = dfs(r->right), sum = a + b + r->val;
        ct = max(ct, ++mp[sum]);
        return sum;
    }
};

Saturday, November 2, 2019

Leetcode 979: Distribute Coins in Binary Tree

https://leetcode.com/problems/distribute-coins-in-binary-tree/description/


Notes:

This question can be solved by recursion, which is usually employed for tree questions.

One way to think is using post-order recursion.

First of all, we need to figure out what should be returned. It should be the coins number from the left  + right + root->val - 1. Minus 1 because we need to keep one at root.

Then next, we should to ask: what is the moves required? If there are more coins than needed in a subtree, we need to move; but if there are less than needed, we also need to move!

Thus, we need to calculate the absolute values for the number of moves.

See the code below:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */

class Solution {
public:
    int distributeCoins(TreeNode* root) {
        res = 0;
        helper(root);
        return res;
    }

private:
    int res;
    int helper(TreeNode* root) {
        if(!root) return 0;
        int l = helper(root->left), r = helper(root->right);
        res += abs(l) + abs(r);
     //   cout<<res<<endl;
        return l + r + root->val - 1;
    }
};

Thursday, October 3, 2019

Leetcode 241: Different Ways to Add Parentheses

https://leetcode.com/problems/different-ways-to-add-parentheses/description/


Notes:

This question could be a very good interview question. Of course, if you cannot come up with the divide-and-conquer idea, it could be overwhelming.

We can apply () any place we want, it means we can divide it any place. So it is a question designed for the very fundamental and very powerful divide-and-conquer concept.

After dividing, we has successfully convert the original problem to a same problem but with a smaller size, so it can be solved by recursion.

For each divided part, we will calculate all the possible results from the expression, just like the original question.

So the basic case it when we have 0 element, we simply return an empty vector, {};

When we have only 1 element, we simply return a vector with that element, {v[i]};

When we have more 1 element, we need to introduce the operators between the numbers.

After we obtain all the possible results for each divided part, we can again connect them back using the operator between them. The is like the merging back concept after dividing them.

See the code below:

class Solution {
public:
    vector<int> diffWaysToCompute(string input) {
        int len = input.size();
        vector<int> nums;
        for(int i=0; i<len; ){//convert the input to array
            if(input[i] >='0' && input[i] <='9'){
                int num = 0;
                while(i<len && input[i] >='0' && input[i] <='9') {
                    num = num*10 + (input[i++] - '0');
                }
                nums.push_back(num);
            }
            if(i<len && input[i] == '+' || input[i] == '-' || input[i] == '*'){
                if(input[i] == '+') nums.push_back(-1);
                else if(input[i] == '-') nums.push_back(-2);
                else nums.push_back(-3);
                ++i;
            }
        }
        return dac(nums, 0, nums.size());
    }

private:
    vector<int> dac(vector<int> &ns, int left, int right) {
        if(left >= right) return {};
        if(left + 1 == right) return {ns[left]};
        vector<int> res;
        for(int i=left; i<right; ++i){
            if(ns[i] == -1 || ns[i] == -2 || ns[i] == -3){
                vector<int> v1 = dac(ns, left, i);//divide and conquer
                vector<int> v2 = dac(ns, i+1, right);
                for(int &a : v1) {//all possible results
                    for(int &b : v2) {
                        if(ns[i] == -1) res.push_back(a+b);
                        else if(ns[i] == -2) res.push_back(a-b);
                        else res.push_back(a*b);
                    }
                }
            }
        }
        return res;
    }
};

Follow up:

It is said it is one phone interview from Google: given a array with integers, add operators, +, -, *, /, or () any places legally. Return all the possible results after the operations.

So the core algorithm used is in the above dac() function. For, /, we need to deal with 0. or data type. And we can discuss with the interviewer for these considerations.

Sunday, September 1, 2019

Leetcode 316: Remove Duplicate Letters

https://leetcode.com/problems/remove-duplicate-letters/description/

Notes:

This question is same as Leetcode 1081, which essentially asks for the same thing.

The native response is to find the smaller[i] at the most-left position first. But before the smaller s[i], when we meet with a larger letter, we need to know whether there are still some in the remaining string after the smaller[i]. If true, we can simply ignore the larger one temporally since there are still some in the remaining part.

How do we know whether there are still some in the remaining string after the smaller[i]? One way is that we can count the number of each letter firstly. Then as scanning through the string, we know exactly how many are left to each letter.

See the code below:

class Solution {
public:
    string removeDuplicateLetters(string s) {
        string res = "";
        vector<int> ct(26, 0), used(26, 0);
        for(auto &a : s) ++ct[a-'a'];
        for(auto &a : s) {
            --ct[a-'a'];
            if(used[a-'a']++ >0) continue;//once already in the res, no need to run again
            while(res.size() && res.back() > a && ct[res.back() - 'a']) {
                used[res.back() - 'a'] = 0;//once pop, is not in the res anymore
                res.pop_back();
            }
            res.push_back(a);
        }
        return res;
    }
};


Another way is to solve this problem recursively: Scan the array greedily, we need to localize the most-left smallest s[i]. But as analyzed above, the requirement for continuing the scanning of the string is that, there are still some in the remaining part of the string. Once this condition become non valid, we can

1): put s[i] into the answer;
2): remove all the elements before s[i]; (why? because there are still some after s[i])
3): remove all the rest s[i] in the remaining string;
4): recursive check the remaining string.

See the code below:

class Solution {
public:
    string removeDuplicateLetters(string s) {
        if(s.size() < 2) return s;
        vector<int> ct(26, 0);
        for(auto &a : s) ++ct[a-'a'];
        int pos = 0;
        for(int i=0; i<s.size(); ++i) {
            if(s[i] < s[pos]) pos = i; //the most-left smallest s[i];
            if(--ct[s[i]-'a'] == 0) break; //no more left in the remaining string.
        }
        for(int i=pos+1; i<s.size(); ++i) {//remove extra s[i]
            if(s[i] == s[pos]) {
                s.erase(i, 1);
                --i;
            }
        }
        return s[pos] + removeDuplicateLetters(s.substr(pos+1));
    }
};

The time complexity of the above recursive code is till O(N) or O(26*N). (each letter at most check 1 time)

Friday, August 2, 2019

LeetCode 156: Binary Tree Upside Down

https://leetcode.com/problems/binary-tree-upside-down/


Notes:
For tree-related questions, we usually can think about recursion. To this question, one way to think about it is that if we can finish f(root->left), then we just need to set root->left->right to the root, root->left->left to the root->right. and set root->left = NULL, and root->right = NULL. Done.

This is a bottom-up recursion.

See the code below:

class Solution {
public:
    TreeNode *upsideDownBinaryTree(TreeNode *root) {
        if (!root || !root->left) return root;//base case
        TreeNode *l = root->left, *r = root->right;
        TreeNode *res = upsideDownBinaryTree(l);
        l->left = r;
        l->right = root;
        root->left = NULL;
        root->right = NULL;
        return res;
    }
};


A second way to solve this problem: iterative scan in a top-down fashion. Actually it is very similar to reverse a LinkedList.

See the code below:

class Solution {
public:
    TreeNode *upsideDownBinaryTree(TreeNode *root) {
        TreeNode *cur = root, *pre = NULL, *next = NULL, *tmp = NULL;
        while (cur) {
            next = cur->left;
            cur->left = tmp;
            tmp = cur->right;
            cur->right = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};

Sunday, July 28, 2019

Leetcode 1140. Stone Game II

https://leetcode.com/problems/stone-game-ii/description/


Notes:

This is a question about optimal games, so usually it is about the so-called minMax algorithm. Similar questions have been seen before frequently, like two players pick up coins optimally from an array of coins.

But for this question, they can only pick up from one side, which means one index should be enough to record the process, since the other side is always the end. The second parameter is M, or the maximum number of piles that one player can pick up.

If Alex pick up 1 pile, then the parameters becomes (1, 1) for Lee, where the first one is index and the second one is M. Then Lee can either pick one pile or two piles (if there are two piles). Then it backs to Alex.

Similarly, if Alex pick up 2 pile, then the parameters becomes (2, 2) for Lee, where the first one is index and the second one is M. Then Lee can make a choice from 1 to 4 piles (if there are enough piles). Then it backs to Alex.

The key of the minMax algorithm is that Alex will always choose the maximum among all his choices (the same as Lee), and then Lee will always leave the minimum of the rest choices to Alex when it switches back to Alex (the same as Alex leaves to Lee). 

Obviously, when one player make one move, the size the question becomes smaller and it is the still the same question. This is exactly when we can apply the recursion trick.

To reduce redundant calculation, we can choose either dp or memorization. The purpose is the same: we only need to calculation once for one case.

See the code below using memorization:

class Solution {
public:
    int stoneGameII(vector& piles) {
        int len = piles.size();
        vector> memo(len, vector(len, -1));
        dfs(piles, 0, 1, memo);
        return memo[0][1];
    }
private:
    int dfs(vector &ps, int x, int m, vector> &ms) {
        if(x>=ps.size()) return 0;
        if(ms[x][m] != -1) return ms[x][m];
        if(x+1==ps.size()) {
            ms[x][m] = ps[x];
            return ms[x][m];
        }
        int t = 0, sum = 0;
        for(int i=x; i<=min(x+2*m-1, (int)ps.size()-1); ++i) {//pay attention to boundary conditions
            sum += ps[i];
            int d = 1e6;
            for(int j=i+1; j<=min(i+1+2*max(m, i-x+1)-1, (int)ps.size()-1); ++j) {
                d = min(d, dfs(ps, j+1, max(max(m, i-x+1), j-i), ms));
            }
            if(d == 1e6) d = 0;//if there is no change of d, it means no element left
            t = max(t, sum + d);
        }
        ms[x][m] = t;
        return ms[x][m];
    }
};

Follow up question: 

How about the players can pick up piles from both sides?