[LeetCode] 464. Can I Win

原题链接: https://leetcode.com/problems/can-i-win/

1. 题目简介

In the “100 game,” two players take turns adding, to a running total, any integer from 1…10. The player who first causes the running total to reach or exceed 100 wins.

What if we change the game so that players cannot re-use integers?

For example, two players might take turns drawing from a common pool of numbers of 1…15 without replacement until they reach a total >= 100.

Given an integer maxChoosableInteger and another integer desiredTotal, determine if the first player to move can force a win, assuming both players play optimally.

You can always assume that maxChoosableInteger will not be larger than 20 and desiredTotal will not be larger than 300.

Example

Input:
maxChoosableInteger = 10
desiredTotal = 11

Output:
false

Explanation:
No matter which integer the first player choose, the first player will lose.
The first player can choose an integer from 1 up to 10.
If the first player choose 1, the second player can only choose integers from 2 up to 10.
The second player will win by choosing 10 and get a total = 11, which is >= desiredTotal.
Same with other integers chosen by the first player, the second player will always win.

在 “100 game” 这个游戏中,两名玩家轮流选择从 1 到 10 的任意整数,累计整数和,先使得累计整数和达到 100 的玩家,即为胜者。

如果我们将游戏规则改为 “玩家不能重复使用整数” 呢?

例如,两个玩家可以轮流从公共整数池中抽取从 1 到 15 的整数(不放回),直到累计整数和 >= 100。

给定一个整数 maxChoosableInteger (整数池中可选择的最大数)和另一个整数 desiredTotal(累计和),判断先出手的玩家是否能稳赢(假设两位玩家游戏时都表现最佳)?

你可以假设 maxChoosableInteger 不会大于 20, desiredTotal 不会大于 300。

2. 解题思路 – 动态规划

动态规划是一种非常好的解题思路,可以解决这个题。在说明解法之前,我想总结一下动态规划的常规思路。

动态规划思路关键在于分解和求解子问题,然后根据子问题的解不断递推,得出最终解,并且在每一步的计算中,需要保留计算过的子问题的解,这样当遇到同样的子问题时就不用继续向下求解而直接可以得到结果。

于是我们需要把所有子问题的状态用一个数据结构(数组、链表等等)来存储,这个数据结构可以把每个状态和对应的结果关联起来,当求解子问题时,如果数据结构里面已经有该状态的解就不用再求了,如果没有则继续求,然后存起来。同样每次求解完一个状态的解后也要将其放入数据结构中保存。

上面的介绍有点抽象,下面将详细介绍实现的方法。

使用int整数来表示数的使用情况

动态规划一般会用一个数据结构把每个状态和结果关联起来,那么在本题中,状态是什么?结果又是什么呢?
这两个问题,不同的思路会有不同的答案。我采用这样一种思路:状态是不同数的使用情况,结果则是在该状态下先手能否赢。

先看状态是如何表示的:
我们可以用一个二进制的数来表示不同数的使用情况。1代表用过,0代表没有用过。例如 maxChoosableInteger = 3 时, 它的状态就有8种:
000(三个数都没有被用过)
001(1被用过了,2、3没有被用过)
010(2被用过了,1、3没有被用过)
011(1、2被用过了,3没有被用过)
100(3被用过了,1、2没有被用过)
101(1、3被用过了,2没有被用过)
110(2、3被用过了,1没有被用过)
111(1、2、3都被用过了)
本题中,maxChoosableInteger不会大于20,而java中 int 类型的数有32位,足够用了。因此我们可以用一个int型的数来表示从1到maxChoosableInteger的所有数的使用情况。

再来看结果是如何表示的:
结果只有三种:先手会赢、先手会输、暂时无法判断。
每一种状态,都会对应一种结果,使用数组State[ i ]来存放。i 就是上文提到的 int 类型的整数,用于表示每一种状态,而State[ i ]就是该状态对应的结果。State[ i ] = 0 时,是无法判断,State[ i ] = 1 时,是先手会赢,State[ i ] = 2时,是先手会输。

实现代码

class Solution {
    int State[];
	public boolean canIWin(int m, int d) {
		//如果总数大于1~m的总和的话,双方都不会赢
		int Maxsum = (1+m)*m/2;
		if(Maxsum < d) {
			return false;
		}
		//如果目标和为0,先手已经赢了
		if( d == 0 ) {
			return true;
		}
		
		State = new int [1 << m];
		return judge(m,d,0);
    }
	
	public boolean judge(int m , int d ,int s )
	{
		//s 是表示1~m的所有数的使用情况的int类型数
		if(State[s] != 0 ) {
			return (State[s] == 1 ? true : false);
		}
		
		if(d <= 0) {
			return false;
		}
		
		for(int i = 1 ; i<=m ; i++) {
			//如果i已经被用过了,就跳过
			int IfiUsed = s & (1 << (i-1));
			if(IfiUsed != 0) {
				continue;
			}
			
			//如果i没有被用过,并且d-i的先手会输,那么d的先手使用i就可以赢了。
			int iUsed = s | (1 << (i-1));
			if( judge(m , d-i , iUsed  ) == false) {
				State[s] = 1;
				return true;
			}
		}
		
		State[s] = 2;
		return false;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值