刷题日记 - 4
最近更新:2025-10-20   |   字数总计:2.8k   |   阅读估时:12分钟   |   阅读量:
  1. 刷题日记 - 4
    1. 3128. 直角三角形 - 力扣(LeetCode)
    2. 28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)
    3. 68. 文本左右对齐 - 力扣(LeetCode)
    4. 125. 验证回文串 - 力扣(LeetCode)
    5. 392. 判断子序列 - 力扣(LeetCode)
    6. 792. 匹配子序列的单词数 - 力扣(LeetCode)

刷题日记 - 4

记录刷题。

3128. 直角三角形 - 力扣(LeetCode)

难度:中等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Solution {
public long numberOfRightTriangles(int[][] grid) {
int m = grid.length; // 获取网格的行数
int n = grid[0].length; // 获取网格的列数

int[] rowPoints = new int[m]; // 用于记录每一行中的点数
int[] colPoints = new int[n]; // 用于记录每一列中的点数

// 统计每一行和每一列中的点数
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) { // 如果当前格子有一个点
rowPoints[i]++; // 增加对应行的点数
colPoints[j]++; // 增加对应列的点数
}
}
}

long res = 0; // 用于存储直角三角形的数量

// 计算直角三角形的数量
for (int i = 0; i < m; i++) {
if (rowPoints[i] < 2) continue; // 如果当前行的点数少于2,则跳过
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) { // 如果当前格子有一个点
// 如果当前位置有一个点,计算以该点为直角的直角三角形数量
res += (colPoints[j] - 1) * (rowPoints[i] - 1);
// 列中的点数减去1,表示该列除去当前点的其他点
// 行中的点数减去1,表示该行除去当前点的其他点
}
}
}

return res; // 返回计算得到的直角三角形数量
}
}

如果某个位置有一个点(即 grid[i][j] == 1),则以该点为直角计算直角三角形的数量。

28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)

难度:简单

KMP算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class Solution {
public int strStr(String haystack, String needle) {
return kmp(haystack,needle);
}

private int[] computeLSPArray(String pattern){
int len = pattern.length();
char[] charArray = pattern.toCharArray();
int[] lsp = new int[len];
lsp[0] = 0;

int i = 1;
int j = 0;

while(i<len){
if(charArray[i]==charArray[j]){
j++;
lsp[i] = j;
i++;
}else{
if(j!=0){
j = lsp[j-1];
}else{
i++;
}
}
}
return lsp;
}

private int kmp(String text,String pattern){

int N = text.length();
int M = pattern.length();

char[] textChars = text.toCharArray();
char[] patternChars = pattern.toCharArray();

int[] lsp = computeLSPArray(pattern);

int i = 0;
int j = 0;

while(i<N){
if(textChars[i]==patternChars[j]){
i++;
j++;
}

if(j==M){
int idx = i-j;
return idx;
// j = lsp[j-1];
}else if(i<N && textChars[i]!=patternChars[j]){
if(j==0){
i++;
}else{
j = lsp[j-1];
}
}
}
return -1;
}
}

主要是KMP,因为已经单独列出一个博客了,所以这里就没加注释。

68. 文本左右对齐 - 力扣(LeetCode)

