LeetCode Minimax类(375 464 486) 题解

375:Guess Number Higher or Lower II  

首先找规律,以为是使左右数字和的差值最小。结果发现不是,例如7的情况,首先选择的数是4而不是5,最终代价是10。

然后回溯+剪枝暴力找每一段区间需要的最小代价,结果超时。

再一想发现回溯的过程中有重复子问题,所以加一个备忘录。Runtime 6ms,比dp要快,原因是剪枝较多。

int getMoneyAmount(int begin, int end, int** amount) {            
    if (begin >= end) 
        return 0;
    else if (amount[begin][end] > 0) {
        return amount[begin][end];
    }
    int minMoney = INT_MAX, lastMoney = INT_MAX;
    for (int index = begin + (end - begin) / 2; index < end; index++) {
        int m_money = index + max(getMoneyAmount(begin, index - 1, amount), getMoneyAmount(index + 1, end, amount));
        if (m_money > lastMoney)
            break;
        if (m_money < minMoney)
            minMoney = m_money;
    }
    amount[begin][end] = minMoney;
    return minMoney;
}
int getMoneyAmount(int n) {
    int** amount = new int*[n + 1];
    for (int i = 0; i <= n; i++) {
        amount[i] = new int[n + 1]();
    }
    return getMoneyAmount(1, n, amount);
}

当然dp也可以做的,递推公式如下:


其中,M[i][j]表示[i, j]区间内完成猜数字所需最小费用。


464: Can I Win

首先回溯,结果超时。考虑到里面又大量重复自问题,例如甲选1,乙选2,剩下[3, n]个数 和 甲选2,乙选1,剩下[3, n]个数 这两种情况是一样的,因为只考虑两人选择的数的综合超过target。但是怎么去记录子问题是个难点,因为有的数选了,有的数没选,还有剩下的数要完成的target是多少(其实这个不是问题,因为确定了选择的数后,剩下的target唯一确定了)。

看了discussion之后,找到了方案,由于最多只有20个数可选,倒数第i个数用二进制的倒数第i位表示,可选为1,已选为0,这样总共有2^20次方种可能情况,用数字[0, 2^20)来表示状态即可。在存储时使用int数组,每个元素0代表未知,1代表该状态下先手能赢,0代表该状态下先手必输。

2^20等于1024*1024,int大小为4Byte,所以空间需求为4MB。

状态递推,遍历每个可能选择的数,如果超过target或者选后对方必输则获胜。每个数都不能赢,则必输。

class Solution {
public:
    bool canIWin(int maxChoosableInteger, int desiredTotal, int* win, int state) {
        if (win[state]) {
            return win[state] > 0;
        }
        for (int i = 0; i < maxChoosableInteger; i++) {
            if (state & 1 << i) {
                if (i + 1 >= desiredTotal || !canIWin(maxChoosableInteger, desiredTotal - i - 1, win, state ^ 1 << i)) {
                    win[state] = 1;
                    return true;
                }  
            }
        }
        win[state] = -1;
        return false;
    }
    
    bool canIWin(int maxChoosableInteger, int desiredTotal) {
        if (maxChoosableInteger * (maxChoosableInteger + 1) / 2 < desiredTotal)
            return false;
        int* win = new int[1 << maxChoosableInteger]();
        return canIWin(maxChoosableInteger, desiredTotal, win, (1 << maxChoosableInteger) - 1);
    }
};

用动态规划的话比较麻烦,例如用win[i]表示状态i时能不能赢,还是用上面的递推公式,从win[0]开始推(所有数字都选完)。但是初始条件处理比较麻烦,因为随着可选数字增多,首先回到target之内的状态是赢。


486:Predict the Winner

这道题和Nim Game的区别在于,Nim先拿完就赢,而这道题胜利要求不一样,胜利条件是所得的数比对方大。

因此记录每个状态的胜利与否是不够的,要记录所得数的和能胜出多少。

由于每次玩家都只能从最左或最右,可得递推式:

    win[i][j] = max(nums[i] - win[i + 1][j], nums[j] - win[i][j - 1])

其中win[i][j]表示在数组下标位于[i, j]区间时,先手能胜出多少。选定一个值后,减去对方胜出的数即可,首尾两种选法取最大值。

bool PredictTheWinner(vector<int>& nums) {
    int len = nums.size();
    int** win = new int*[len];
    for (int i = 0; i < len; i++) {
        win[i] = new int[len];
        win[i][i] = nums[i];
    }
    for (int cnt = 1; cnt < len; cnt++) {
        for (int i = 0; i < len - cnt; i++) {
            int j = i + cnt;
            win[i][j] = max(nums[i] - win[i + 1][j], nums[j] - win[i][j - 1]);
        }
    }
    return win[0][len - 1] >= 0;
}


总结:Minimax这类题就是可能用到了min或max函数的动态规划/记忆化搜索?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值