大佬,牛!!!
- 题目:就是给你两个数,一个是我们成为n,第二个称为tar。要求两个从1到n区数,谁取出来以后,取出的数之和要是大于等于tar,那么就赢了。问一下第一个人能赢吗。
- 我的思路:我以为一定是有规律的,然后始终没找到规律,而且本来做这种博奕类的就不太会。想了半天,直接看题解了,原来是类似暴力,然后进行剪枝。其中这个大佬的思路,让我学到了很多的东西。
- 大佬思路:也基本是暴力,只不过这里用到了一个数组,用来存储这个情况出现过没有。并且大佬用位来记录这个数是不是取过,这跟redis的bitmaps很相似,这样可以大大降低空间复杂度。
- 首先明确一点,就是一共有多少种情况,我们假设n是10,每个数取与不取,所以一共有2的10次方种情况。也就是1<<n,因此这个数组的长度应该是2的n次方。
- 然后就是每种情况,我们用一个int类型的数used进行表示,例如used等于11,转换到位之后等于1011,这就可以表示我们取了第1,3,4个。所以每次进行判断之前,我们先看dp数组中的第used位置的数是不是已经计算过了。
- 然后就是dp数组中的内容了,初始化是0,1表示先手的人能赢,2表示输,0表示还没有进行计算。
- 然后就是递归了(递归是本次user,能不能赢,把所有的没拿的都遍历一下就行了),递归就是本人拿i赢了,或者下一个人拿i输了,这时候就能将dp[used]设置为1,并且return即可。
- 然后遍历完所有的,都没有赢,则输掉。
- 技巧:这里技巧还是挺多的,基础就是深度优先,然后用一个数组进行标记是不是使用过,但是这个数组比较难定义和判断,大佬用位来进行判断,这个思路简直绝了。里面还有一些位移运算、逻辑运算等。
java代码
class Solution {
public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
// 目标是desiredTotal,
if (maxChoosableInteger >= desiredTotal) {
return true;
}
if ((maxChoosableInteger + 1) * maxChoosableInteger / 2 < desiredTotal) {
return false;// 谁也不会赢
}
char[] dp = new char[1 << maxChoosableInteger];// 1表示能赢
return process(maxChoosableInteger, desiredTotal, 0, 0, dp) == 1;
}
/**
* 这里注意递归的目的,一次递归就是找到拿哪一个能赢
* @param n 表示最大的值
* @param tar 表示目标值,也就是界限
* @param used used是一个数,这个数用位来表示第i个数是不是使用,也就是说used一样,则表示已经出现过
* @param cur 表示当前的求和
* @param dp 是一个数组,用来记录这种情况是不是出现过
* @return 返回的是一个char,
*/
public char process(int n, int tar, int used, int cur, char[] dp) {
if (dp[used] != 0) {// 之前遇到过了,
return dp[used];
}
for (int i = 0; i < n; i++) {
if (((used >> i) & 1) != 0) {// 这一个数已经被选过了
continue;
}
// 自己拿第i个赢了,或者下一个人拿第i个输了
if (cur + i + 1 >= tar || process(n, tar, used | (1 << i), cur + i + 1, dp) == 2) {
dp[used] = 1;
return 1;
}
}
dp[used] = 2;
return 2;
}
}
- 总结:题目的思路就是暴力求解了,只不过过程中可以进行剪枝,因为有一些情况我们已经计算过了。关键是大佬用位来进行标记,从而将一个二维数组变成了一维,并且也方便了比较的过程,真的是学到了。上大佬链接。