Programming Assignment 4: Boggle
编程作业四
我的代码:BoggleSolver.java
问题简介
Boggle 是一个文字游戏,有 16 个每面都有字母的骰子,开始随机将它们放在 4 * 4 的板子上,如图:

图片来自:wiki。
游戏双方就在上面找有效的单词,单词越长分数越高,有效的单词具体指:
- 有效单词相邻字符的骰子也相邻,上下左右,对角线也行。
- 有效单词不能重复使用某个骰子。
- 有效单词的长度至少为 3。
- 有效单词要能在字典中找到,专有名词不算。
举例:

左边的单词 PINES 是有效的,右边的 DATES 是无效的(显然不相邻)。
不同长度的单词计分如下:
| word length | points |
|---|---|
| 0 - 2 | 0 |
| 3 - 4 | 1 |
| 5 | 2 |
| 6 | 3 |
| 7 | 5 |
| 8+ | 11 |
最后,因为英语单词中字母 Q 后面几乎总是会跟着 U,所以直接在骰子上写了 QU,计分的时候按两个字母算。
任务摘要
Your task. Your challenge is to write a Boggle solver that finds all valid words in a given Boggle board, using a given dictionary. Implement an immutable data type BoggleSolver with the following API:
public class BoggleSolver
{
// Initializes the data structure using the given array of strings as the dictionary.
// (You can assume each word in the dictionary contains only the uppercase letters A through Z.)
public BoggleSolver(String[] dictionary) // Returns the set of all valid words in the given Boggle board, as an Iterable.
public Iterable<String> getAllValidWords(BoggleBoard board) // Returns the score of the given word if it is in the dictionary, zero otherwise.
// (You can assume the word contains only the uppercase letters A through Z.)
public int scoreOf(String word)
}
详细要求参见:Boggle。
问题分析
照着 Checklist 里建议的编程步骤一步步做下来。先熟悉作业为我们提供的数据结构:BoggleBoard.java,这个看下 API 就好,要注意的是板子上的 QU 为方便它直接用 Q 表示,后面我们要自己处理。第二步叫我们选个标准的数据结构来表示输入的字典,像 SET, TreeSet,或是 HashSet,用集合来表示一堆没重复的单词构成的字典,想想也是很合理。但后面觉得是没有必要再特地拿一个集合来表示。
第三步建议我们用 DFS 在 Board 上找出可能的单词组合,这步花了点时间才实现。传统的 DFS 是遍历所有的点,而且只会访问每个点一次。但是这里从不同的路径访问同一个字符显然算两个单词,所以递归返回之前还要把这个位置标记为没有被访问过,允许下次从不同的路再访问它。再者,路径上的字符要连起来才能构成单词,于是递归的 DFS 我还加了个 StringBuilder 类型的参数 pre,每次 new 一下传到下一层再接上这个位置的字符。
第四步是很重要的一步,像第三步那样找出相邻字符所有可能组成的单词,我在四乘四的板子上都跑到堆栈溢出。其实,发现字典里没有以当前组成的 pre 为前缀的单词时,这条 DFS 就不用再递归下去了,有很多 DFS 刚开始就会结束,而且基本不会有跑到底的 DFS。建议我们再实现一个数据结构,用来查询字典中是否有以 pre 为前缀的单词。
那第二步集合只是为了方便判断某个单词是否在字典中吗,那完全可以和第四步合起来吧我觉得,像 TrieSet 里就有这两个方法。但是课程里实现的 TrieSET.java 支持的是拓展 ASCII 的 256-way Trie,这里只会有 A 到 Z 共 26 个单词,感觉会浪费很多空间。于是,一开始调用了课程里有的 TST.java(三向单词查找树实现的符号表),以单词(字符串)为键,值随便。最后让我们解决特殊的 "QU" 情形,问题不大,字符是 Q,给 pre 拼接上 QU 就好。
又一番调试,本地测试通过,先交一波上去评测。拿到了 92 分,似乎开了个头,但实际上前面就做挺久了。。。具体是有四个时间测试没有通过,是参考程序的两百多倍。
Test 2: timing getAllValidWords() for 5.0 seconds using dictionary-yawl.txt
(must be <= 2x reference solution)
- reference solution calls per second: 8870.50
- student solution calls per second: 42.90
- reference / student ratio: 206.79
=> passed student <= 10000x reference
=> FAILED student <= 25x reference
=> FAILED student <= 10x reference
=> FAILED student <= 5x reference
=> FAILED student <= 2x reference
那没办法,考虑 Checklist 里建议的可能的优化部分:
Possible Optimizations
You will likely need to optimize some aspects of your program to pass all of the performance points (which are, intentionally, more challenging on this assignment). Here are a few ideas:
Make sure that you have implemented the critical backtracking optimization described above. This is, by far, the most important step—several orders of magnitude!
Think about whether you can implement the dictionary in a more efficient manner. Recall that the alphabet consists of only the 26 letters A through Z.
Exploit that fact that when you perform a prefix query operation, it is usually almost identical to the previous prefix query, except that it is one letter longer.
Consider a nonrecursive implementation of the prefix query operation.
Precompute the Boggle graph, i.e., the set of cubes adjacent to each cube. But don't necessarily use a heavyweight Graph object.
第一点已经实现了,本着有现成就尽量不自己写的精神,再试试调用课程里其它的程序:TrieSET.java 和 TrieST,但本地测试的结果并没有比 TST.java 好,那就只能自己写了。
一开始,还是想着直接用现成的前缀判断方法。先在课程程序的基础上,忙活半天改出了 26-way trie 和 TST with \(R^{2}\) root 这两个版本,但本地测试并没有什么明显的效果,还是一开始的 TST 表现最好,也可能是我改得不好吧。再直接考虑最后一点,预处理一下板子,先算出每个位置的邻居,这样就不用每次 DFS 到了再算一次。效果也一般,而且我一开始用放整数的集合数组(泛型数组)来存,交上去评测编译时会有警告,编译部分还会被扣分。
到了最后的最后,只能自己写单词树了,后来觉得还不如一开始就自己实现好了。然后,当然是参考着课程里的程序来写的,主要目的是优化前缀的查找。因为这边的前缀查找比较特殊,相邻的查找总是只差一个字符,作业就提示我们是否能利用这一现象。在程序里实现单词查找树,前缀的判断就可以融入 DFS。递归传下去的不只是这条路上的 pre,这个 pre 在单词树中的位置节点也可以传下去,只要这个节点一为空,就说明单词树中没有以 pre 为前缀的单词,也就可以跳出这条 DFS。不好说明,可以直接看代码的 DFS 部分:BoggleSolver.java。
大概是这么个思想,最终实现自然是又捣鼓了好久。本地测试通过,交上去,拿了 98 分,还差最后一个测试,只要耗时小于参考程序的两倍就好。于是又盯着代码,各种小优化,像测试用随机的 4 * 4 板子,预计算出邻居不用每次都算,像空间还够,用数组存,下标来加快查找等等,但最终还是差那么 0.03。。。
Test 2: timing getAllValidWords() for 5.0 seconds using dictionary-yawl.txt
(must be <= 2x reference solution)
- reference solution calls per second: 9160.06
- student solution calls per second: 4502.85
- reference / student ratio: 2.03
=> passed student <= 10000x reference
=> passed student <= 25x reference
=> passed student <= 10x reference
=> passed student <= 5x reference
=> FAILED student <= 2x reference
Total: 8/9 tests passed!
去作业论坛寻找新的优化思路,有人说他把板子存成一维的,就不用老是访问作业提供的板子对象查这个位置字符是啥。稍加修改,本地测试感觉良好,貌似会快那么一点点,交上去终于 a 掉了所有点!!
Test 2: timing getAllValidWords() for 5.0 seconds using dictionary-yawl.txt
(must be <= 2x reference solution)
- reference solution calls per second: 6842.46
- student solution calls per second: 4873.98
- reference / student ratio: 1.40
=> passed student <= 10000x reference
=> passed student <= 25x reference
=> passed student <= 10x reference
=> passed student <= 5x reference
=> passed student <= 2x reference
Total: 9/9 tests passed!
据说(论坛)比参考程序耗时少还会有额外分数,但优化点我都用上了,也不想再优化了,100 分就好啦,哈哈。
测试结果

