codewars 7×7 Skyscrapers 问题解决

  • 1、背景说明
  • 2、题目分析
    • 2.1 题目描述
    • 2.2 4x4的问题
    • 2.3 思路分析
  • 3、代码编写
    • 3.1 全排列组合及统计
    • 3.2 限制规则统计
    • 3.3 根据规则列表开始填写
    • 3.4 空白格子的填写
    • 3.5 使用到的辅助方法
  • 4、总结

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 matrix int[][]. 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-311-84-712-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 := falsefor _, e := range in {if e == c {exist = truebreak}}if exist {continue}temp := make([]int, 0)temp = append(temp, in...)temp = append(temp, c)next <- temp}
}func permutaionConImpl(req chan []int, out chan []int, s []int) {go func() {//递归退出条件: len(v) == len(s)-1v, ok := <-reqif !ok {return}next := outif len(v) != len(s)-1 {next = make(chan []int)permutaionConImpl(next, out, s)}prefixIncrement(v, s, next)for in := range req {prefixIncrement(in, s, next)}close(next)}()
}
  • 然后统计列表中每个组合正向反向能看到的层数
...
result := PermutationConcurrency(data)for _, items := range result {// 统计正向反向数出的层数count := calcCount(items)reCount := calcCount(reverse(items))// 组装规则字符串字典clueListMap[count][reCount] = append(clueListMap[count][reCount], items)clueListMap[count][0] = append(clueListMap[count][0], items)
}
...func calcCount(data []int) int {max, count := data[0], 1for _, cur := range data[1:] {if cur > max {count++max = cur}}return count
}

3.2 限制规则统计

根据规则条件分组

...
selfClueNodeList := make([]*SelfClueNode, 0)existMap := make(map[int]interface{})for i := 0; i < len(clues); i++ {if clues[i] == 0 {continue}// 组装有限制的规则列表if i < N {selfClueNodeList = append(selfClueNodeList, &SelfClueNode{i, clues[i], clues[3*N-1-i]})if clues[3*N-1-i] != 0 {existMap[3*N-1-i] = struct{}{}}} else if i < 2*N {selfClueNodeList = append(selfClueNodeList, &SelfClueNode{i, clues[i], clues[5*N-1-i]})if clues[5*N-1-i] != 0 {existMap[5*N-1-i] = struct{}{}}} else {// 需要去掉已经在双向列表中的规则if _, exists := existMap[i]; exists {continue}selfClueNodeList = append(selfClueNodeList, &SelfClueNode{i, clues[i], 0})}}sort.Slice(selfClueNodeList, func(i, j int) bool {// 排序规则// 1、forward和rev都不为0的排前面// 2、两个clue中forward, rev最大的排前面maxNum := 0if selfClueNodeList[j].Forward > maxNum {maxNum = selfClueNodeList[j].Forward}if selfClueNodeList[j].Rev > maxNum {maxNum = selfClueNodeList[j].Rev}if selfClueNodeList[i].Rev == 0 && selfClueNodeList[j].Rev != 0 {return false}if selfClueNodeList[j].Rev == 0 && selfClueNodeList[i].Rev != 0 {return true}return selfClueNodeList[i].Forward > maxNum || selfClueNodeList[i].Rev > maxNum
})
...

3.3 根据规则列表开始填写

每个规则的填写

