Kotlin双指针嵌套While循环引发StringIndexOutOfBoundsException问题排查
LeetCode《通过删除字母匹配到字典里最长单词》实现问题排查与修复
我在解决LeetCode的《通过删除字母匹配到字典里最长单词》(Longest Word in Dictionary Through Deleting)问题时,用Kotlin实现了双指针+嵌套While循环的方案,却触发了越界异常:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 1
我加了多重判断防止越界,但错误依然存在,而且外层For循环的执行次数看起来超过了字典元素数量。调试发现:
- 注释内层两个While循环时,外层循环正常遍历0-3;
- 不注释时会额外打印一次
i=0并报错,找不到问题根源。
问题代码
class Solution { fun findLongestWord(s: String, dictionary: List<String>): String { var answer = "" for (i in dictionary.indices) { // Printing i to test the iterations of function // Prints 0, 1, 2, 3 when below while loops are commented out // Prints 0,1,2,3,0 when the below two while loops aren't commented out. println(i) if (answer.length > dictionary[i].length) { continue } var tempString = s.toCharArray().toMutableList() var low = 0 var dictLow = 0 var high = tempString.lastIndex var dictHigh = dictionary[i].lastIndex do { // Commenting out the below two while loops causes function to work while (tempString.size >= answer.length && tempString.isNotEmpty() && tempString[low] != dictionary[i][dictLow] ) { tempString.removeAt(low) high -= 1 } while (tempString.size >= answer.length && tempString.isNotEmpty() && tempString[high] != dictionary[i][dictHigh]) { tempString.removeAt(high) high -= 1 } if (tempString.joinToString("") == dictionary[i]) { var potentialAnswer = tempString.joinToString("") if (potentialAnswer.length > answer.length) { answer = potentialAnswer } else if (potentialAnswer.length == answer.length) { var list = mutableListOf(potentialAnswer, answer).sorted() answer = list[0] } break } else { low += 1 high -= 1 dictLow += 1 dictHigh -= 1 } } while (tempString.size > answer.length && low < high) } return answer } }
问题根源分析
索引越界的核心原因
- 内层While循环的条件判断顺序错误:先访问
tempString[low]或tempString[high],再检查指针是否合法。比如tempString被删除元素后,low可能已经大于等于tempString.size,此时访问元素必然触发越界。 - 未检查
dictLow和dictHigh的合法性:当这两个指针超出当前字典单词的索引范围时,访问dictionary[i][dictLow]会直接抛出异常。 - 手动维护
high变量错误:删除元素后手动high -=1,当tempString被删空时,high会变成-1,但后续循环仍可能访问tempString[high]。
- 内层While循环的条件判断顺序错误:先访问
外层循环重复打印的误解
实际上for (i in dictionary.indices)只会遍历一次字典的所有索引,你看到的重复打印i=0是因为第一次i=0的do-while循环执行了多次,但println(i)是在for循环内部,每次for迭代只会打印一次i。重复打印大概率是调试时的误判,或是异常抛出后调试环境的重复执行。
修复后的代码
class Solution { fun findLongestWord(s: String, dictionary: List<String>): String { var answer = "" for (i in dictionary.indices) { println(i) val dictWord = dictionary[i] // 当前答案更长,直接跳过 if (answer.length > dictWord.length) { continue } val tempChars = s.toMutableList() var low = 0 var dictLow = 0 var high = tempChars.lastIndex var dictHigh = dictWord.lastIndex var isMatched = false do { // 先检查指针合法性,再访问元素,避免越界 while (tempChars.isNotEmpty() && low < tempChars.size && dictLow <= dictHigh && tempChars[low] != dictWord[dictLow] && tempChars.size >= answer.length) { tempChars.removeAt(low) high = tempChars.lastIndex // 直接取最新的lastIndex,避免手动维护出错 } while (tempChars.isNotEmpty() && high >= 0 && dictHigh >= dictLow && tempChars[high] != dictWord[dictHigh] && tempChars.size >= answer.length) { tempChars.removeAt(high) high = tempChars.lastIndex } // 检查是否匹配成功 val currentStr = tempChars.joinToString("") if (currentStr == dictWord) { isMatched = true break } else { // 移动指针前先检查合法性,避免无效循环 if (low >= tempChars.size || dictLow >= dictWord.length) { break } low += 1 dictLow += 1 if (high < 0 || dictHigh < 0) { break } high -= 1 dictHigh -= 1 } } while (tempChars.size > answer.length && low <= high && dictLow <= dictHigh) // 更新答案逻辑优化 if (isMatched) { if (dictWord.length > answer.length) { answer = dictWord } else if (dictWord.length == answer.length) { // 字典序比较,取更小的 answer = if (dictWord < answer) dictWord else answer } } } return answer } }
修复关键点
- 调整条件判断顺序:先检查指针是否在合法范围内,再访问元素,从根源避免越界。
- 自动维护索引:删除元素后直接用
tempChars.lastIndex更新high,避免手动维护错误。 - 增加指针合法性检查:移动指针前先判断是否还能移动,避免无效循环和越界。
- 优化答案更新:直接使用原字典单词比较,无需转换临时字符列表,提升效率。
内容的提问来源于stack exchange,提问作者GoGuy
相关产品推荐
相关产品推荐

