谷歌面试-扔鸡蛋

今天想跟大家分享一个有意思的面试题,这让我再一次感叹思维的奇妙,接下来我们一起看看吧~

首先来看看题目:

你有2颗鸡蛋,需要以最少的尝试次数来判断在100层的高楼上,哪一层楼是鸡蛋的安全层。

换句话说,就是需要确定我们从哪一层楼扔鸡蛋下去,鸡蛋恰好不会摔碎。高于安全层鸡蛋都会碎,低于安全层都不会碎。比如鸡蛋在第1层没有摔碎,在第2层摔碎了,那么鸡蛋的安全层就是第1层。

这里有几个假设条件:

  1. 没有摔碎的鸡蛋可以重复使用;

  2. 每颗鸡蛋的坚硬程度都是相同的。

在这里插入图片描述

这道题乍一看挺简单的,但其实解答相对复杂,而且解法多种多样,要在面试时逻辑清楚地表达完整思路,不仅要求面试者的知识储备要广、反应能力要快,逻辑思维和语言表达能力也是必不可少的。

成为经典可谓当之无愧。

解法1:简单粗暴

我们先来个最省事儿的方法:假设我们只有一颗鸡蛋,显然只有从一楼开始扔,逐层试探,直到鸡蛋摔碎,安全层就是第N-1层。

但是缺点想必大家也看出来了,这是拼运气啊,最坏情况需要扔100次。

用一颗鸡蛋的方法虽然简单粗暴,但也是给两颗鸡蛋的情况缕清一些思路。

简单写一下如何实现:

// 假设arr表示100层楼,每层楼鸡蛋会不会碎,如果arr[i] === 1 表示i层楼的鸡蛋会碎,arr[i] === 0表示第i层楼的鸡蛋不会碎// 简单暴力
const throwEggs1 = (arr) => {for(let i = 1; i <= 100; i++) {if (arr[i] === 1) {return i}}
}

解法2:常规二分

有两颗鸡蛋,二分法想必是大多数同学脑海里浮现的第一个念头吧?

我们先从50楼扔一颗鸡蛋,如果没碎,就往上继续二分,到75楼继续扔······

这是比较顺利的情况,如果不顺利呢,比如我们从50楼扔鸡蛋,直接碎了,那就只有一颗鸡蛋了。

这时候我们就回到解法1了,只能从1楼开始遍历,又是拼运气的时候了,要是运气好,1楼鸡蛋就没了,那测试次数就是1+1=2次,但最坏情况就是1+49=50次。

这么多次,显然是不能通过google面试的。

// 常规二分const throwEggs2 = (arr) => {let left = 1let right = 100let mid = 0while(left <= right) {mid = Math.floor(left + right) / 2if (arr[mid] === 0) {left = mid + 1} else {right = mid - 1}}return mid
}

解法3:均衡切割

虽然二分法不够优秀,但体现了切分范围的思想。

我们的基本思路是,将100层切分成两个维度,由两个鸡蛋分别控制一个维度。

一个维度是用第一颗鸡蛋分金定穴,另一个维度是用第二颗鸡蛋在前蛋的基础上进行遍历。

换言之,我们是将100层切分成若干个区块,由第一颗鸡蛋确定最高安全楼层所属的区块,再由第二颗鸡蛋逐层确定其具体的位置。

在1-100层楼之间,假设我们从上往下尝试,即从100层开始扔第一颗蛋,大概率是碎了,那第二颗蛋便又回到了解法1

所以,我们应该从下往上进行划分、尝试,这样即使第一颗鸡蛋碎了,用第二颗蛋遍历的成本也比较低。

比如第一颗蛋每10层扔一次,第一次从10层扔,第二次从20层扔,第三次从30层扔……一直扔到100层。

第二颗蛋就只用在第一颗蛋摔碎的层数和前一次的安全层之间的9层进行范围遍历。

也就是说,要是第一颗鸡蛋在第30层摔碎了,那就拿第二颗蛋从21层到29层逐层尝试。

这样的最好情况就是第一颗蛋在第10层碎掉,总的尝试次数为1+1=2次。

