解析一道区块链行业前三公司的笔试题

Table of Contents

前言

年初找工作时,面过一家自称区块链行业前三的公司(事实上也差不多),他们笔试题蛮有意思的,时隔半年,估计他们也更新题库了,就拿出来和大家分享一下。

题目

)

翻译如下:

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<y的区域,可以访问点可以乘以8;对于x>0和y=x且x>0这两条边,只需要乘以4就好,因为它们是被两个区块所共有的,乘以8就算重了;特别需要注意的是(x=0,y=0)这点,是所有可以遍历的区域的中心,乘以1即可。

编码

按照上述的分析,编码工作就可以依次开始了。

首先,先定义一个关于的点的结构体,并增加一个计算坐标和的函数。

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反馈说,面试官觉得我的年龄和实力不符合团队结构的期望 ˚‧º·(˚ ˃̣̣̥⌓˂̣̣̥ )‧º·˚

那次面试,也最终断了我继续在区块链行业奋斗的希望。没办法,我可是要工作养家的男人哈。