forked from realpacific/algorithms
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathWordSearch.kt
204 lines (171 loc) · 6.25 KB
/
WordSearch.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
package questions
import _utils.UseCommentAsDocumentation
import utils.shouldBe
import kotlin.collections.LinkedHashSet
/**
* Given an m x n grid of characters board and a string word, return true if word exists in the grid.
* The word can be constructed from letters of sequentially adjacent cells, where adjacent cells are
* horizontally or vertically neighboring. The same letter cell may not be used more than once.
*
* [Source](https://leetcode.com/problems/word-search)
*/
@UseCommentAsDocumentation
private class WordSearch {
private class Tree(val location: Location) {
private val _children = mutableSetOf<Tree>()
val children: Set<Tree>
get() = _children.toSet()
val childrenLength: Int
get() = _children.size
fun add(tree: Tree) {
_children.add(tree)
}
fun add(location: Location) {
add(Tree(location))
}
}
private val visited: LinkedHashSet<Location> = LinkedHashSet()
fun exist(board: Array<CharArray>, word: String): Boolean {
if (word.length > board[0].size * board.size) return false
val occurrencesOfFirstChar = findOccurrencesInBoard(word[0], board) ?: return false
// when word length is 1, only check occurrences
if (word.length == 1 && occurrencesOfFirstChar.isNotEmpty()) {
return true
}
// else do DFS with every first character occurrence as root
for (occurrence in occurrencesOfFirstChar) {
visited.clear() // restart
val node = Tree(occurrence) // root
if (checkIfExists(board, word.substring(1), node)) {
return true
}
}
return false
}
private fun checkIfExists(board: Array<CharArray>, remaining: String, node: Tree): Boolean {
visited.add(node.location)
val preLength = node.childrenLength
addMatchingCharactersInNeighbors(node, board, remaining[0], node.location) // add children to the Tree [node]
val postLength = node.childrenLength
val matchExists = preLength != postLength
if (matchExists && remaining.length == 1) { // only one character remains and there is already a match
return true
}
for (child in node.children) { // DFS on children
if (checkIfExists(board, remaining.substring(1), child)) {
return true
} else {
visited.remove(child.location) // make it available for revisit later
}
}
return false
}
/**
* Adds the matches of [nextChar] to [node] in the vertical and horizontal neighbors if they haven't been visited yet
*/
private fun addMatchingCharactersInNeighbors(
node: Tree,
board: Array<CharArray>,
nextChar: Char?,
location: Pair<Int, Int>,
) {
if (nextChar == null) return
val (row, col) = location
board.getOrNull(row + 1)?.getOrNull(col)?.also {
if (nextChar == it && !visited.contains(row + 1 to col)) node.add(row + 1 to col)
}
board.getOrNull(row - 1)?.getOrNull(col)?.also {
if (nextChar == it && !visited.contains(row - 1 to col)) node.add(row - 1 to col)
}
board.getOrNull(row)?.getOrNull(col + 1)?.also {
if (nextChar == it && !visited.contains(row to col + 1)) node.add(row to col + 1)
}
board.getOrNull(row)?.getOrNull(col - 1)?.also {
if (nextChar == it && !visited.contains(row to col - 1)) node.add(row to col - 1)
}
}
private fun findOccurrencesInBoard(char: Char, board: Array<CharArray>): MutableList<Location>? {
val occurrences = mutableListOf<Location>()
for (row in 0..board.lastIndex) {
for (col in 0..board[0].lastIndex) {
val character = board[row][col]
if (char == character) {
occurrences.add(row to col)
}
}
}
return if (occurrences.isNotEmpty()) occurrences else null
}
}
private typealias Location = Pair<Int, Int>
fun main() {
WordSearch().exist(
board = arrayOf(
charArrayOf('A', 'B', 'C', 'E'),
charArrayOf('S', 'F', 'E', 'S'),
charArrayOf('A', 'D', 'E', 'E')
), word = "ABCESEEEFSAD"
) shouldBe true
WordSearch().exist(
board = arrayOf(
charArrayOf('A', 'B', 'C', 'E'),
charArrayOf('S', 'F', 'E', 'S'),
charArrayOf('A', 'D', 'E', 'E')
), word = "ABCESEEEFS"
) shouldBe true
WordSearch().exist(
board = arrayOf(
charArrayOf('A', 'B', 'C', 'E'),
charArrayOf('S', 'F', 'E', 'S'),
charArrayOf('A', 'D', 'E', 'E')
), word = "ABCEFSADEESE"
) shouldBe true
WordSearch().exist(
board = arrayOf(
charArrayOf('b'),
charArrayOf('a'),
charArrayOf('b'),
charArrayOf('b'),
charArrayOf('a')
), word = "baa"
) shouldBe false
WordSearch().exist(
board = arrayOf(
charArrayOf('a', 'a', 'a', 'a'),
charArrayOf('a', 'a', 'a', 'a'),
charArrayOf('a', 'a', 'a', 'a')
), word = "aaaaaaaaaaaaa"
) shouldBe false
WordSearch().exist(
board = arrayOf(
charArrayOf('a', 'b'),
charArrayOf('c', 'd')
), word = "abcd"
) shouldBe false
WordSearch().exist(
board = arrayOf(
charArrayOf('a')
), word = "a"
) shouldBe true
WordSearch().exist(
board = arrayOf(
charArrayOf('A', 'B', 'C', 'E'),
charArrayOf('S', 'F', 'C', 'S'),
charArrayOf('A', 'D', 'E', 'E')
), word = "SEE"
) shouldBe true
WordSearch().exist(
board = arrayOf(
charArrayOf('A', 'B', 'C', 'E'),
charArrayOf('S', 'F', 'C', 'S'),
charArrayOf('A', 'D', 'E', 'E')
), word = "ABCCED"
) shouldBe true
WordSearch().exist(
board = arrayOf(
charArrayOf('A', 'B', 'C', 'E'),
charArrayOf('S', 'F', 'C', 'S'),
charArrayOf('A', 'D', 'E', 'E')
), word = "ABCB"
) shouldBe false
}