zoukankan      html  css  js  c++  java
  • 不一样的猜数字游戏 — leetcode 375. Guess Number Higher or Lower II

    好久没切 leetcode 的题了,静下心来切了道,这道题比较有意思,和大家分享下。

    我把它叫做 "不一样的猜数字游戏",我们先来看看传统的猜数字游戏,Guess Number Higher or Lower。题意非常的简单,给定一个数字 n,系统会随机从 1 到 n 中抽取一个数字,你需要写一个函数 guessNumber,它的作用是返回系统选择的数字,同时你还有一个外部的 API 可以调用,是为 guess 函数,它会将你猜的数字和系统选择的数字比较,是大了还是小了。

    非常的简单,稍微有点常识的童鞋应该都能想到二分查找的方案(插句题外话,这游戏让我想到了儿时的幸运52)。关于二分查找,可以参考下我以前写的一篇文章 http://www.cnblogs.com/zichi/p/5118032.html,几乎囊获了所有二分查找的情况。这道题代码比较简单,可以参考 guess-number-higher-or-lower.cpp,比较蛋疼的是不支持 JavaScript。

    核心代码:

    int guessNumber(int n) {
      int start = 1, end = n;
      int ans;
    
      while (start <= end) {
        int mid = start + (end - start) / 2;
        int val = guess(mid);
    
        if (val == -1)
          end = mid - 1;
        else if (val == 1)
          start = mid + 1;
        else {
          ans = mid;
          break;
        }
      }
    
      return ans;
    }
    

    还有一点需要注意下,取 mid 值时不能用 (start + end) / 2,不然会溢出,TLE 掉!

    接着进入正题,来看 Guess Number Higher or Lower II 这道题,跟前者比,有何区别呢?同样是给定一个数字 n,系统会随机从 1 到 n 中选择一个整数,你要做的还是将这个数猜出来。你每猜一个数字,需要花费一定的 money,比如你猜 m,那么你就要花费 m,求解你要将这个数字猜出来,至少需要的 money

    举个栗子,比如 n 为 5,系统选择的数字是 1。如果我先猜 3,系统提示你猜大了,然后再猜 2,系统提示你猜大了,那么你就可以确定是 1 了,花费 3+2=5。但是很明显第二次猜测应该猜 1,这样花费就少了。再比如我选的是 4,第一次还是猜 3,系统提示你猜小了,第二次猜 4,中了,总共花费 3+4=7,如果 n 为 5,至少需要 7?非也,正确的解法是先猜 4,如果数字在 1-3 之间,那么再猜 2,至少需要的应该是 6!

    这是一道很典型的动态规划题,你根本不可能去盲目地猜,然后使劲地暴力递归去解!这样的复杂度是指数级的。是否能够递推求解?比如已经知道 n 为 1-5 的情况,当 n 为 6 时,第一次猜,我们可以有 6 种猜法,分别选择 1,2,3,4,5 和 6,我们以猜 3 为例,比如说第一把猜了 3,那么如果猜的大了,那么我们接下去要求的是从 [1, 2] 中猜到正确数字所需要花费的最少 money,记为 x,如果猜的小了,那么我们接下去要求的是从 [4, 6] 中猜到正确数字所需要花费的最少 money,记为 y,如果刚好猜中,则结束。很显然,如果第一把猜 3,那么猜中数字至少需要花费的 money 为 3 + max(x, y, 0),"至少需要的花费",就要我们 "做最坏的打算,尽最大的努力",即取最大值。这是第一把取 3 的情况,我们还需要考虑其他 5 种情况,然后六种情况再取个最小值,就是 n=6 至少需要的 money!(想想,是不是这样?)

    最后来编码,我们需要一个二维数组来表示最值。首先我们定义一个二维数组 ans[][],ans[i][j] 表示 i-j 中任取一个数字,猜中这个数字需要至少花费的 money。

    定义 ans 数组,并且初始化:

    // ans[i][j] 表示从 [i, j] 中任取一个数字
    // 猜中这个数字至少需要花费的 money
    var ans = [];
    for (var i = 0; i <= n; i++)
      ans[i] = [];
    

    接着我们定义一个函数 DP,DP(ans, x, y) 表示 [x, y] 中任取一个数字,猜中这个数字需要花费的最少 money,而 ans 是为数组的引用。很显然,我们要求的就是 DP(ans, 1, n) 的返回值,直接看代码。

    function DP(ans, from, to) {
      // 如果 from >= to
      if (from >= to)
        return 0;
    
      // 如果 ans[from][to] 已经求得
      // 直接 return
      if (ans[from][to])
        return ans[from][to];
    
      // 先赋值 Infinity,便于之后的比较
      ans[from][to] = Infinity;
    
      // 现在要从 [from, to] 中猜数字
      // 假设先猜 i,i 可以是 [from, to] 中的任何数字,遍历之
      for (var i = from; i <= to; i++) {
        // left 为从 [from, i - 1] 猜对数字至少需要花费的 money
        var left = DP(ans, from, i - 1);
        // right 为从 [i + 1, to] 猜对数字至少需要花费的 money
        var right = DP(ans, i + 1, to);
    
        // tmp 为先猜 i,从 [from, to] 猜对数字至少需要花费的 money
        var tmp = i + Math.max(left, right);
    
        // 跟别的方案比较(即跟不是先猜 i 的方法比较)
        // 取最小值
        ans[from][to] = Math.min(ans[from][to], tmp);
      }
    
      return ans[from][to];
    }
    

    注释写的很清晰了,如果再细分的话,个人觉得这可以说是一道 "记忆化DP",不晓得有没有这个词?好像只听说过 "记忆化搜索"?DP 本来就是记忆化的过程吧?好了不钻牛角尖了,完整代码可以从我们的 Repo https://github.com/hanzichi/leetcode 获取。

  • 相关阅读:
    AngularJS Insert Update Delete Using PHP MySQL
    Simple task manager application using AngularJS PHP MySQL
    AngularJS MySQL and Bootstrap Shopping List Tutorial
    Starting out with Node.js and AngularJS
    AngularJS CRUD Example with PHP, MySQL and Material Design
    How to install KVM on Fedora 22
    Fake_AP模式下的Easy-Creds浅析
    河南公务员写古文辞职信
    AI
    政协委员:最大愿望是让小学生步行上学
  • 原文地址:https://www.cnblogs.com/lessfish/p/5701194.html
Copyright © 2011-2022 走看看