难度:困难

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
class Solution {
public List<String> fullJustify(String[] words, int maxWidth) {
List<String> res = new ArrayList<>(); // 存放结果字符串的列表
LinkedList<String> tmp = new LinkedList<>(); // 存放当前行的单词
int tmpLen = 0; // 当前行单词长度之和
int SPACE = 1; // 空格长度
int len = words.length; // 单词数组长度
int p = 0; // 当前单词指针

while(p < len) {
String word = words[p];
if(tmpLen == 0) { // 如果当前行没有单词
tmpLen += word.length(); // 更新当前行长度
tmp.offer(word); // 加入当前单词
} else if(tmpLen + word.length() + SPACE <= maxWidth) { // 如果加入当前单词后长度不超过最大宽度
tmpLen = tmpLen + word.length() + SPACE; // 更新当前行长度
tmp.offer(word); // 加入当前单词
} else { // 如果加入当前单词后长度会超过最大宽度,表明现在需要将tmp中的所有单词作为一行了
String tmpString = fun(tmp, tmpLen, maxWidth); // 对齐当前行单词
res.add(tmpString); // 将对齐后的字符串加入结果列表
tmpLen = 0; // 重置当前行长度
continue; // 继续处理当前单词
}
p++; // 移动到下一个单词
}

// 特殊处理最后一行 (最后一行左对齐)
StringBuilder sb = new StringBuilder();
sb.append(tmp.poll());
while(!tmp.isEmpty()){
sb.append(' ');
sb.append(tmp.poll());
}
if(sb.length()<maxWidth){
for(int i=sb.length();i<maxWidth;i++){
sb.append(' ');
}
}
res.add(sb.toString());

return res;
}

// 将当前行的单词组合输出成两点对齐的字符串
private String fun(LinkedList<String> tmp, int tmpLen, int maxWidth) {

int wordCount = tmp.size(); // 当前行单词数
int spaceCount = maxWidth - tmpLen + wordCount - 1; // 需要填充的空格数
StringBuilder sb = new StringBuilder();
int p = 0;
int[] spaces = new int[wordCount - 1]; // 每个间隔应填充的空格数

if(wordCount > 1) {
int base = spaceCount / (wordCount - 1); // 平均每个间隔的空格数
for(int i = 0; i < wordCount - 1; i++) {
spaces[i] = base;
}
int remain = spaceCount % (wordCount - 1); // 处理空格分配不均匀的情况
int idx = 0;
while(remain > 0) {
spaces[idx]++;
remain--;
idx++;
}
}

while(p < wordCount) {
String word = tmp.poll();
if(p == wordCount - 1) {
sb.append(word); // 最后一个单词之后没有间隔
} else {
sb.append(word); // 添加当前单词
for(int i = 0; i < spaces[p]; i++) { // 添加当前单词后的间隔
sb.append(' ');
}
}
p++;
}

if(sb.length() < maxWidth) {
for(int i = sb.length(); i < maxWidth; i++) {
sb.append(' ');
}
}
return sb.toString();
}
}

主要难点在于处理文本对齐过程中涉及的多个细节:

  1. 控制行宽度

需要准确控制每行的宽度不超过给定的 maxWidth。这包括计算当前行的单词长度和空格长度,以及判断何时需要换行。

  1. 处理不同的对齐方式

对于每行的对齐方式,需要考虑以下几种情况:

  • 普通行:除了最后一行外的所有行需要两端对齐,空格要尽可能均匀分布。
  • 最后一行:最后一行只需左对齐,且单词之间只需一个空格,剩余空格全部放在行尾。
  1. 分配空格

在两端对齐时,必须均匀分配空格,并处理空格无法均匀分配的情况:

  • 均匀分配:当空格数能够均匀分配时,每个间隔的空格数相同。
  • 不均匀分配:当空格数不能均匀分配时,需优先在前面的间隔中多加一个空格。

125. 验证回文串 - 力扣(LeetCode)

难度:简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public boolean isPalindrome(String s) {
char[] charArray = s.toCharArray(); // 将字符串转换为字符数组

// 提取出所有的字母数字字符
StringBuilder sb = new StringBuilder();
for (char ch : charArray) {
if ('A' <= ch && ch <= 'Z') {
sb.append((char) (ch - 'A' + 'a')); // 将大写字母转换为小写字母
} else if (('a' <= ch && ch <= 'z') || ('0' <= ch && ch <= '9')) {
sb.append(ch); // 保留小写字母和数字
}
}

char[] newCharArray = sb.toString().toCharArray(); // 将过滤后的字符串转换为字符数组
// 判断是否为回文
int head = 0; // 头指针
int tail = sb.length() - 1; // 尾指针
boolean flag = true; // 标志位,用于判断是否为回文

while (head < tail) { // 当头指针小于尾指针时
char headChar = newCharArray[head]; // 取头指针指向的字符
char tailChar = newCharArray[tail]; // 取尾指针指向的字符
if (headChar != tailChar) { // 如果两个字符不相等
flag = false; // 标志位设为false,表示不是回文
break; // 退出循环
}
head++; // 头指针向右移动
tail--; // 尾指针向左移动
}

return flag; // 返回标志位
}
}

