不同的猜數字遊戲 — leetcode 375. Guess Number Higher or Lower II

很久沒切 leetcode 的題了,靜下心來切了道,這道題比較有意思,和你們分享下。javascript

我把它叫作 "不同的猜數字遊戲",咱們先來看看傳統的猜數字遊戲,Guess Number Higher or Lower。題意很是的簡單,給定一個數字 n,系統會隨機從 1 到 n 中抽取一個數字,你須要寫一個函數 guessNumber,它的做用是返回系統選擇的數字,同時你還有一個外部的 API 能夠調用,是爲 guess 函數,它會將你猜的數字和系統選擇的數字比較,是大了仍是小了。html

很是的簡單,稍微有點常識的童鞋應該都能想到二分查找的方案(插句題外話,這遊戲讓我想到了兒時的幸運52)。關於二分查找,能夠參考下我之前寫的一篇文章 http://www.cnblogs.com/zichi/p/5118032.html,幾乎囊獲了全部二分查找的狀況。這道題代碼比較簡單,能夠參考 guess-number-higher-or-lower.cpp,比較蛋疼的是不支持 JavaScript。java

核心代碼:git

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 掉!github

接着進入正題,來看 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。code

定義 ans 數組,而且初始化:htm

// 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 獲取。

相關文章
相關標籤/搜索