Programming Assignment 4: Boggle的更多相关文章
- 课程一(Neural Networks and Deep Learning),第三周(Shallow neural networks)—— 3.Programming Assignment : Planar data classification with a hidden layer
Planar data classification with a hidden layer Welcome to the second programming exercise of the dee ...
- Algorithms: Design and Analysis, Part 1 - Programming Assignment #1
自我总结: 1.编程的思维不够,虽然分析有哪些需要的函数,但是不能比较好的汇总整合 2.写代码能力,容易挫败感,经常有bug,很烦心,耐心不够好 题目: In this programming ass ...
- Algorithms : Programming Assignment 3: Pattern Recognition
Programming Assignment 3: Pattern Recognition 1.题目重述 原题目:Programming Assignment 3: Pattern Recogniti ...
- Programming Assignment 2: Randomized Queues and Deques
实现一个泛型的双端队列和随机化队列,用数组和链表的方式实现基本数据结构,主要介绍了泛型和迭代器. Dequeue. 实现一个双端队列,它是栈和队列的升级版,支持首尾两端的插入和删除.Deque的API ...
- 课程一(Neural Networks and Deep Learning),第二周(Basics of Neural Network programming)—— 2、编程作业常见问题与答案(Programming Assignment FAQ)
Please note that when you are working on the programming exercise you will find comments that say &q ...
- Programming Assignment 5: Kd-Trees
用2d-tree数据结构实现在2维矩形区域内的高效的range search 和 nearest neighbor search.2d-tree有许多的应用,在天体分类.计算机动画.神经网络加速.数据 ...
- Programming Assignment 4: 8 Puzzle
The Problem. 求解8数码问题.用最少的移动次数能使8数码还原. Best-first search.使用A*算法来解决,我们定义一个Seach Node,它是当前搜索局面的一种状态,记录了 ...
- coursera普林斯顿算法课part1里Programming Assignment 2最后的extra challenge
先附上challenge要求: 博主最近在刷coursera普林斯顿大学算法课part1部分的作业,Programming Assignment2最后的这个extra challenge当初想了一段时 ...
- Programming Assignment 2: Deques and Randomized Queues
编程作业二 作业链接:Deques and Randomized Queues & Checklist 我的代码:Deque.java & RandomizedQueue.java & ...
随机推荐
- C/C++ 类型内存占用详解
最近做一些面试题目碰到了很多次考察C/C++类型内存占用的题目,主要考察队C/C++的指针.类型等的熟悉程度. 本blog为了方面大家参考,总结了常见的类型内存占用的情况,能力所限,若有问题,请指出! ...
- 如何获取div距离浏览器顶部的高度,宽度,内容
JS就可以获取了, document.getElementById("DIV的ID或者其它选择").offsetTop;这是离顶部 JQ可以这样: $("#aaa&quo ...
- springboot 源码笔记
1.springAppication构造器 基于getSpringFactoriesInstances方法构造如下类(获取文件内容在META-INF/spring.factories文件中) 1.1 ...
- Application.DoEvents()和多线程
首先将以下代码放到Button事件里面: private void btnStart_Click(object sender, EventArgs e) { for (int q = 0; ...
- 谷歌眼镜能给Apple Watch带来啥前车之鉴?
当下,你想不听到Apple Watch的消息都难.这款智能手表在三月初发布时,有关它的新闻报道铺天盖地.记者们在博客上对发布会的每个阶段进行了实况报道,苹果粉丝们通过博客. 推特和YouTube视频对 ...
- VFL使用
关于界面布局约束的方法有很多,纯代码布局,可以使用官方原生布局(很繁琐).VFL.Masonary第三方等,在xib或者storyboard中也可以使用Autolayout的界面约束进行布局约束. ...
- 设置navigationbar透明度时的坑
1.需要设置导航条透明度时 UIImage *image = [UIImage imageNamed:@"bg_clear.png"]; //设置背景颜色 [nav2.na ...
- 移动端Web Meta标签
原文 http://blog.segmentfault.com/jianjian_532633/1190000000654839 添加到推刊 在介绍移动端特有 meta 标签之前,先简单说一下 ...
- python中新式类和经典类
python中的类分为新式类和经典类,具体有什么区别呢?简单的说, 1.新式类都从object继承,经典类不需要. Python 2.x中默认都是经典类,只有显式继承了object才是新式类 Pyth ...
- mybatis作用、基本使用、小结
1. MyBatis 1.1. 作用 MyBatis是持久层框架,它是支持JDBC的!简化了持久层开发! 使用MyBatis时,只需要通过接口指定数据操作的抽象方法,然后配置与之关联的SQL语句,即可 ...