本文首发于 语雀文档

题目

https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/

流程图 & 调试代码

所有流程图 & 调试代码,均以 Github 仓库上的为准

https://github.com/blueju/leetcode/

第 1 次尝试

代码

  1. /**
  2. * @param {string} s
  3. * @return {number}
  4. */
  5. var lengthOfLongestSubstring = function (s) {
  6. let stack = new Set();
  7. for (let i = 0; i < s.length; i++) {
  8. for (let j = i; j < s.length; j++) {
  9. if (stack.has(s[j])) {
  10. stack.clear();
  11. continue;
  12. }
  13. }
  14. }
  15. return stack.length;
  16. };

流程图

image.png

结果

未通过测试用例

错在:

  • 无法统计无重复字符最长子串,因为每次发现有重复项时,都直接清空栈了,也算是错误理解题目了。

第 2 次尝试

代码

  1. /**
  2. * @param {string} s
  3. * @return {number}
  4. */
  5. var lengthOfLongestSubstring = function (s) {
  6. let resultStack = new Set();
  7. let tempStack = new Set();
  8. for (let i = 0; i < s.length; i++) {
  9. for (let j = i; j < s.length; j++) {
  10. if (tempStack.has(s[j])) {
  11. if (tempStack.length > resultStack.length) {
  12. resultStack = new Set([...tempStack]);
  13. tempStack.clear();
  14. } else {
  15. tempStack.clear();
  16. }
  17. break;
  18. } else {
  19. tempStack.add(s[j]);
  20. }
  21. }
  22. }
  23. return resultStack.length;
  24. };

流程图

image.png

总结

未通过测试用例

错在:

  • 错误使用了 Set 统计长度的 API
  • 错误使用了 for 的中断跳出

第 3 次尝试

代码

  1. /**
  2. * @param {string} s
  3. * @return {number}
  4. */
  5. var lengthOfLongestSubstring = function (s) {
  6. let result = new Set()
  7. let stack = new Set()
  8. const length = s.length
  9. for (let i = 0; i < length; i++) {
  10. for (let j = i; j < length; j++) {
  11. const item = s[j]
  12. if (stack.has(item)) {
  13. if (result.size < stack.size) {
  14. result = new Set([...stack])
  15. stack.clear()
  16. } else {
  17. stack.clear()
  18. }
  19. break
  20. } else {
  21. stack.add(item)
  22. }
  23. }
  24. }
  25. return result.size
  26. };

总结

未通过测试用例

错在

  • 未考虑特殊情况(当字符串是””和” “时,字符串长度是 0 和 1 的时候)

思考:

  • 结果老是用复杂数据结构存储好吗?我需要只是长度而已。

第 4 次尝试(通过测试用例)

代码

  1. /**
  2. * @param {string} s
  3. * @return {number}
  4. */
  5. var lengthOfLongestSubstring = function (s) {
  6. const length = s.length
  7. if (length === 0) return 0
  8. if (length === 1) return 1
  9. let maxLength = 0
  10. let stack = new Set()
  11. for (let i = 0; i < length; i++) {
  12. for (let j = i; j < length; j++) {
  13. if (stack.has(s[j])) {
  14. maxLength = Math.max(maxLength, stack.size)
  15. stack.clear()
  16. break
  17. } else {
  18. stack.add(s[j])
  19. }
  20. }
  21. }
  22. return maxLength
  23. };

流程图

image.png

总结

通过了测试用例
image.png

第 5 次尝试(通过测试用例)

再想想, 每次清空栈后,第一个 for 循环的索引 i 到下一位,如果我们是 abcd 遇到 c,那之前栈中保存的 abcd 中的 bcd 岂不是重新在下一次索引为 i 的 for 循环中入栈保存。

那我们是不是可以在判断栈中是否已有与 s[j] 相同的数据时,如果有也顺便判断它的位置,然后在下一次索引为 i 的 for 循环时,将 i 直接定位到重复项后面一位,以上一段话中的 abcd 遇到 c 为例,直接 i 定位到 d 处,这样就省去了在下次循环时 i 从 b 开始的时间。

代码

  1. /**
  2. * @param {string} s
  3. * @return {number}
  4. */
  5. var lengthOfLongestSubstring = function (s) {
  6. const length = s.length
  7. if (length === 0) return 0
  8. if (length === 1) return 1
  9. let maxLength = 0
  10. let stack = []
  11. for (let i = 0; i < length; i++) {
  12. for (let j = i; j < length; j++) {
  13. const index = stack.indexOf(s[j])
  14. if (index > -1) {
  15. maxLength = Math.max(maxLength, stack.length)
  16. stack.length = 0
  17. i = i + index
  18. break
  19. } else {
  20. stack.push(s[j])
  21. maxLength = Math.max(maxLength, stack.length)
  22. }
  23. }
  24. }
  25. return maxLength
  26. };

流程图

image.png

总结

通过了测试用例

虽然执行用时反而比第 4 次尝试更长了,我认为主要原因是 Set 改为 Array 的原因,并且我也尝试将第 4 次尝试改为了 Array,执行用时也一样延长到了 600+ ms。但即使如此相比,也并没有快能多,合理怀疑是测试用例的长度不够长。

image.png
思考:

  • 我们能不能一次 for 循环搞定

第 6 次尝试(通过测试用例)

我们之前每次发现重复项后,都改变第一层 for 循环的索引进行重新入栈,我们依旧以 abcdb 这个例子, 第一次 for 循环,发现重复项 b 时,栈的值为 abcd;清空栈第二次 for 循环,发现重复项时,肯定是以 cd 开头,那这个 cd 是重复的,如果测试用例更长,那 cd 这样的重复计算项也会更长,那我们是不是可以考虑将其缓存起来。

看来,举例子很重要呀,能从中发现规律。

代码

基于第 4 次尝试,我们可以按以上思路改成这样:

  1. /**
  2. * @param {string} s
  3. * @return {number}
  4. */
  5. var lengthOfLongestSubstring = function (s) {
  6. const length = s.length
  7. if (length === 0) return 0
  8. if (length === 1) return 1
  9. let maxLength = 0
  10. let stack = []
  11. for (let i = 0; i < length; i++) {
  12. if (stack.includes(s[i])) {
  13. stack.shift()
  14. i--
  15. } else {
  16. stack.push(s[i])
  17. maxLength = Math.max(stack.length, maxLength)
  18. }
  19. }
  20. return maxLength
  21. };

基于第 5 次尝试,我们可以按以上思路改成这样:

  1. /**
  2. * @param {string} s
  3. * @return {number}
  4. */
  5. var lengthOfLongestSubstring = function (s) {
  6. const length = s.length
  7. if (length === 0) return 0
  8. if (length === 1) return 1
  9. let maxLength = 0
  10. let stack = []
  11. for (let i = 0; i < length; i++) {
  12. const result = stack.indexOf(s[i])
  13. if (result > -1) {
  14. stack.splice(0, result + 1)
  15. i--
  16. } else {
  17. stack.push(s[i])
  18. maxLength = Math.max(stack.length, maxLength)
  19. }
  20. }
  21. return maxLength
  22. };

结果

比之前可快多了
image.png