Skip to content

Commit 540c627

Browse files
committed
add notes
1 parent c19a90b commit 540c627

12 files changed

Lines changed: 829 additions & 0 deletions

Algorithm/24.文本左右对齐.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,86 @@
7171

7272
### 思路
7373

74+
字符串大模拟,分情况讨论即可:
75+
76+
- 如果当前行只有一个单词,特殊处理为左对齐;
77+
- 如果当前行为最后一行,特殊处理为左对齐;
78+
- 其余为一般情况,分别计算「当前行单词总长度」、「当前行空格总长度」和「往下取整后的单位空格长度」,然后依次进行拼接。当空格无法均分时,每次往靠左的间隙多添加一个空格,直到剩余的空格能够被后面的间隙所均分。
79+
80+
```java
81+
82+
class Solution {
83+
public List<String> fullJustify(String[] words, int maxWidth) {
84+
List<String> ans = new ArrayList<>();
85+
int n = words.length;
86+
List<String> list = new ArrayList<>();
87+
for (int i = 0; i < n; ) {
88+
// list 装载当前行的所有 word
89+
list.clear();
90+
list.add(words[i]);
91+
int cur = words[i++].length();
92+
while (i < n && cur + 1 + words[i].length() <= maxWidth) {
93+
cur += 1 + words[i].length();
94+
list.add(words[i++]);
95+
}
96+
97+
// 当前行为最后一行,特殊处理为左对齐
98+
if (i == n) {
99+
StringBuilder sb = new StringBuilder(list.get(0));
100+
for (int k = 1; k < list.size(); k++) {
101+
sb.append(" ").append(list.get(k));
102+
}
103+
while (sb.length() < maxWidth) sb.append(" ");
104+
ans.add(sb.toString());
105+
break;
106+
}
107+
108+
// 如果当前行只有一个 word,特殊处理为左对齐
109+
int cnt = list.size();
110+
if (cnt == 1) {
111+
String str = list.get(0);
112+
while (str.length() != maxWidth) str += " ";
113+
ans.add(str);
114+
continue;
115+
}
116+
117+
/**
118+
* 其余为一般情况
119+
* wordWidth : 当前行单词总长度;
120+
* spaceWidth : 当前行空格总长度;
121+
* spaceItem : 往下取整后的单位空格长度
122+
*/
123+
int wordWidth = cur - (cnt - 1);
124+
int spaceWidth = maxWidth - wordWidth;
125+
int spaceItemWidth = spaceWidth / (cnt - 1);
126+
String spaceItem = "";
127+
for (int k = 0; k < spaceItemWidth; k++) spaceItem += " ";
128+
StringBuilder sb = new StringBuilder();
129+
for (int k = 0, sum = 0; k < cnt; k++) {
130+
String item = list.get(k);
131+
sb.append(item);
132+
if (k == cnt - 1) break;
133+
sb.append(spaceItem);
134+
sum += spaceItemWidth;
135+
// 剩余的间隙数量(可填入空格的次数)
136+
int remain = cnt - k - 1 - 1;
137+
// 剩余间隙数量 * 最小单位空格长度 + 当前空格长度 < 单词总长度,则在当前间隙多补充一个空格
138+
if (remain * spaceItemWidth + sum < spaceWidth) {
139+
sb.append(" ");
140+
sum++;
141+
}
142+
}
143+
ans.add(sb.toString());
144+
}
145+
return ans;
146+
}
147+
}
148+
```
149+
150+
复杂度分析:
151+
152+
- 时间复杂度:会对 words 做线性扫描,最坏情况下每个 words[i] 独占一行,此时所有字符串的长度为 n∗maxWidth。复杂度为 O(n∗maxWidth)
153+
- 空间复杂度:最坏情况下每个 words[i] 独占一行,复杂度为 O(n∗maxWidth)
74154

75155

76156
---

Algorithm/25.验证回文串.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,63 @@
3939

