LeetCode 补充题 127. 有效代码物理行数统计
题目描述
:::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重新初始化,因为普通字面量按约定不跨行。扫描时先处理已有状态:在块注释中只识别
*/;在字面量中只处理转义字符和同类闭合引号。只有普通状态才能开启//或/*。这样引号内的注释标记、注释中的引号都不会被误解释。普通状态遇到非空白字符即标记本行有代码,开引号本身也属于代码。遇到行注释直接结束本行,块注释结束后继续检查同行余下内容;行末最多计数一次,不合并不同物理行。
解题步骤
- 块注释状态跨行保存,每行重新记录是否出现代码。
- 在字面量中只处理转义及闭合引号,普通状态再识别注释起止。
- 本行有代码才增加计数,保持原物理行边界不拼接。
代码实现
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. 删除注释 | 中等 | 原题移除注释并拼接有效内容,本题必须保留物理行边界,且要识别字面量中的注释符号。 |