最坏的情况是在第100层碎掉,总尝试次数为10+9=19次。

在这里插入图片描述

// 均衡切割
const throwEggs3 = (arr) => {let count = 0// 第一个鸡蛋for (let i = 1; i < 10; i++) {if (arr[i * 10] === 1) {count = i * 10break}}// 第二个鸡蛋for(let i = count - 1; i >= count - 10; i--) {if (arr[i] !== 1) {return i}}
}

解法4:微妙平衡

上面的方法,看似已经比较完美了。

但是我们再具像化一点,就能发现问题:第一颗鸡蛋能快速定位安全楼层低的情况,但如果安全楼层位置越高,耗时就会越久,而第二颗鸡蛋在每个区块内的消耗,都是一样的。

如果鸡蛋的最高安全层为18或者98,用解法3的思路的话,这两种情况的总尝试次数并不一样:

最高安全楼层为18时,第一颗鸡蛋试了2次就定位了区块;而最高安全楼层为98时,第一颗鸡蛋试了10次才定位了区块。

虽然第二颗鸡蛋在区块内部的逐层尝试次数是一样的,但98层对应的总尝试次数就多太多了。

原因就是区块完全均匀划分对大数不利

明白了这个缺陷,也就知道了改进的基本思想:要对100找出一种二维区块划分,但不是均匀划分。

对于比较小的区块部分,其包含的楼层范围可以适当增加;越向大数部分走,其包含的楼层范围越来越小。从下往上,每一个区块内所含楼层递减。

在最高安全楼层比较低的情况下,第一颗鸡蛋尝试的次数少;在最高安全楼层比较高的情况下,则第二颗鸡蛋尝试的次数少。

就是用第二颗鸡蛋尝试次数的减少来弥补第一颗鸡蛋需要的尝试次数的递增,使两颗鸡蛋在不同维度上的尝试次数此消彼长,达到一种总体上的平衡。

每上一个区块,第一颗鸡蛋消耗的次数就+1,我们索性就假设每个区块包含的楼层数逐级递减1,以达到平衡。

那么每个区块包含的层数应该如何划分呢?

我们假设第一个区块有X层,那么第二个就是X-1,以此类推,我们就得到了一个方程式:

X+(X-1)+(X-2)+···+3+2+1≥100

可以看出来,这时候区块个数和第一个区块包含的层数其实是相等的。

在这里插入图片描述

现在我们回过头来,再仔细看看方程式,是不是有点熟悉,不就是等差数列求和么!

所以我们化简方程式:

X(X+1)/2≥100

这里X最小值我们向上取整,得到14。

有同学会问为什么一定到1呢,最后一个区块一定只有1层吗?

不是的,到1是表示在X个区块的情况下,最多能覆盖的层数。

比如我们这个例子,X是14,求出的楼层总数是105,也就是可以覆盖105层,题目要求的100层当然绰绰有余。

由此,第一个区块包含14层楼,即1到14层;

第二个区块包含13层楼,即15到27层;

第三个区块包含12层楼,即28到39层;

······

第一颗鸡蛋依次试第14、27、39、50、60、69、77、84、90、95、99、100层。只要期间任何一次鸡蛋碎了,就拿第二颗鸡蛋从上一次的安全层之后开始逐层尝试,直至第二颗鸡蛋也摔碎为止。

用这个方法,总次数一定不会超过14次

因为最高安全楼层越高,第一颗鸡蛋的尝试次数也就越多,但第二颗鸡蛋的尝试次数随之越来越少,两者始终维持着一种微妙的平衡,最后总的尝试次数波动也不会太大。

在这里插入图片描述

下面是全部的代码实现:

