题目描述

:::fold-green 相关原题

LeetCode 原题: ✅ 722. 删除注释

:::

给你一个字符串数组 source,其中 source[i] 表示源码的第 i 个物理行。请返回至少包含一个注释外非空白字符的物理行数。

需要识别 // 行注释、/*…*/ 块注释,以及普通单引号和双引号字面量。字面量中的注释符号视为普通内容,反斜杠可以转义下一个字符。

跨行块注释前后的代码仍分别计入各自的物理行,不能拼接为一行。

示例 1:

输入: source = ["int a; // value","/* start","end */int b;"," "]
输出: 2
解释: 删除注释和纯空白行后,仅保留 int a; 与 int b;。

示例 2:

输入: source = ["a/*comment","more*/b"]
输出: 2
解释: 首行的 a 和末行的 b 都是代码,分别计数,不把它们拼接为一行。

示例 3:

输入: source = ["String s = \"// text\";", "// comment"]
输出: 1
解释: 引号中的 // 是字符串内容,只有第二行是真正的行注释。

提示:

  • 输入词法完整,块注释闭合且不嵌套。
  • 普通字面量不跨行。
  • 不支持原始字符串、文本块或宏导致的词法变化。
  • 跨行块注释前后的代码分别计入各自的物理行。
  • 源码使用可打印 ASCII 字符及制表符。

题意分析

不能把删除注释后的文本重新拼接再统计行数,因为跨行块注释前后的代码属于不同物理行。只需判断每个原始行是否含有效代码,而注释符号是否生效取决于当前词法状态。

解法:保留物理行的词法状态扫描

核心思路

[!blue]

block 表示是否处于块注释,必须跨行保留;每行的 code 标记和字面量引号 quote 重新初始化,因为普通字面量按约定不跨行。

扫描时先处理已有状态:在块注释中只识别 */;在字面量中只处理转义字符和同类闭合引号。只有普通状态才能开启 // 或 /*。这样引号内的注释标记、注释中的引号都不会被误解释。

普通状态遇到非空白字符即标记本行有代码,开引号本身也属于代码。遇到行注释直接结束本行,块注释结束后继续检查同行余下内容;行末最多计数一次,不合并不同物理行。

解题步骤

  1. 块注释状态跨行保存,每行重新记录是否出现代码。
  2. 在字面量中只处理转义及闭合引号,普通状态再识别注释起止。
  3. 本行有代码才增加计数,保持原物理行边界不拼接。

代码实现

class Solution {
    public int effectiveLines(String[] source) {
        boolean block = false;
        int count = 0;

        for (String line : source) {
            boolean code = false;
            char quote = 0;

            for (int i = 0; i < line.length(); ) {
                char c = line.charAt(i);

                if (block) {
                    if (c == '*' && i + 1 < line.length() && line.charAt(i + 1) == '/') {
                        block = false;
                        i += 2;
                    } else {
                        i++;
                    }
                } else if (quote != 0) {
                    if (c == '\\') {
                        i += 2;
                    } else {
                        if (c == quote) {
                            quote = 0;
                        }

                        i++;
                    }
                } else if (c == '/' && i + 1 < line.length() && line.charAt(i + 1) == '/') {
                    break;
                } else if (c == '/' && i + 1 < line.length() && line.charAt(i + 1) == '*') {
                    block = true;
                    i += 2;
                } else {
                    if (!Character.isWhitespace(c)) {
                        code = true;
                    }

                    if (c == '"' || c == 39) {
                        quote = c;
                    }

                    i++;
                }
            }

            if (code) {
                count++;
            }
        }

        return count;
    }
}
func effectiveLines(source []string) int {
    block := false
    count := 0
    for _, line := range source {
        code := false
        quote := byte(0)
        for i := 0; i < len(line); {
            c := line[i]
            if block {
                if c == '*' && i+1 < len(line) && line[i+1] == '/' {
                    block = false
                    i += 2
                } else {
                    i++
                }
            } else if quote != 0 {
                if c == '\\' {
                    i += 2
                } else {
                    if c == quote {
                        quote = 0
                    }
                    i++
                }
            } else if c == '/' && i+1 < len(line) && line[i+1] == '/' {
                break
            } else if c == '/' && i+1 < len(line) && line[i+1] == '*' {
                block = true
                i += 2
            } else {
                if c != ' ' && c != '\t' && c != '\r' && c != '\n' && c != '\v' && c != '\f' {
                    code = true
                }
                if c == '"' || c == 39 {
                    quote = c
                }
                i++
            }
        }
        if code {
            count++
        }
    }
    return count
}

复杂度分析

  • 时间复杂度:$O(C)$。
  • 空间复杂度:额外空间 $O(1)$,C 为输入源码字符数。

关键点总结

[!green]

不同词法状态的优先级决定符号含义;引号中的 // 不是注释,块注释中的引号也不是字面量入口。

易错点总结

[!yellow]

不能先调用删除注释再统计输出行数,跨行块注释会改变逻辑行数;引号内的 // 和 /* 都不应开启注释。

相似题目

题目 难度 关联与区别
722. 删除注释 中等 原题移除注释并拼接有效内容,本题必须保留物理行边界,且要识别字面量中的注释符号。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/1588080017
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!