leetcode_1025_除数博弈

题目:

爱丽丝和鲍勃一起玩游戏,他们轮流行动。爱丽丝先手开局。

最初,黑板上有一个数字 N 。在每个玩家的回合,玩家需要执行以下操作:

选出任一 x,满足 0 < x < N 且 N % x == 0 。
用 N - x 替换黑板上的数字 N 。
如果玩家无法执行这些操作,就会输掉游戏。

只有在爱丽丝在游戏中取得胜利时才返回 True,否则返回 false。假设两个玩家都以最佳状态参与游戏。

 

示例 1:

输入:2
输出:true
解释:爱丽丝选择 1,鲍勃无法进行操作。
示例 2:

输入:3
输出:false
解释:爱丽丝选择 1,鲍勃也选择 1,然后爱丽丝无法进行操作。
 

提示:

1 <= N <= 1000

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/divisor-game
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


思路:

这个题目标签为“简单”,但是又比一般的简单类型难一点。

不是简单的随机遍历。还要考虑:怎么让自己赢,以及如何让对方输。

所以根据要求N%X==0,在每次取数时就会有一定的规律:

如果N是奇数,那么只能随机抽取(1,N)之间的一个奇数X才可满足条件

如果N是偶数,那么直接抽取1即可。这样抽“我”输的风险最小。

Note:

整个抽取过程类似搜索,但是必须进行记忆化搜索,即记住中间结果以便搜索时的可能的使用,避免不必要的搜索,实现剪枝的效果。

在实现的时候,原本使用两个数组来记忆化:

①book数组来标识:数N时的博弈结果以前是否搜索过。book[N]=1表示搜索过。book[N]=0表示未搜索过。

②dp数组来标识:数N时的博弈结果。true则是爱丽丝获胜。否则失败。

后来进行精简化:将dp数组和book数组的功能整合到book数组中:

book[N] = 1时的含义不变

增加book[N] = 2,来表示数N是爱丽丝获胜。


实现(再次体会到C语言的高效性23333):

JAVA版:

package leetcode;

/*
USER:LQY
DATE:2020/7/24
TIME:8:04
*/
public class leetcode_1025 {

    public static void main(String []args){
        new leetcode_1025().divisorGame(3);
    }
    public boolean divisorGame(int N) {

        int []book = new int[N+1];
        boolean []dp = new boolean[N+1];
//       使用非精简版时需要如下初始化 。否则不需要初始化,保持全零即可。
        dp[1] = false;
        dp[2] = true;
        book[1] = 1;
        book[2] = 2;
        boolean res = solve(N, 1, book, dp);
        System.out.println(res);
        return res;
    }
//    非精简版
    public boolean solve(int N, int flag, int []book, boolean []dp){
        if(N <= 2) return dp[N] ^ (flag==1);
        if(book[N] == 1) return dp[N];
        book[N] = 1;
        boolean res = false;
        if(N%2 != 0){
            for(int i = N-2;i >= 1;i -= 2){
                if(N%i != 0) continue;
                res = res || solve(N-i, -flag, book, dp);
            }
        }else{
            res = res || solve(N-1, -flag, book, dp);
        }

        return dp[N] = res;

    }
//    精简版。省去了dp数组,节省了内存空间
//    使用book数组来记忆化。空间换时间。
//    book[N] == 0表示:数N时的博弈结果还未知。
//    book[N] == 1表示在前面的博弈过程中,数N已经出现过,此时必定已经记录了数字为N时的结果。直接返回即可。
//    同时,book[N] == 1也表示数字为N时,爱丽丝失败。
//    book[N] == 2表示:数字为N时,爱丽丝取胜。
    public boolean solve(int N, int flag, int []book){
        if(N <= 2) return (book[N]==2) ^ (flag==1);

        if(book[N] == 1) return book[N]==2;  //此时直接返回,因为已经记录了结果,不需要重复计算。
        //没有记录过数N时的结果。
        book[N] = 1;  //因为当前就是要来计算数N时的博弈结果的,所以将book[N]标记为1(而不是2!!!)
        boolean res = false;  //数为N时的博弈结果暂存变量。初始肯定为false。
//        有两种取数策略:
//        ①如果当前的N为奇数。那么“我”只能在(0,N)之间取出一个奇数x才能满足:N%x == 0 的条件。
        if(N%2 != 0){
//            所以使用一个for循环遍历,类似查找。
            for(int i = N-2;i >= 1;i -= 2){
                if(N%i != 0) continue;
                res = res || solve(N-i, -flag, book);
            }
        }
//        ②如果当前的N为偶数,那么“我”可以直接就取出一个1就可以了
//        当然,如果想取出其他可以整除N的偶数也可,但是不符合博弈心理:“我”要想方设法赢。
//        所以,该题并不是简单的遍历。。。
        else{
            res = res || solve(N-1, -flag, book);
        }
        book[N] = res ? 2 : 1;  //如果res为真,设book[N]=2来记录。
        return res;

    }
}

C语言版:

bool solve(int, int, int []);
bool divisorGame(int N){
    if(N == 1) return false;
        int book[N+1];
        for(int i = 0;i <= N;i++) book[i] = 0;
        
        // boolean []dp = new boolean[N+1];
        return solve(N, 1, book);
    }
    bool solve(int N, int flag, int book[]){
        if(N <= 2) return (book[N]==2) ^ (flag==1);

        if(book[N] == 1) return book[N]==2;
        book[N] = 1;
        bool res = false;
        if(N%2 != 0){
            for(int i = N-2;i >= 1;i -= 2){
                if(N%i != 0) continue;
                res = res || solve(N-i, -flag, book);
            }
        }else{
            res = res || solve(N-1, -flag, book);
        }
        book[N] = res ? 2 : 1;
        return res;

    }

Python版:

class Solution:
    def divisorGame(self, N: int) -> bool:
        if(N == 1):
            return False
        book = [0 for i in range(N+1)]
        

        def solve(N: int, flag: int, book: List[int]) -> bool:
            if(N <= 2):
                return (book[N]==2) ^ (flag==1)
            if(book[N] == 1):
                return book[N]==2
            book[N] = 1
            res = False

            if(N%2 != 0):
                for i in range(N-2, 0, -2):
                    if(N%i != 0):
                        continue
                    res = res or solve(N-i, -flag, book)
            else:
                res = res or solve(N-1, -flag, book)

            if(res):
                book[N] = 2
            
            return res 
        return solve(N, 1, book)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值