// 假设arr表示100层楼,每层楼鸡蛋会不会碎,如果arr[i] === 1 表示i层楼的鸡蛋会碎,arr[i] === 0表示第i层楼的鸡蛋不会碎// 暴力
const throwEggs1 = (arr) => {for(let i = 1; i <= 100; i++) {if (arr[i] === 1) {return i}}
}// 常规二分const throwEggs2 = (arr) => {let left = 1let right = 100let mid = 0while(left <= right) {mid = Math.floor(left + right) / 2if (arr[mid] === 0) {left = mid + 1} else {right = mid - 1}}return mid
}// 均衡切割
const throwEggs3 = (arr) => {let count = 0// 第一个鸡蛋for (let i = 1; i < 10; i++) {if (arr[i * 10] === 1) {count = i * 10break}}// 第二个鸡蛋for(let i = count - 1; i >= count - 10; i--) {if (arr[i] !== 1) {return i}}
}// 微妙平衡
const throwEggs4 = (arr) => {let block = 10let count = 0// block(block + 1) / 2 >= 100while(block * block + block < 100 * 2) {block++}// 第一个鸡蛋的尝试let temp = block // 每层区块的最后一个while(temp <= 100) {if (arr[temp] === 1) {count = tempbreak}--blocktemp += block}// 第二个鸡蛋的尝试for(let i = count - 1; i >= count - block; i--) {if (arr[i] === 0) {return i}}
}

除了文中我们讨论的解法,这道题也还有其他解法可以选择,如果感兴趣大家可以自己研究研究,也欢迎来一起讨论!

凭心而论,要是在毫无准备的情况下,一般人只能想到第一种,做过算法题的,应该能够想到第二种,想到解法三已经是最优解了,能在这么短的时间内想到解法四的大神是凤毛麟角。

好了,祝大家周末愉快!

最后,希望大家读完这篇文章都能有所收获!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/106750.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

EureKa快速入门

EureKa快速入门 远程调用的问题 多个服务有多个端口&#xff0c;这样的话服务有多个&#xff0c;硬编码不太适合 eureKa的作用 将service的所有服务的端口全部记录下来 想要的话 直接从注册中心查询对于所有服务 每隔一段时间需要想eureKa发送请求 保证服务还存活 动手实践 …

【C语言基础】源文件与头文件详解

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

五、多表查询-3.3连接查询-自连接

一、语法 二、演示-自连接&#xff08;内连接&#xff09; 【例】查询员工 及其 所属领导的名字&#xff08;managerid&#xff0c;领导也是员工表emp1表中的数据&#xff09; &#xff01;&#xff01;必须起别名&#xff01;&#xff01; ——内连接只查询交集部分的数据 …

grep命令的用法

文章目录 前言一、使用说明二、应用举例 前言 grep 命令用于查找文件里符合条件的字符串。 一、使用说明 -r: 如果需要搜索目录中的文件内容, 需要进行递归操作, 必须指定该参数 -i: 对应要搜索的关键字, 忽略字符大小写的差别 -n: 在显示符合样式的那一行之前&#xff0c;标…

基于MATLAB的径向基函数插值(RBF插值)(一维、二维、三维)

基于MATLAB的径向基函数插值&#xff08;RBF插值&#xff09;&#xff08;一维、二维、三维&#xff09; 0 前言1 RBF思路2 1维RBF函数2.1 参数说明2.1.1 核函数选择2.1.2 作用半径2.1.3 多项式拟合2.1.4 误差项&#xff08;光滑项&#xff09; 3 2维RBF函数4 3维RBF函数 惯例声…

AI 绘画Stable Diffusion 研究(十四)SD 图生图+剪映制作人物说话视频

大家好&#xff0c;我是风雨无阻。 前一篇&#xff0c;我们详细介绍了使用 SadTlaker制作数字人视频案例&#xff0c;感兴趣的朋友请前往查看:AI 绘画Stable Diffusion 研究&#xff08;十三&#xff09;SD数字人制作工具SadTlaker使用教程。 对于没有安装 SadTlaker 插件的朋友…

Vue2向Vue3过度核心技术生命周期

目录 1 Vue生命周期2 Vue生命周期钩子3 生命周期钩子小案例1.1 在created中发送数据1.2 在mounted中获取焦点 4 案例-小黑记账清单4.1 需求图示&#xff1a;4.2 需求分析3.思路分析4.代码准备 1 Vue生命周期 思考&#xff1a;什么时候可以发送初始化渲染请求&#xff1f;&#…