...if clues.Index < N {// 从上到下,列ifReverse = falseisRow = falsecurIndex = clues.Indexfor i := 0; i < N; i++ {nowData[i] = result[i][curIndex]}} else if clues.Index < 2*N {// 反向,行,需要将数据倒转然后从左到右ifReverse = trueisRow = truecurIndex = clues.Index - Nfor j := 0; j < N; j++ {nowData[j] = result[curIndex][j]}} else if clues.Index < 3*N {// 反向,列,需要将数据倒转然后从上到下ifReverse = trueisRow = falsecurIndex = 3*N - 1 - clues.Indexfor i := 0; i < N; i++ {nowData[i] = result[i][curIndex]}} else {// 从左到右,行ifReverse = falseisRow = truecurIndex = 4*N - 1 - clues.Indexfor j := 0; j < N; j++ {nowData[j] = result[curIndex][j]}}for cluesListIndex := clueIndexList[level]; cluesListIndex < len(cluesNodeList); cluesListIndex++ {data := cluesNodeList[cluesListIndex]if ifReverse {data = reverse(data)}if isMatch(data, nowData, result, isRow, curIndex) {clueIndexList[level] = cluesListIndex + 1fillData(result, data, curIndex, isRow)if dfs(level+1, result, cluesList, clueIndexList) {return true}clueIndexList[level] = 0fillData(result, nowData, curIndex, isRow)}}
...

3.4 空白格子的填写

在填写完规则行列后还需要填写剩下的空白格子

...i, j := zeroNodeList[level][0], zeroNodeList[level][1]for num := 1; num <= N; num++ {if isValid(result, i, j, num) {result[i][j] = numif dfs1(level+1, result, zeroNodeList) {return true}result[i][j] = 0}}
...

3.5 使用到的辅助方法

// 校验行列是否重复
func isValid(result [][]int, i, j, num int) bool {for k := 0; k < N; k++ {if k != j && result[i][k] != 0 && result[i][k] == num {return false}}for k := 0; k < N; k++ {if k != i && result[k][j] != 0 && result[k][j] == num {return false}}return true
}// 填充数据
func fillData(result [][]int, data []int, index int, isRow bool) {if isRow {for j := 0; j < N; j++ {result[index][j] = data[j]}} else {for i := 0; i < N; i++ {result[i][index] = data[i]}}
}func reverse(data []int) []int {result := make([]int, len(data))copy(result, data)for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {result[i], result[j] = result[j], result[i]}return result
}func calcCount(data []int) int {max, count := data[0], 1for _, cur := range data[1:] {if cur > max {count++max = cur}}return count
}func isMatch(data []int, existsNum []int, result [][]int, isRow bool, index int) bool {for i := 0; i < N; i++ {if existsNum[i] == 0 {continue}if existsNum[i] != data[i] {return false}}if isRow {// 行需要判断每一列是否有重复for j := 0; j < N; j++ {if existsNum[j] != 0 {continue}for i := 0; i < N; i++ {if i == index {continue}if result[i][j] != 0 && result[i][j] == data[j] {return false}}}} else {// 列需要判断每一行是否有重复for i := 0; i < N; i++ {if existsNum[i] != 0 {continue}for j := 0; j < N; j++ {if j == index {continue}if result[i][j] != 0 && result[i][j] == data[i] {return false}}}}return true
}

4、总结

我以前接触过数独这种类似题目,所以在看到这个题目时会在第一时间就想到使用回溯法来进行解题,但因为有时间限制,从而不能像数独一样用爆破法一个格子一个格子的填写,加上限制条件的特殊性,最终想到一整行或一整列的解题方法,成功的在限制时间内解出了这道题。但我感觉这个解法并不是最优的解法,其中还有很多可以优化的地方,我自己一时半会是想不出来了,希望大家能多多的提提建议,尽量的优化解答。

这是我第一次在csdn上写文章,磕磕碰碰了几天最终写完了,算是完成了长时间来的一次尝试,文笔不好所以导致文章的内容质量不是很高,今后会持续的做知识输出,也会提高自己的写作能力,如果有写得不好的地方希望大家能理解!

原创不易!转载请注明作者!谢谢!

codewars 7×7 Skyscrapers 问题解决相关推荐

  1. 【Codewars】7×7 摩天大楼

    介绍 链接:7×7 Skyscrapers C#答案(原因:懒,但是完全可以转成C++):bajdcc/learnstl 题目(机翻): 在7乘7格的网格中,你只想在每个广场上放置一个摩天大楼,只有一 ...

  2. [导入]ArcGIS破解克隆后出错问题解决

    学校机房要装ArcGIS软件,因为我这有最新从网上拖下来的完整版,于是我和几个同学去帮忙.在装完一台机子以后,老师使用网络发布功能发布到其它机器.但是现在问题出来了.破解因为是建立在一定机器名上的,机 ...

  3. TSP问题解决:模拟退火、贪心法、爬山法,Python实现

    TSP问题解决:模拟退火.贪心法.爬山法,Python实现这里写目录标题 一.TSP问题 二.简单介绍:贪心法.爬山法.模拟退火 三.python代码实现 四.分别用这三种方法得出结果,进行比较 一. ...

  4. Moto Z6 手机 联通GSM卡上网设置, 使用技巧及疑难问题解决

    本篇主要内容 一. Moto Z6 手机 联通GSM卡 上网设置 二. 联通GPRS业务相关 三. 联通活动 四. UP新势力使用常识 五."新势力"相关使用规则 六. 实用功能介 ...

  5. jenkins部署成功执行自动化测试代码失败问题解决

    一.问题描述 我的jenkins运行在虚拟机上,mysql服务在主机上: 1.先执行jenkins的deploy任务,拉取远程开发代码并进行部署 2.deploy任务部署完成触发部署test任务,运行 ...

  6. 外网映射nginx端口丢失问题解决

    一.背景: nginx监听8082端口,2个tomcat分别为8180和8280,外网映射端口为13410. 实际的访问地址有2个,内网是http://10.130.0.250:8082/jwell- ...

  7. 【Matlab小问题】记一次simulink修改后保存就会崩溃问题解决

    在网上找了说更新显卡驱动,还有删除libfreetype.dll的方法,这俩种方法我试过了,都没有效果 MATLAB 崩溃文件: C:\Users\admin\AppData\Local\Temp\m ...

  8. Docker命令和问题解决、ES常用操作

    总结一些日常使用的Docker命令和遇到问题的解决方法,还有ES的一些常用语句,便于提高日常工作中是Docker和ES时提供工效率. 一.Docker常用语句和问题解决 1.1什么是Docker Do ...

  9. [转]HA高可用集群中“脑裂“问题解决

    什么是脑裂(split-brain) 在"双机热备"高可用(HA)系统中,当联系两个节点的"心跳线"断开时(即两个节点断开联系时),本来为一个整体.动作协调的H ...

  10. npm ERR! renren-fast-vue@1.2.2 build: `gulp`问题解决

    npm ERR! renren-fast-vue@1.2.2 build: `gulp`问题解决 renren-fast-vue下载 环境说明 问题描述 问题解决 1.第一步是升级gulp到4.0 2 ...

最新文章

  1. TensorFlow练习18: 根据姓名判断性别
  2. 为什么单击用户账户没有反应_为什么您的网站没有流量?是因为用户搜不到你!...
  3. ARC 100 C - Linear Approximation题解---三分法
  4. xss植入_前端安全之XSS攻击
  5. ssm oracle mysql_ssm连接oracle数据库
  6. java框架_Java 中几种常用的 RPC 框架介绍
  7. 花费巨资参加SAP培训真的有用吗?
  8. 航班预订系统测试用例
  9. BigGAN高保真自然图像合成的大规模GAN训练
  10. 知乎周源微信_每周源代码41-搜索代码,共享代码和阅读代码(和注释)
  11. 如何保护眼睛,可采用语音方式获取外界知识,并积极锻炼身体做到劳逸结合。
  12. Unity3D_3dsMax-Vray材质导入
  13. asp.net实现识别客户端浏览器或操作系统
  14. Linux与数据结构 2019-2-1
  15. 我的物联网项目(二十六) 商家微信充值流程优化
  16. 三菱FX3U——ST编程点动与自锁
  17. 基础操作--Ubuntu常用命令
  18. 可编程作息时间控制器设计
  19. 来可电子串口RS232/485/UART转CANbus总线转换器网关CANUART-100T
  20. 机器运算知识点计算机组成原理,计算机组成原理知识点有哪些

热门文章

  1. 《微观经济学》第一章
  2. 二十四节气—立秋,文案、海报分享。
  3. 电脑很大,电脑内存很大为什么还是很卡
  4. python模拟行星运动_动态模拟运行太阳系的行星运转
  5. 我的JAVA求学之路
  6. A + B Problem Too
  7. SpringBoot集成Nacos
  8. 什么是uclinux?
  9. linux skyeye,用skyeye运行uClinux内核
  10. Error:field larger than field limit(131072)解决方法