4040
### 思路
4141

42+
##### 方法一
4243

44+
最简单的方法是对字符串 s 进行一次遍历,并将其中的字母和数字字符进行保留,放在另一个字符串 sgood 中。这样我们只需要判断 sgood 是否是一个普通的回文串即可。
45+
46+
判断的方法有两种:
47+
48+
- 第一种是使用语言中的字符串翻转 API 得到 sgood 的逆序字符串 sgood_rev,只要这两个字符串相同,那么 sgood 就是回文串。
49+
- 第二种是使用双指针。初始时,左右指针分别指向 sgood 的两侧,随后我们不断地将这两个指针相向移动,每次移动一步,并判断这两个指针指向的字符是否相同。当这两个指针相遇时,就说明 sgood 时回文串。
50+
51+
```java
52+
class Solution {
53+
public boolean isPalindrome(String s) {
54+
StringBuffer sgood = new StringBuffer();
55+
int length = s.length();
56+
for (int i = 0; i < length; i++) {
57+
char ch = s.charAt(i);
58+
if (Character.isLetterOrDigit(ch)) {
59+
sgood.append(Character.toLowerCase(ch));
60+
}
61+
}
62+
int n = sgood.length();
63+
int left = 0, right = n - 1;
64+
while (left < right) {
65+
if (Character.toLowerCase(sgood.charAt(left)) != Character.toLowerCase(sgood.charAt(right))) {
66+
return false;
67+
}
68+
++left;
69+
--right;
70+
}
71+
return true;
72+
}
73+
}
74+
```
75+
76+
复杂度分析:
77+
78+
- 时间复杂度:O(n),其中n是字符串s的长度。
79+
80+
- 空间复杂度:O(n)。由于我们需要将所有的字母和数字字符存放在另一个字符串中,在最坏情况下,新的字符串sgood与原字符串s完全相同,因此需要使用 O(n) 的空间。
81+
82+
83+
##### 方法二:在原字符串上直接判断
84+
85+
我们可以对方法一中第二种判断回文串的方法进行优化,就可以得到只使用 O(1) 空间的算法。
86+
87+
我们直接在原字符串 s 上使用双指针。在移动任意一个指针时,需要不断地向另一指针的方向移动,直到遇到一个字母或数字字符,或者两指针重合为止。
88+
89+
也就是说,我们每次将指针移到下一个字母字符或数字字符,再判断这两个指针指向的字符是否相同。
90+
91+
92+
93+
94+
复杂度分析:
95+
96+
- 时间复杂度:O(n),其中n是字符串s的长度。
97+
98+
- 空间复杂度:O(1)。
4399

44100
---
45101
- 邮箱 :charon.chui@gmail.com