【算法专题突破】双指针 - 复写零(2)

目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后&#xff1a; 1. 题目解析 题目链接&#xff1a;1089. 复写零 - 力扣&#xff08;Leetcode&#xff09; 我先来读题&#xff0c; 题目的意思非常的简单&#xff0c;其实就是&#xff0c; 遇到 0 就复制一个写进数组&a…

ElementUI Table 翻页缓存数据

Element UI Table 翻页保存之前的数据,网上找了一些,大部分都是用**:row-key** 和 reserve-selection,但是我觉得有bug,我明明翻页了…但是全选的的个框还是勾着的(可能是使用方法不对,要是有好使的…请cute我一下…感谢) 所以自己写了一个… 思路: 手动勾选的时候,将数据保存…

jsp 图书销售系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 图书销售系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0&…

Vue实现Excel表格中按钮增加小数位数,减少小数位数功能,多用于处理金融数据

效果图 <template><div><el-button click"increaseDecimals">A按钮</el-button><el-button click"roundNumber">B按钮</el-button><el-table :data"tableData" border><el-table-column v-for&q…

POSTGRESQL 如何用系统函数来诊断权限问题

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

win10 下运行 npm run watch-poll问题

背景&#xff1a;在本地练习laravel项目&#xff0c;windows 宝塔环境&#xff08;之前装过ubuntu子系统&#xff0c;很慢&#xff0c;就放弃了。有知道的兄弟说下&#xff0c;抱拳&#xff09;。以下命令我是在本地项目中用git bash里运行的&#xff0c;最好用管理员权限打开你…

ARM开发,stm32mp157a-A7核中断实验(实现按键中断功能)

1.实验目的&#xff1a;实现KEY1/LEY2/KE3三个按键&#xff0c;中断触发打印一句话&#xff0c;并且灯的状态取反&#xff1b; key1 ----> LED3灯状态取反&#xff1b; key2 ----> LED2灯状态取反&#xff1b; key3 ----> LED1灯状态取反&#xff1b; 2.分析框图: …

Leetcode 2235.两整数相加

一、两整数相加 给你两个整数 num1 和 num2&#xff0c;返回这两个整数的和。 示例 1&#xff1a; 输入&#xff1a;num1 12, num2 5 输出&#xff1a;17 解释&#xff1a;num1 是 12&#xff0c;num2 是 5 &#xff0c;它们的和是 12 5 17 &#xff0c;因此返回 17 。示例…

Django REST framework实现api接口

drf 是Django REST framework的简称&#xff0c;drf 是基于django的一个api 接口实现框架&#xff0c;REST是接口设计的一种风格。 一、 安装drf pip install djangorestframework pip install markdown # Markdown support for the browsable API. pip install …

[matlab]matlab配置mingw64编译器

第一步&#xff1a;下载官方绿色版本mingw64编译器然后解压放到一个非中文空格路径下面 比如我mingw64-win是我随便改的文件名&#xff0c;然后添加环境变量&#xff0c;选择用户或者系统环境变量添加下面的变量 变量名&#xff1a; MW_MINGW64_LOC 变量值&#xff1a;自己的m…

<C++> 内存管理

1.C/C内存分布 让我们先来看看下面这段代码 int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] {1, 2, 3, 4};char char2[] "abcd";char *pChar3 "abcd";int *ptr1 (int *) mal…

QGIS学习1-入门学习

QGIS作为一个广受欢迎的开源GIS&#xff0c;很多GIS的学生都了解过。但是因为学校老师都是教的Arcgis&#xff0c;因此很少去充分的学习。QGIS和arcgis一样&#xff0c;有完整的官方帮助文档&#xff0c;我也是要根据官方的帮助文档进行学习等。 https://www.qgis.org/zh-Hans/…

C++避坑——most vexing parse问题

1."坑"的问题是什么&#xff1f; 先看一段代码&#xff1a; class Functor { public:void operator()(){std::cout << "我是线程的初始函数" << std::endl;} };int main() {std::thread t(Functor());// 强制高速编译器这是一个构造函数!t.j…