不一样的猜数字游戏 — 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 获取。
不一样的猜数字游戏 — leetcode 375. Guess Number Higher or Lower II的更多相关文章
- [LeetCode] 375. Guess Number Higher or Lower II 猜数字大小 II
We are playing the Guess Game. The game is as follows: I pick a number from 1 to n. You have to gues ...
- [LeetCode] 375. Guess Number Higher or Lower II 猜数字大小之二
We are playing the Guess Game. The game is as follows: I pick a number from 1 to n. You have to gues ...
- [leetcode]375 Guess Number Higher or Lower II (Medium)
原题 思路: miniMax+DP dp[i][j]保存在i到j范围内,猜中这个数字需要花费的最少 money. "至少需要的花费",就要我们 "做最坏的打算,尽最大的努 ...
- Leetcode 375. Guess Number Higher or Lower II
We are playing the Guess Game. The game is as follows: I pick a number from 1 to n. You have to gues ...
- Leetcode之二分法专题-374. 猜数字大小(374. Guess Number Higher or Lower)
Leetcode之二分法专题-374. 猜数字大小(374. Guess Number Higher or Lower) 我们正在玩一个猜数字游戏. 游戏规则如下:我从 1 到 n 选择一个数字. 你 ...
- 【LeetCode】375. Guess Number Higher or Lower II 解题报告(Python)
[LeetCode]375. Guess Number Higher or Lower II 解题报告(Python) 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://f ...
- 375 Guess Number Higher or Lower II 猜数字大小 II
我们正在玩一个猜数游戏,游戏规则如下:我从 1 到 n 之间选择一个数字,你来猜我选了哪个数字.每次你猜错了,我都会告诉你,我选的数字比你的大了或者小了.然而,当你猜了数字 x 并且猜错了的时候,你需 ...
- leetcode 374. Guess Number Higher or Lower 、375. Guess Number Higher or Lower II
374. Guess Number Higher or Lower 二分查找就好 // Forward declaration of guess API. // @param num, your gu ...
- 375. Guess Number Higher or Lower II
最后更新 四刷? 极大极小算法..还是叫极小极大的.. 首先要看怎么能保证赢. 比如2个数,猜第一个猜第二个都能保证下一轮我们赢定了,为了少交钱,我们猜小的. 比如3个数,猜第二个才能保证下一轮再猜一 ...
随机推荐
- js中兼容性问题的封装(能力检测)
所谓兼容性,就是看浏览器是否支持当前对象的属性或是方法,先通过获得页面文本内容的方式来了解兼容性,并封装函数或对象以解决此类问题. 获得页面文本内容的方式:innnerText 或textConten ...
- SharePoint 2013 定制搜索显示模板
前言 之前我们已经介绍了一些关于搜索的相关配置,当然,用户关于搜索的要求可能是各种各样.有时候,用户会说,你们的显示结果太Low了,确实是:不过,在SharePoint中,我们可以很容易的定制搜索结果 ...
- 在Autodesk应用程序商店发布基于浏览器的Web应用程序
你一定已经听说过Autodesk应用程序商店了,通过Autodesk应用程序商店,你可以免费下载或购买来自全球的优秀开发者发布的应用程序,来帮助你更快更方便的完成你的工作.而且作为开发者,您也可以在A ...
- SharePoint 2013 User Profile Services之跨场发布
在之前博客中已经描述了User Profile的两种配置场景,这篇博客将详细介绍微软官方推荐的配置方法. 测试环境的架构可以参考之前的博客内容,这里就不做介绍了,直接切入主题. 1. 在sp-farm ...
- Java打印九九乘法表
package com.czgo; /** * 九九乘法表 * * @author AlanLee * */ public class Print99 { public static void mai ...
- 为什么数据库有时候不能定位阻塞(Blocker)源头的SQL语句
在SQL Server数据库或OACLE数据库当中,通常一个会话持有某个资源的锁,而另一个会话在请求这个资源,就会出现阻塞(blocking).这是DBA经常会遇到的情况.当出现SQL语句的阻塞时,很 ...
- WinForm:DataGridView新增加行
1.不显示最下面的新行 通常 DataGridView 的最下面一行是用户新追加的行(行头显示 * ).如果不想让用户新追加行即不想显示该新行,可以将 DataGridView 对象的 AllowUs ...
- 基于ambari2.4.0进行二次开发
目录 线上修改 源码结构 技术点 编译环境的搭建 安装samba 安装编译环境 整体编译 ambari-web的编译 WEB内容修改 wiki:https://cwiki.apache.org/co ...
- MongoDB学习笔记~环境搭建
回到目录 Redis学习笔记已经告一段落,Redis仓储也已经实现了,对于key/value结构的redis我更愿意使用它来实现数据集的缓存机制,而对于结构灵活,查询效率高的时候使用redis就有点不 ...
- linux top 源码分析
/* * Copyright (c) 2008, The Android Open Source Project * All rights reserved. * * Redistribution a ...