这个题比较简单,只需要注意下面两点:

  • 字符处理和过滤
    • 有效字符提取
    • 将大写字母转换成小写字母
  • 双指针判断回文

392. 判断子序列 - 力扣(LeetCode)

难度:简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
public boolean isSubsequence(String s, String t) {
boolean res = false; // 结果标志,初始化为false

int sLen = s.length(); // 字符串s的长度
int tLen = t.length(); // 字符串t的长度

if (sLen < 1) { // 如果字符串s为空
return true; // 空字符串总是其他字符串的子序列
}

char[] sChars = s.toCharArray(); // 将字符串s转换为字符数组
char[] tChars = t.toCharArray(); // 将字符串t转换为字符数组

int q = 0; // 指向字符串s的指针
int p = 0; // 指向字符串t的指针

char sCur = sChars[q]; // 当前s中的字符
char tCur;
while (p < tLen) { // 遍历字符串t
tCur = tChars[p]; // 当前t中的字符
if (tCur == sCur) { // 如果t中的字符和s中的字符相同
q++; // 移动s的指针
if (q == sLen) { // 如果s的指针到达s的末尾
res = true; // 表示s是t的子序列
break; // 退出循环
}
sCur = sChars[q]; // 更新当前s中的字符
}
p++; // 移动t的指针
}

return res; // 返回结果
}
}

当给出的输入只有一个s的时候,还是挺简单的,只需要注意一下几个方面:

  • 双指针同步遍历
  • 特殊情况处理(s为空的时候)

792. 匹配子序列的单词数 - 力扣(LeetCode)

难度:中等

上一题的进阶,但是现在给出的不仅是一个字符串,给出的是字符串数组,求满足文本串子序列的字符串的个数。

遍历字符串数组中的所有字符串使用上一题的方法进行判断是否是子序列,这样的做法会超时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Solution {
public int numMatchingSubseq(String s, String[] words) {
// 预处理字符串s
char[] sChars = s.toCharArray();
int len = s.length();
int[][] dp = new int[len+1][26]; // 动态规划数组,用于记录每个字符下一个出现的位置
// dp[i][ch-'a'] = j 表明,在字符串s中 索引为i到len-1的子字符串中,字符ch出现的下一个位置

// 初始化dp数组
for (char ch = 'a'; ch <= 'z'; ch++) {
int nextPos = -1;
for (int i = len - 1; i >= 0; i--) {
if (sChars[i] == ch) {
nextPos = i; // 更新当前字符的位置
}
dp[i][ch - 'a'] = nextPos; // 记录字符ch在位置i的下一个出现位置
}
}

// 填充字符串末尾的情况
Arrays.fill(dp[len], -1);

int res = 0; // 记录匹配的子序列数量
boolean flag; // 标志位,记录是否找到匹配的子序列

// 遍历每个单词,检查是否是s的子序列
for (String word : words) {
flag = true;
int idx = -1; // 初始化索引为-1,用于dp数组的查找
for (char ch : word.toCharArray()) {
idx = dp[idx + 1][ch - 'a']; // 查找字符ch在s中的下一个出现位置
if (idx == -1) { // 如果找不到字符ch
flag = false; // 标记为false
break; // 结束当前单词的检查
}
}
if (flag) res++; // 如果找到匹配的子序列,结果加1
}

return res; // 返回匹配的子序列数量
}
}

用朴素的思路为什么会超时:

朴素的判定过程需要使用双指针扫描两个字符串,其中对于原串的扫描,会有大量的字符会被跳过(无效匹配),即只有两指针对应的字符相同时,匹配串指针才会后移。

如果给出的短串数量很多,就会出现很多次无效匹配。

预处理原串之后有什么好处:

对于要匹配的短字符串,遍历每一个字符,不断地寻找该字符在长字符串中的位置,然后将位置更新,寻找下一个字符,相当于在长字符串上“跳跃”。

如果下一个位置为 -1,表示长字符串再没有该字符了,表明短字符串不是长字符串的子序列;如果能正常遍历完毕,则表明是子序列。

这样只需要预处理一次原串。