前言
年初找工作时,面过一家自称区块链行业前三的公司(事实上也差不多),他们笔试题蛮有意思的,时隔半年,估计他们也更新题库了,就拿出来和大家分享一下。
题目
翻译如下:
Sandy是一个制作地图的探险家。她从点(0,0)开始探索,每次可以向左、向右、向上或者向下移动一点。例如,如果Sandy站在(x,y)处,她可以移动到(x+1,y),(x-1,y),(x,y+1)或(x,y-1)。她不能跳也不能走对角线方向。
John要阻止Sandy,他会在坐标数字的绝对值之和大于21的地方埋地雷。例如,点(59,-79)有地雷,因为5+7+9+9>21;而点(-113,-104)没有地雷,因为1+1+3+1+0+4<=21。
如果Sandy踩到地雷,她会死。她不能跳过地雷,必须绕着走。
请编写一个程序,来计算Sandy从(0,0)开始可以访问的点的数量。
分析
首先,可以快速判断出这道题考的是图的遍历,遍历的方法无非深度优先,或者广度优先。此题是要计算所有可遍历的点,于是,我选择用广度优先。
然后,因为Sandy可以向四个方向移动,初步可以判断可以遍历的范围是关于XY轴对称。再则,根据地雷的埋放规则“坐标数字的绝对值之和大于21的地方埋地雷”, 可以进一步推出可以遍历的范围是关于y=x、y=-x、y=0和x=0对称的。举个例子,(56,65),(-56,65),(56,-65),(-56,-65)和(65,56),(-65,56),(65,-56),(-65,-56)都是埋放的地雷的点。
于是,我们只需要考虑x>=0,y>=0且x<=y的区域,乘以对称的次数,就可以推出所有可以遍历的点。但需要注意的是对于x>0,y>0且x
编码
按照上述的分析,编码工作就可以依次开始了。
首先,先定义一个关于的点的结构体,并增加一个计算坐标和的函数。
type Point struct {
X int
Y int
}
func (p Point) CoordinateSum() int {
xNumStrs := strings.Split(strconv.Itoa(p.X), "")
yNumStrs := strings.Split(strconv.Itoa(p.Y), "")
numStrs := append(xNumStrs, yNumStrs...)
sum := 0
for _, str := range numStrs {
num, _ := strconv.ParseInt(str, 10, 32)
sum += int(num)
}
return sum
}
CoordinateSum不会改变点的X,Y值,于是,定义和值类型的方法。
接着,在通用的遍历图算法,需要有个map判断当前点是否遍历过,来剪枝。在本题中,还需要判断当前点是否是地雷,于是,我把这两段逻辑写到了一方法里。
var checkedPoints map[Point]bool
func CheckPoint(p Point) bool {
if _, ok := checkedPoints[p]; ok {
return false
}
if p.Y > p.X || p.X < 0 || p.Y < 0{
return false
}
return p.CoordinateSum() <= 21
}
全局变量在工程化的代码中是需要尽量避免使用的,这里图方便,权且用一下。
下面就是广度优先遍历的代码了。
var toCheckPoints = list.New()
checkedPoints = map[Point]bool{}
var point = Point{0, 0}
toCheckPoints.PushFront(point)
checkedPoints[point] = true
for toCheckPoints.Len() > 0 {
element := toCheckPoints.Front()
point = toCheckPoints.Remove(element).(Point)
searchPoint := Point{point.X, point.Y - 1}
Search(searchPoint, toCheckPoints)
searchPoint = Point{point.X - 1, point.Y}
Search(searchPoint, toCheckPoints)
searchPoint = Point{point.X + 1, point.Y}
Search(searchPoint, toCheckPoints)
searchPoint = Point{point.X, point.Y + 1}
Search(searchPoint, toCheckPoints)
}
func Search(searchPoint Point, toCheckPoints *list.List) {
if CheckPoint(searchPoint) {
checkedPoints[searchPoint] = true
toCheckPoints.PushFront(searchPoint)
} else {
if _, ok := checkedPoints[searchPoint]; !ok {
checkedPoints[searchPoint] = false
}
}
}
最后,就是根据遍历的过的点,计算可以遍历点的数量。
pointsOnMap := 0
var minedPoints = list.New()
var visiblePoints = list.New()
for point, visible := range checkedPoints {
if visible {
if point.X == 0 && point.Y == 0 {
pointsOnMap += 1
} else if point.Y == 0 || point.X == point.Y {
pointsOnMap += 4
} else {
pointsOnMap += 8
}
visiblePoints.PushFront(point)
}else {
minedPoints.PushFront(point)
}
}
fmt.Println("Points on map are", pointsOnMap)
程序写好,但是不确定对不对怎么办?
这个吗,就需要把可以遍历的点和地雷分别画出来,就可以知道。
这里,我选择了用Echarts的散点图的在线示例来画。地址是https://echarts.baidu.com/examples/editor.html?c=scatter-simple
使用的配置是:
option = {
dataZoom: [
{
type: 'slider',
show: true,
xAxisIndex: [0],
start: 0,
end: 500,
},
{
type: 'slider',
show: true,
yAxisIndex: [0],
start: 0,
end: 250
}
],
xAxis: {},
yAxis: {},
series: [{
symbolSize: 1,
data: [
[10.0, 8.04],
],
type: 'scatter'
}]
};
用算出来的点,替换data部分就可以画出来了。
可以遍历的点的图是:
地雷的点的图是:
完整代码
package main
import (
"container/list"
"fmt"
"strconv"
"strings"
)
type Point struct {
X int
Y int
}
func (p Point) CoordinateSum() int {
xNumStrs := strings.Split(strconv.Itoa(p.X), "")
yNumStrs := strings.Split(strconv.Itoa(p.Y), "")
numStrs := append(xNumStrs, yNumStrs...)
sum := 0
for _, str := range numStrs {
num, _ := strconv.ParseInt(str, 10, 32)
sum += int(num)
}
return sum
}
var checkedPoints map[Point]bool
func main() {
var toCheckPoints = list.New()
checkedPoints = map[Point]bool{}
var point = Point{0, 0}
toCheckPoints.PushFront(point)
checkedPoints[point] = true
for toCheckPoints.Len() > 0 {
element := toCheckPoints.Front()
point = toCheckPoints.Remove(element).(Point)
searchPoint := Point{point.X, point.Y - 1}
Search(searchPoint, toCheckPoints)
searchPoint = Point{point.X - 1, point.Y}
Search(searchPoint, toCheckPoints)
searchPoint = Point{point.X + 1, point.Y}
Search(searchPoint, toCheckPoints)
searchPoint = Point{point.X, point.Y + 1}
Search(searchPoint, toCheckPoints)
}
//fmt.Println(len(checkedPoints))
//fmt.Println(checkedPoints)
pointsOnMap := 0
var minedPoints = list.New()
var visiblePoints = list.New()
for point, visible := range checkedPoints {
if visible {
if point.X == 0 && point.Y == 0 {
pointsOnMap += 1
} else if point.Y == 0 || point.X == point.Y {
pointsOnMap += 4
} else {
pointsOnMap += 8
}
visiblePoints.PushFront(point)
}else {
minedPoints.PushFront(point)
}
}
fmt.Println("Points on map are", pointsOnMap)
//for e := minedPoints.Front(); e != nil; e = e.Next() {
// p := e.Value.(Point)
// fmt.Println("[", p.X, ",", p.Y,"],")
//}
}
func Search(searchPoint Point, toCheckPoints *list.List) {
if CheckPoint(searchPoint) {
checkedPoints[searchPoint] = true
toCheckPoints.PushFront(searchPoint)
} else {
if _, ok := checkedPoints[searchPoint]; !ok {
checkedPoints[searchPoint] = false
}
}
}
func CheckPoint(p Point) bool {
if _, ok := checkedPoints[p]; ok {
return false
}
if p.Y > p.X || p.X < 0 || p.Y < 0{
return false
}
return p.CoordinateSum() <= 21
}
答案:287881
后记
笔试我是答对了,但是也没能得到面试机会……
HR反馈说,面试官觉得我的年龄和实力不符合团队结构的期望 ˚‧º·(˚ ˃̣̣̥⌓˂̣̣̥ )‧º·˚
那次面试,也最终断了我继续在区块链行业奋斗的希望。没办法,我可是要工作养家的男人哈。