You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
    }
}

问题根源分析

  1. 索引越界的核心原因

    • 内层While循环的条件判断顺序错误:先访问tempString[low]或tempString[high],再检查指针是否合法。比如tempString被删除元素后,low可能已经大于等于tempString.size,此时访问元素必然触发越界。
    • 未检查dictLow和dictHigh的合法性:当这两个指针超出当前字典单词的索引范围时,访问dictionary[i][dictLow]会直接抛出异常。
    • 手动维护high变量错误:删除元素后手动high -=1,当tempString被删空时,high会变成-1,但后续循环仍可能访问tempString[high]。
  2. 外层循环重复打印的误解
    实际上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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 17:15:37