动态规划中的一个有意思的问题(纸牌问题、玩家博弈问题)

134 阅读2分钟

玩家博弈问题题目

给定一个整型数组,代表数值不同的纸牌排成一条线。玩家A和玩家B依次拿走每张纸牌。规定玩家A先拿,玩家B后拿。但是每个玩家每次只能拿走最左或最右的纸牌,玩家A和玩家B都绝顶聪明。请返回最后获胜者的分数。

分析

由于玩家A先拿,所以会先从左侧或者右侧选择一张,同时令后手的B获得的数值最小。如果令f(int from, int to)为从from到to范围上先手拿时获得的总价值最高的选择,g(int from, int to)为from到to范围上后手拿时获得的总价值最高的选择,则:

f(from,to)=max(arr[from]+g(from+1,to),arr[to]+g(from,to1))f(from, to) = max(arr[from] + g(from + 1, to), arr[to] + g(from, to - 1))
g(from,to)=min(f(from+1,to),f(from,to1))g(from, to) = min(f(from + 1, to), f(from, to - 1))

暴力递归代码

 // 根据规则,返回获胜者的分数
  public static int win1(int[] arr) {
      if (arr == null || arr.length == 0) {
          return 0;
      }
      int first = f1(arr, 0, arr.length - 1);
      int second = g1(arr, 0, arr.length - 1);
      return Math.max(first, second);
  }


  static int f(int[] arr, int L, int R) {
    if (L == R) {
          return arr[L];
      }
      int p1 = arr[L] + g(arr, L + 1, R);
      int p2 = arr[R] + g(arr, L, R - 1);
      return Math.max(p1, p2);
  }
  static int g(int[] arr, int L, int R) {
      if (L == R) return 0;
      int p1 = f(arr, L + 1, R);
      int p2 = f(arr, L, R - 1);
      return Math.min(p1, p2);
  }

改为动态规划

static int windp(int[] arr) {
    if (arr == null || arr.length < 1) return 0;
    int len = arr.length;
    int[][] gmap = new int[len][len];
    int[][] fmap = new int[len][len];
    for (int i = 0; i < len; i++) {
        gmap[i][i] = 0;
        fmap[i][i] = arr[i];
    }
    //除此之外,L一定不大于R,也就是取矩阵的上半部分
    for (int j = 1; j < len; j++) {
        int L = 0;
        int R = j;
        while (R < len) {
            fmap[L][R] = Math.max(arr[L] + gmap[L + 1][R], arr[R] + gmap[L][R - 1]);
            gmap[L][R] = Math.min(fmap[L + 1][R], fmap[L][R - 1]);
            L++;
            R++;
        }
    }
    return Math.max(fmap[0][len - 1],gmap[0][len - 1]);

}

总结

这个题说到底还是由暴力递归改来的,而暴力递归就是朴素的尝试,在这个题中,题目中要求要每次取牌的时候,要么从最左取要么从最右取,这就是两种可能性,把这两种可能性列出来,就可以解出来了。