codewars 7×7 Skyscrapers 问题解决
1、背景说明
最近工作上的任务完成得比较超前,闲暇之余就会在刷题网站上刷题玩。前两天偶然刷到一道1kyu(codewars上题目难度按等级排名,数字越小等级越高,题目也越难,1kyu代表1级即最难),仔细看了题目描述后发现和之前做过的一道4kyu的题类似,于是我就开始了解题之旅,最终在经历了2天的奋战后终于解决了这个问题。为了更好的巩固从问题中学习到的知识和思路,也为了做一次知识分享的尝试,我把从解题开始到结束的经历和感悟写成了这篇文章,初次尝试会比较生疏,希望大家能多多理解。
2、题目分析
2.1 题目描述
In a grid of 7 by 7 squares you want to place a skyscraper in each square with only some clues:
- The height of the skyscrapers is between 1 and 7
- No two skyscrapers in a row or column may have the same number of floors
- A clue is the number of skyscrapers that you can see in a row or column from the outside
- Higher skyscrapers block the view of lower skyscrapers located behind them
Can you write a program that can solve this puzzle in time?
看到这或许你还不明白这道题想要你解决的问题到底是什么,那么请注意下面这句话:
This kata is based on 4 By 4 Skyscrapers and 6 By 6 Skyscrapers by FrankK. By now, examples should be superfluous; you should really solve Frank’s kata first, and then probably optimise some more. A naive solution that solved a 4×4 puzzle within 12 seconds might need time somewhere beyond the Heat Death of the Universe for this size. It’s quite bad.
里面对为什么题目描述中没有例子的原因做了解释:这道题是基于4x4和6x6的问题之上的,所以为了解决这个问题,我们需要先看一下4x4和6x6的题目。
2.2 4x4的问题
Example:
To understand how the puzzle works, this is an example of a row with 2 clues. Seen from the left side there are 4 buildings visible while seen from the right side only 1:
There is only one way in which the skyscrapers can be placed. From left-to-right all four buildings must be visible and no building may hide behind another building:
Example of a 4 by 4 puzzle with the solution:
看到这里应该大概了解题目想说明什么了吧,想象在每个格子里放置一个高度为格子中数字的’楼房’,然后从对应的方向看过去,能看到几个’楼房’的数量即为格子外的限制条件。
像 1234
这样一排数字,从左往右看,4个格子的’楼房’都能被看到,所以左边是4
,而从右往左看的话因为最右边的格子里的4层高的’楼房’把后边的全部挡住了,所以只能看到1个’楼房’,右边就是1
。
下面我们再来题目需求
Task:
- Finish:
func SolvePuzzle(clues []int) [][]int
- Pass the clues in an array of 16 items. This array contains the clues around the clock, index:
- If no clue is available, add value
0
- Each puzzle has only one possible solution
SolvePuzzle()
returns matrixint[][]
. The first indexer is for the row, the second indexer for the column. (Python: returns 4-tuple of 4-tuples, Ruby: 4-Array of 4-Arrays)
看到这题目需求就完全明确了,一开始会给定一个数组作为限制条件(即从各行各列各方向看过去的楼层高度),我们需要完成一个SolvePuzzle
函数,函数的功能如下:
- 计算一个行列满足给定限定条件的二维数组
- 每行每列的数字不能重复
- 需要在12秒内计算出结果
2.3 思路分析
- 首先想到是8皇后的问题(如果不了解8皇后问题的自行百度喔),但又多了限制条件,于是重新思考思路。重新思考的思路还是基于8皇后之上,这种问题一般都需要使用回溯来解决,主要是找到回溯的条件。苦苦思考之后,发现这个问题的限制条件是一行或一列的,那么可以每次填写一整行和一整列,这样会减少回溯的次数,至于每次回溯的限制,则直接根据行或列两头的限制条件来决定。
- 然后又出现了另外的问题,比如4x4两边的限制条件是1和4,那么就只有一种填法:4321,而且4x4的排列组合很少,完全可以手动把每种限制条件的排序全部列出来再选择。但7x7的问题如果要自己手动写的话就太多,还容易出错,于是就想到了先生成一个全排列的列表,然后遍历列表,把每一个排列从头到尾能数出的高度和从尾到头能数出的高度全部计算出来,同时按照高度分在不同的map中,这样方便后面的使用。
- 各个排列对应的数字都计算好了,现在需要的是划分限制条件数组了,从4x4的问题中我们能够看出,给定了16个数字,但是其中
0-3
和11-8
、4-7
和12-15
是一一对应限制了一列或一行的,那么可以将限制条件筛选出来。首先筛选从小到大筛选两头都有数字的限制条件为一组,因为两头都有限制的行或列对应可以填的数字组合越少,然后再把只有一边有数字的限制条件筛选出来,如果两边都为0的直接忽略。选好之后再按照限制条件的大小进行排序,组装成一个列表,确保限制条件越大的在前面。 - 前提工作已经完成了,这个时候就需要开始填写格子了,首先明确的是,我们需要先填写有限制条件的格子,即每行每列两头有数字的格子,根据限制条件从前面计算的全排列组合高度map中取出组合填入格子中,填入的时候需要判断是否和已有的数字冲突,如果冲突则不符合,需要重新取组合,填好一个限制条件对应的行或列后再填下一个规则,若所有的组合都不能填入表格中,则应该回溯到上一步重新填写,知道所有的规则都填写完为止。
- 不要以为这个时候就完成了,刚刚我们填写的只是有规则的行或列,还有些格子是没有被规则覆盖到的,这个时候我们就需要一个格子一个格子的填写,每个格子填写的时候需要判断行和列中的数字是否重复,如果不重复则填好后继续填下一个格子,如果所有数字都重复则需要回溯到上一个格子重新填写,如果无法完成空白格子的填写,则需要回溯到上一步规则填写中,从最后一个规则重新填写,直到所有的空白格子都完成填写,这个时候才最终完成了解题。
3、代码编写
完整的代码地址 : https://github.com/wshhz/codewars/blob/master/1kyu/solvePuzzle.go
3.1 全排列组合及统计
- 先通过go轻量级的协程来生成全排列的列表
// PermutationConcurrency 并发计算全排列
func PermutationConcurrency(s []int) [][]int {
req, out := make(chan []int), make(chan []int)
//开启goroutine计算
permutaionConImpl(req, out, s)
over := make(chan [][]int)
//要开goroutine读取out,如果放在主函数中,会导致死锁。
go func() {
result := make([][]int, 0)
for res := range out {
result = append(result, res)
}
over <- result
}()
for _, c := range s {
sl := []int{
c}
req <- sl
}
close(req)
return <-over
}
func prefixIncrement(in []int, s []int, next chan []int) {
for _, c := range s {
exist := false
for _, e := range in {
if e == c {
exist = true
break
}
}
if exist {
continue
}
temp := make([]int, 0)
temp = append(temp, in