动态规划:游戏赢家(一)

    给定一个非负数组,A、B两人玩游戏,轮流从数组的头部或者尾部取出元素,加到各自的和中,直到数组元素取完,取到的元素和最大的玩家赢。问对于给定的数组,先开始的玩家能否保证赢得游戏(如果相等也算先手的玩家赢)。

    例如给定数组为[1, 5, 233, 7],A为开始第一步的玩家。A可以取1或者7,如果A先取1,剩下[5, 233, 7];B可以取5或者7,但不管B取任何元素,A都可以取233,从而A肯定可以赢得游戏。

    分析:先走的玩家赢得游戏,即要求先走的玩家的元素和大于等于数组和的一半。

    如果按照游戏的步骤,把数组从长向短的方向进行,每进行一步都会产生不同的选择,如果要记录所有可能的情况,时间复杂度和空间复杂度都比较高。

    方法一:可以把这个过程倒过来,对于给定数组,反过来按照从短到长的方向进行计算。

    用dp[i][j]来记录先手玩家取得的元素和,则 d[i][j] = max(d[i+1][j] + nums[i], d[i][j-1] + nums[j]),其中d[i+1][j]是对手选择元素之后的结果,由对手来确定,有可能是以下两种情况之一:d[i+1][j] = d[i+2][j] 或者d[i+1][j] = d[i+1][j-1],同理,d[i][j-1] = d[i+1][j-1]或者d[i][j-1] = d[i][j-2]。由于对手的目的是使先手玩家的和最小,所以d[i+1][j] = min (dp[i + 2][j], dp[i + 1][j - 1]), d[i][j-1] = min (dp[i + 1][j - 1], dp[i][j - 2]) 。因此,dp[i][j] = max( min (dp[i + 1][j - 1], dp[i + 2][ j]) + nums[i], min (dp[i][j - 2], dp[i + 1][ j - 1]) + nums[j]})

    (相当于把选元素的过程以逆序进行了,如果按照正序,为了使最后的和最大,每一步选择的并不一定是使当前和最大的元素;但逆序的过程,从最后一步开始选,每一步都选使当前和最大的元素,到最后一步得到的肯定是最大的和。)

    此过程实际上为minmax算法的实现,从搜索树的叶子情况开始(所有可能的搜索路径的终点开始),每一步取若是先手的主动权,取最大值,如果是对手的主动权,取最小值。以此类推,直到根节点。

   public boolean PredictTheWinner(int[] nums) {
        int n = nums.length, sum = 0;
	if(n % 2 == 0) return true; //如果数组元素个数为偶数,直接计算下标为奇数的元素和与下标为偶数的元素和,先手的总可以取到下标全为奇数或全为偶数的元素
        int[][] dp = new int[n][n];
        for(int i=0; i < n; i++) {
            dp[i][i] = nums[i]; //奇数个元素,最后一个元素肯定是先手方取得
            sum += nums[i];
        }

        for(int j = 0; j < n; j++){
            for(int i = j - 1; i >= 0; i--){
            	int a = (i + 1 < n && j - 1 >= 0) ? dp[i + 1][j - 1] : 0;
		int b = (i + 2 < n) ? dp[i + 2][ j] : 0;
		int c = (j - 2 >= 0) ? dp[i][j - 2] : 0;
                dp[i][j] = Math.max(Math.min(a, b) + nums[i], Math.min(a, c) + nums[j]);
            }
        }

        return dp[0][n - 1] * 2 >= sum;
    }
    方法二:使用递归的方法
    public boolean PredictTheWinner(int[] nums) {
        return helper(nums, 0, nums.length-1)>=0;
    }
    private int helper(int[] nums, int s, int e){        
        return s==e ? nums[e] : Math.max(nums[e] - helper(nums, s, e-1), nums[s] - helper(nums, s+1, e)); //返回的是两方取得的和的差,立场不断改变,如果当前是A走,那么上一步的helper得到的是B-A,且当前元素应该加到A的和中,得到的差为num-helper。
    }


猜你喜欢

转载自blog.csdn.net/xiezongsheng1990/article/details/80010495