白板题跟系统设计问题的相同点,就是重视
厘清问题
与沟通
。
相比於系统设计,白板题往往需要写出能够运行的程序
,或者提供面试官认为可行性高的演算法
;大部分的白板题难度会落在 LeeCode Easy 的等级,有程序基础的人基本上都能够给出答案,但除了给出答案外,程序的正确率与执行效率
也会列入评分。
本篇文章笔主要是分享答题思路
以及完善程序
,演算法并非笔者所擅长;如果想提升演算法的能力,除了买一本书好好阅读外,同时也要去 LeetCode 刷题累积实战经验。
有些公司是考 LeeCode Medium/Hard 等级的题目,除非天赋异禀;否则这个难度需要花大量时间(可能半年以上)练习,才能在现场稳定发挥。
初探白板题
设计一个简易的抽奖程序
衍伸问题
白板题大概分成以下几种形式:
相较於其他形式,更注重与面试官的「沟通」与「确认问题」。
现在的 IDE 实在太方便了,函式会自动补全、错误会直接 Highlight,很多工程师脱离 IDE 後真的是武功全废。
有些人会因为不适应有人盯着写程序,导致现场发挥失常。
也许你已经非常熟悉 LeeCode 的解题模式;但白板题跟 LeeCode 不同的地方在於通常没有测资、题目相对模糊
。
如果你接到题目就直接解题,可能会忽略一些隐性条件
;在了解题目时如果发现模糊地带,请与面试官厘清问题
,有时自己想像出来的条件反而会增加题目的复杂度。
通常在你给出第一版的解答後,面试官会以你的解答为基础循序渐进问一些延伸性的问题
,这部分除了考核程序能力外,也是判断求职者在开发过程中能否与团队成员沟通
;毕竟随着专案规模的扩增,单打独斗的情境已不多见。
如果遇上你无法解决的白板题,请以积极的态度与面试官沟通你想到的解题方式和卡住的点
;相比於求职者的程序能力,有些面试官更加看重求职者的人格特质
;有些人也许程序能力不足,但能理解面试官的问题,并在讨论过程中作出改善;若能在解题过程中被面试官视为有潜力的人才
,也是有机会获得 Offer 的。
备注 1:解题过程中并不是所有面试官都是以「讨论」的形式与你沟通,有些是以「质问」的态度面对求职者;遇到用「质问」态度的面试官,你也要思考
自己是否能够与这样的同事一起共事
。
备注 2:白板题需要有良好的程序基础与抗压性,才可能在面试现场表现优良。
为了方便读者阅读与验证,本篇文章以上机考
的形式向大家分享。
请你设计一个抽奖系统,奖品有「一奖、二奖、三奖」,其余都是感谢奖。
一奖、二奖、三奖的中奖机率可以自由输入,若没输入则默认一奖(1%)、二奖(2%)、三奖(3%)。
时间限制:5 分钟
这个题目虽然简单,却有很多延伸的议题可以讨论
;是一个了解求职者基本功
的经典题目。
在面对白板题时,笔者是先求有再求好
;如果一昧追求高效但自己不熟悉的演算法,有时会有意想不到的错误。
笔者的逻辑是先把签放入签筒,再透过乱数抽奖印出结果,以符合题目的基础要求为目标:
调整参数
变更中奖机率。默认
的中奖机率:一奖(1%)、二奖(2%)、三奖(3%)。Math.random()
函式模拟乱数抽奖。中奖结果
。// 写一个函式,有默认的抽奖机率:一奖(1%)、二奖(2%)、三奖(3%)
function lottery(first_prize = 1, second_prize = 2, third_prize = 3) {
let lickbox = [];
// 放入签筒
for (var i = 0; i < 100; i++) {
if (first_prize !== 0) {
lickbox.push("一奖");
first_prize--;
} else if (second_prize !== 0) {
lickbox.push("二奖");
second_prize--;
} else if (third_prize !== 0) {
lickbox.push("三奖");
third_prize--;
} else {
lickbox.push("感谢奖");
}
}
// 抽奖
console.log(lickbox[Math.floor(Math.random() * 99)]);
}
lottery();
上面的程序先不论演算法优劣,还有许多细节是没有被考虑到的
;在完成 MVP 後有些面试官会先问你:「之前有写过类似的程序吗?」如果没有相关经验就老实说没有吧;接下来面试官会以这个程序为基础,跟你讨论哪里还可以做改善。
考点:求职者对浮点数的处理
很显然,如果使用者输入的机率包含小数点,现在的函式无法应付,下面是笔者的处理方式:
最长的小数点
有几位(N)。就没有小数点
了。签筒的总数量
。function lottery(first_prize = 1, second_prize = 2, third_prize = 3) {
// 取得最长小数点
let tmp = [first_prize, second_prize, third_prize];
let longest = 0;
tmp.forEach((prize) => {
let after_point = prize.toString().split(".")[1];
if (after_point) {
if (after_point.length > longest) {
longest = after_point.length;
}
}
});
// 让机率变整数
let multiple = Math.pow(10, longest);
// 没有用 Math.floor 会出现 js 小数点结尾的 bug,ex:11100.000000000002
first_prize = Math.floor(first_prize * multiple);
second_prize = Math.floor(second_prize * multiple);
third_prize = Math.floor(third_prize * multiple);
// 签筒的总数量
let max = multiple * 100;
let lickbox = [];
// 放入签筒
for (var i = 0; i < max; i++) {
if (first_prize !== 0) {
lickbox.push("一奖");
first_prize--;
} else if (second_prize !== 0) {
lickbox.push("二奖");
second_prize--;
} else if (third_prize !== 0) {
lickbox.push("三奖");
third_prize--;
} else {
lickbox.push("感谢奖");
}
}
// 抽奖
console.log(lickbox[Math.floor(Math.random() * (max - 1))]);
}
lottery(1.11, 2.34, 3.567);
考点:能举例会发生错误的情境,并设计程序来验证传入的参数
如果没有对输入参数做验证,程序的稳定性真的是极低,下面列举几个要做的基础验证:
中奖机率不可能超过 100%
,如果一奖、二奖、三奖加起来超过 100 是不合理的。中奖机率要是正数
,输入负的机率也是不合理。文字或是符号就一定是错的
。这边笔者透过撰写一个函式(validate_input)来验证输入的参数,如果不符规则就会跳出错误讯息
。
function validate_input(first_prize, second_prize, third_prize) {
// 确认是否为正数
var reg = /^(?=.+)(?:[1-9]\d*|0)?(?:\.\d+)?$/;
if (!(reg.test(first_prize) && reg.test(second_prize) && reg.test(third_prize))) {
console.log("请确认输入参数皆为正数!");
return false;
}
// 确认没爆表
if (first_prize + second_prize + third_prize > 100) {
console.log("中奖机率超过 100 %,爆表了!");
return false;
}
return true;
}
const argsArray = [
[100, 2.34, 3.567], //中奖率不可能超过100
[-1, 2.34, 3.567], //机率不可为负
[1, "错误", 3.567], //含有文字
[1, 2.34, 3.567],
];
argsArray.forEach((args) => {
console.log("验证: " + args);
validate_input.apply(this, args);
});
考点:是否有自动化的测试方案来验证程序
程序的正确性并不是用嘴说的,面试官想了解你会用什麽方法验证程序的正确性
。
笔者这边会写一个测试函式来蒐集抽奖结果,并对原本程序做如下调整:
这里附上调整过的完整程序:
function initLickbox(first_prize = 1, second_prize = 2, third_prize = 3) {
// 先验证输入参数
if (!validate_input(first_prize, second_prize, third_prize)) {
return;
}
// 取得最长小数点
let tmp = [first_prize, second_prize, third_prize];
let longest = 0;
tmp.forEach((prize) => {
let after_point = prize.toString().split(".")[1];
if (after_point) {
if (after_point.length > longest) {
longest = after_point.length;
}
}
});
// 让机率变整数
let multiple = Math.pow(10, longest);
// 没有用 Math.floor 会出现 js 小数点结尾的 bug,ex:11100.000000000002
first_prize = Math.floor(first_prize * multiple);
second_prize = Math.floor(second_prize * multiple);
third_prize = Math.floor(third_prize * multiple);
// 签筒的总数量
let max = multiple * 100;
let lickbox = [];
// 放入签筒
for (var i = 0; i < max; i++) {
if (first_prize !== 0) {
lickbox.push("一奖");
first_prize--;
} else if (second_prize !== 0) {
lickbox.push("二奖");
second_prize--;
} else if (third_prize !== 0) {
lickbox.push("三奖");
third_prize--;
} else {
lickbox.push("感谢奖");
}
}
return lickbox;
}
function lottery(lickbox = []) {
let max = lickbox.length;
// 抽奖
let result = lickbox[Math.floor(Math.random() * (max - 1))];
return result;
}
function validate_input(first_prize, second_prize, third_prize) {
// 确认是否为正数
var reg = /^(?=.+)(?:[1-9]\d*|0)?(?:\.\d+)?$/;
if (!(reg.test(first_prize) && reg.test(second_prize) && reg.test(third_prize))) {
console.log("请确认输入参数皆为正数!");
return false;
}
// 确认没爆表
if (first_prize + second_prize + third_prize > 100) {
console.log("中奖机率超过 100 %,爆表了!");
return false;
}
return true;
}
function qa(test_times = 10000) {
// 设定中奖机率
let first_prize = 1.5,
second_prize = 2.33,
third_prize = 3.98;
let thanks_prize = 100 - first_prize - second_prize - third_prize;
let lickbox = initLickbox(first_prize, second_prize, third_prize);
let first = 0,
second = 0,
third = 0,
thanks = 0;
for (var i = 0; i < test_times; i++) {
let result = lottery(lickbox);
if (result == "一奖") {
first++;
} else if (result == "二奖") {
second++;
} else if (result == "三奖") {
third++;
} else {
thanks++;
}
}
console.log("一奖:" + ((first / test_times) * 100).toFixed(2) + "% (" + first_prize + "%)");
console.log("二奖:" + ((second / test_times) * 100).toFixed(2) + "% (" + second_prize + "%)");
console.log("三奖:" + ((third / test_times) * 100).toFixed(2) + "% (" + third_prize + "%)");
console.log("感谢奖:" + ((thanks / test_times) * 100).toFixed(2) + "% (" + thanks_prize + "%)");
}
qa();
// console.log(lottery(initLickbox()))
图片为程序测试的结果:
上面只是笔者在面对白板题时的简单发想,这边还有一些问题留给读者思考:
即使是抽奖系统这种看似简单的题目,如果深入探讨还是有许多的细节需要完善。
笔者上面所提出的衍伸问题
,其实是求职者一开始要跟面试官厘清的细节
,如果能够在开始作业前就定义好需求
,在开发的过程中就能避免不合适的设计
。
不过有些面试官会倾向等求职者完成 MVP 後再做细节讨论
,笔者就曾遇过面试官出完白板题後就离开 15 分钟让求职者作答;所以实际状况还是需要读者在现场随机应变
。
感谢大家的阅读,如果喜欢我的文章可以订阅
接收通知;如果有帮助到你,按Like
可以让我更有写文的动力,我们明天见~
我在 Medium 平台 也分享了许多技术文章
❝ 主题涵盖「MIS & DEVOPS、资料库、前端、後端、MICROSFT 365、GOOGLE 云端应用、自我修炼」希望可以帮助遇到相同问题、想自我成长的人。❞
连续 30 天不中断每天上传一支教学影片,教你如何用 React 加上 Firebase 打造社群...
D18. 函式库 #include<stdio.h> main(){ printf(&q...
程序语言,也是一种语言,所以也有一些语法可以归类。 像学英文一样,知道语法可以帮助我们学会如何可以组...
今天来做这个很实用的东东,很多频道都有这个功能 将机器人加入频道 机器人要加入频道的话只能加成管理...
今天在学习3d建模的时候,发现一个有趣的东西:3d建模的模型,如果要方便使用在各种软件当中的话,需要...