Algorithm/26.判断子序列.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
26.判断子序列
2+
===
3+
4+
5+
### 题目
6+
7+
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
8+
9+
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
10+
11+
进阶:
12+
13+
如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
14+
15+
16+
示例 1:
17+
18+
- 输入:s = "abc", t = "ahbgdc"
19+
- 输出:true
20+
21+
示例 2:
22+
23+
- 输入:s = "axc", t = "ahbgdc"
24+
- 输出:false
25+
26+
27+
提示:
28+
29+
- 0 <= s.length <= 100
30+
- 0 <= t.length <= 10^4
31+
- 两个字符串都只由小写字符组成。
32+
33+
### 思路
34+
35+
36+
首先,如果 s 是空串,直接返回 true,因为空串是任何字符串的子序列。
37+
设置双指针 i , j 分别指向字符串 s , t 的首个字符,遍历字符串 t:
38+
39+
- 当 s[i] == t[j] 时,代表匹配成功,此时同时 i++ , j++ ;
40+
- 进而,若 i 已走过 s 尾部,代表 s 是 t 的子序列,此时应提前返回 true ;
41+
- 当 s[i] != t[j] 时,代表匹配失败,此时仅 j++ ;
42+
43+
若遍历完字符串 t 后,字符串 s 仍未遍历完,代表 s 不是 t 的子序列,此时返回 false 。
44+
45+
```java
46+
47+
class Solution {
48+
public boolean isSubsequence(String s, String t) {
49+
int i = 0, j = 0;
50+
while (i < s.length() && j < t.length()) {
51+
if (s.charAt(i) == t.charAt(j)) {
52+
i++;
53+
}
54+
j++;
55+
}
56+
return i == s.length();
57+
}
58+
}
59+
```
60+
61+
复杂度分析:
62+
63+
- 时间复杂度 O(N) : 其中 N 为字符串 t 的长度。最差情况下需完整遍历 t 。
64+
- 空间复杂度 O(1) : i , j 变量使用常数大小空间。
65+
66+
67+
##### 进阶问题解法
68+
69+
如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
70+
71+
72+
这种类似对同一个长字符串做很多次匹配的 ,可以像 KMP 算法一样,先用一些时间将长字符串中的数据 提取出来,磨刀不误砍柴功。有了提取好的数据,就可以快速的进行匹配。
73+
74+
因为S非常多,所以可以通过一次性对T进行简化处理,这样来减少后续每一次S匹配T的遍历时间:
75+
76+
- 这里需要的数据就是匹配到某一点时,待匹配的字符在长字符串中 下一次 出现的位置。
77+
78+
- 所以我们前期多做一点工作,将长字符串研究透彻,假如长字符串的长度为 n,建立一个 n∗26 大小的矩阵,表示每个位置上26个字符下一次出现的位置。实现如下:
79+
80+
- 对于要匹配的短字符串,遍历每一个字符,不断地寻找该字符在长字符串中的位置,然后将位置更新,寻找下一个字符,相当于在长字符串上“跳跃”。
81+
82+
- 如果下一个位置为 -1,表示长字符串再没有该字符了,返回 false 即可。
83+
84+
- 如果能正常遍历完毕,则表示可行,返回 true
85+
86+
- 需要注意的一点
87+
88+
- 对于 "abc" 在 "ahbgdc" 上匹配的时候,由于长字符串第一个 a 的下一个出现 a 的位置为 -1(不出现),会导致一个 bug。
89+
90+
- 所以在生成数组时在长字符串前插入一个空字符即可。
91+
92+
```java
93+
94+
//进阶问题的解决
95+
public boolean isSubsequence(String s, String t) {
96+
97+
//考虑到 对第一个字符的处理 ,在t 之前一个空字符
98+
t=' '+t;
99+
100+
//对t长字符串 做预处理
101+
int[][] dp = new int[t.length()][26];//存储每一个位置上 a--z的下一个字符出现的位置
102+
for (char c = 'a'; c <= 'z'; c++) {//依次对每个字符作处理
103+
int nextPos = -1;//表示接下来不会在出现该字符
104+
105+
for (int i = t.length() - 1; i >= 0; i--) {//从最后一位开始处理
106+
dp[i][c - 'a'] = nextPos;//dp[i][c-'a'] 加上外层循环 就是对每一个位置的a---z字符的处理了
107+
if (t.charAt(i) == c) {//表示当前位置有该字符 那么指向下一个该字符出现的位置就要被更新 为i
108+
nextPos = i;
109+
}
110+
}
111+
}
112+
113+
//数据的利用 ,开始匹配
114+
int index=0;
115+
for (char c:s.toCharArray()){
116+
index=dp[index][c-'a'];//因为加了' ',所以之后在处理第一个字符的时候 如果是在第一行,就会去第一行,不影响之后字符的判断
117+
if(index==-1){
118+
return false;
119+
}
120+
}
121+
return true;
122+
}
123+
```
124+
125+
---
126+
- 邮箱 :charon.chui@gmail.com
127+
- Good Luck!
128+
129+
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
27.两数之和 II - 输入有序数组
2+
===
3+
4+
5+
### 题目
6+
7+
给你一个下标从1开始的整数数组numbers,该数组已按非递减顺序排列,请你从数组中找出满足相加之和等于目标数target的两个数。
8+
9+
如果设这两个数分别是numbers[index1]和numbers[index2],则`1 <= index1 < index2 <= numbers.length`
10+
11+
以长度为`2`的整数数组`[index1, index2]`的形式返回这两个整数的下标`index1``index2`
12+
13+
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
14+
15+
你所设计的解决方案必须只使用常量级的额外空间。
16+
17+
18+
示例 1:
19+
20+
- 输入:numbers = [2,7,11,15], target = 9
21+
- 输出:[1,2]
22+
- 解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2]
23+
24+
示例 2:
25+
26+
- 输入:numbers = [2,3,4], target = 6
27+
- 输出:[1,3]
28+
- 解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3]
29+
30+
示例 3:
31+
32+
- 输入:numbers = [-1,0], target = -1
33+
- 输出:[1,2]
34+
- 解释:-1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2]
35+
36+
37+
提示:
38+
39+
- 2 <= numbers.length <= 3 * 104
40+
- -1000 <= numbers[i] <= 1000
41+
- numbers 按 非递减顺序 排列
42+
- -1000 <= target <= 1000
43+
- 仅存在一个有效答案
44+
45+
### 思路
46+
47+
注意题目说仅存在一个有效答案
48+
49+
50+
初始时两个指针分别指向第一个元素位置和最后一个元素的位置。每次计算两个指针指向的两个元素之和,并和目标值比较。如果两个元素之和等于目标值,则发现了唯一解。如果两个元素之和小于目标值,则将左侧指针右移一位。如果两个元素之和大于目标值,则将右侧指针左移一位。移动指针之后,重复上述操作,直到找到答案。
51+
52+
使用双指针的实质是缩小查找范围。那么会不会把可能的解过滤掉?答案是不会。
53+
54+
假设`numbers[i]+numbers[j]=target`是唯一解,其中`0≤i<j≤numbers.length−1`
55+
56+
初始时两个指针分别指向下标0和下标`numbers.length−1`,左指针指向的下标小于或等于i,右指针指向的下标大于或等于 j。 除非初始时左指针和右指针已经位于下标 i 和 j,否则一定是左指针先到达下标 i 的位置或者右指针先到达下标 j 的位置。
57+
58+
- 如果左指针先到达下标 i 的位置,此时右指针还在下标 j 的右侧,`sum>target`,因此一定是右指针左移,左指针不可能移到 i 的右侧。
59+
60+
- 如果右指针先到达下标 j 的位置,此时左指针还在下标 i 的左侧,`sum<target`,因此一定是左指针右移,右指针不可能移到 j 的左侧。
61+
62+
由此可见,在整个移动过程中,左指针不可能移到 i 的右侧,右指针不可能移到 j 的左侧,因此不会把可能的解过滤掉。
63+
64+
65+
由于题目确保有唯一的答案,因此使用双指针一定可以找到答案。
66+
67+
68+
```python
69+
class Solution:
70+
def twoSum(self, numbers: List[int], target: int) -> List[int]:
71+
left = 0
72+
right = len(numbers) - 1
73+
result = [-1]*2
74+
while left < right:
75+
sum = numbers[left] + numbers[right]
76+
if sum < target:
77+
left += 1
78+
elif sum > target:
79+
right -= 1
80+
else:
81+
return [left+1, right+1]
82+
83+
return [-1, -1]
84+
```
85+
86+
复杂度分析:
87+
88+
- 时间复杂度:O(n),其中 n 是数组的长度。两个指针移动的总次数最多为 n 次。
89+
90+
- 空间复杂度:O(1)。
91+
92+
93+
---
94+
- 邮箱 :charon.chui@gmail.com
95+
- Good Luck!
96+
97+

0 commit comments

Comments
 (0)