0%

有限状态机思想

在刷Leetcode的时候发现又一部分字符串转化的问题可以使用有限状态机很好的解决,所以记录一下,方便下次遇到这种题型进行编写。

字符串转换整数(atoi)

题目描述

请你来实现一个atoi函数,使其能将字符串转换成整数。

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。接下来的转化规则如下:

  • 如果第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字字符组合起来,形成一个有符号整数。
  • 假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成一个整数。
  • 该字符串在有效的整数部分之后也可能会存在多余的字符,那么这些字符可以被忽略,它们对函数不应该造成影响。

注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换,即无法进行有效转换。

在任何情况下,若函数不能进行有效的转换时,请返回 0 。

提示:

  • 本题中的空白字符只包括空格字符 ‘ ‘ 。
  • 假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。

示例 1:

1
2
输入: "42"
输出: 42

示例 2:

1
2
3
4
输入: "   -42"
输出: -42
解释: 第一个非空白字符为 '-', 它是一个负号。
我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。

示例 3:

1
2
3
输入: "4193 with words"
输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。

示例 4:

1
2
3
4
输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。
因此无法执行有效的转换。

示例 5:

1
2
3
4
输入: "-91283472332"
输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。
因此返回 INT_MIN (−231) 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/string-to-integer-atoi
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

方法一:

在剑指offer上有这一题,上面提供的方法是使用类似于分类的方法、

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
class Solution {
public int myAtoi(String str) {
if (str==null || str.length()<=0) {
return 0;
}
long res = 0;
String s = str.trim();
if (s.length()>0) {
char[] chars = s.toCharArray();
int start = 0;
boolean isPositive =true;

if (chars[0]=='+') {
isPositive =true;
start = 1;
}else if (chars[0]=='-') {
isPositive =false;
start = 1;
}

for (int i = start; i < chars.length; i++) {

if (chars[i]>='0' && chars[i]<='9') {
res = res*10+chars[i]-'0';
if (res>Integer.MAX_VALUE) {
return isPositive?Integer.MAX_VALUE :Integer.MIN_VALUE;
}
}
else{
break;
}
}

if (!isPositive) {
res = 0-res;
}
}
return (int)res;
}
}

这种方法,比较好,理解起来也比较简单

方法二:

使用状态机进行编写。字符串处理的题目往往涉及复杂的流程以及条件情况,如果直接上手写程序,一不小心就会写出极其臃肿的代码。

因此,为了有条理地分析每个输入字符的处理方法,我们可以使用自动机这个概念:

我们的程序在每个时刻有一个状态 s,每次从序列中输入一个字符c,并根据字符 c 转移到下一个状态 s'。这样,我们只需要建立一个覆盖所有情况的从 sc 映射到 s' 的表格即可解决题目中的问题。

首先我们可以确定输入有:

  • 空格’ ‘
  • 符号:+-号
  • 数字:0,1,2,3,4,5,6,7,8,9
  • 其他

下面是官方题解给出的自动机状态图

我们也可以用下面的表格来表示这个自动机:

‘ ‘ +/- number other
start start signed in_number end
signed end end in_number end
in_number end end in_number end
end end end end end

接下来编程部分就非常简单了:我们只需要把上面这个状态转换表抄进代码即可。

另外自动机也需要记录当前已经输入的数字,只要在 s’ 为 in_number 时,更新我们输入的数字,即可最终得到输入的数字。

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
class Automaton {
//状态
string state = "start";
//对应的表格
unordered_map<string, vector<string>> table = {
{"start", {"start", "signed", "in_number", "end"}},
{"signed", {"end", "end", "in_number", "end"}},
{"in_number", {"end", "end", "in_number", "end"}},
{"end", {"end", "end", "end", "end"}}
};
//返回表格中的列
int get_col(char c) {
if (isspace(c)) return 0;
if (c == '+' or c == '-') return 1;
if (isdigit(c)) return 2;
return 3;
}
public:
int sign = 1;
long long ans = 0;

void get(char c) {
state = table[state][get_col(c)];
if (state == "in_number") {
ans = ans * 10 + c - '0';
ans = sign == 1 ? min(ans, (long long)INT_MAX) : min(ans, -(long long)INT_MIN);
}
else if (state == "signed")
sign = c == '+' ? 1 : -1;
}
};

class Solution {
public:
int myAtoi(string str) {
Automaton automaton;
for (char c : str)
automaton.get(c);
return automaton.sign * automaton.ans;
}
};

原地址:https://leetcode-cn.com/problems/string-to-integer-atoi/solution/zi-fu-chuan-zhuan-huan-zheng-shu-atoi-by-leetcode-/
可以有限状态机的解法非常巧妙,对于条件多、状态多的情况来说无疑是最好的解法。

有效数字

题目描述

验证给定的字符串是否可以解释为十进制数字。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"0" => true
" 0.1 " => true
"abc" => false
"1 a" => false
"2e10" => true
" -90e3 " => true
" 1e" => false
"e3" => false
" 6e-1" => true
" 99e2.5 " => false
"53.5e93" => true
" --6 " => false
"-+3" => false
"95a54e53" => false

说明: 我们有意将问题陈述地比较模糊。在实现代码之前,你应当事先思考所有可能的情况。这里给出一份可能存在于有效十进制数字中的字符列表:

  • 数字 0-9
  • 指数 - “e”
  • 正/负号 - “+”/“-“
  • 小数点 - “.”

当然,在输入中,这些字符的上下文也很重要。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-number
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

方法一:

使用状态机进行编写。根据题目描述我们知道输入有

  • 数字 0-9
  • 指数 - “e”
  • 正/负号 - “+”/“-“
  • 小数点 - “.”

然后可以画一下状态图(来自https://leetcode-cn.com/problems/valid-number/solution/biao-qu-dong-fa-by-user8973/):

接着跟上面一样画出表格

state blank +/- 0-9 . e other
0 0 1 6 2 -1 -1
1 -1 -1 6 2 -1 -1
2 -1 -1 3 -1 -1 -1
3 8 -1 3 -1 4 -1
4 -1 7 5 -1 -1 -1
5 8 -1 5 -1 -1 -1
6 8 -1 6 3 4 -1
7 -1 -1 5 -1 -1 -1
8 8 -1 -1 -1 -1 -1
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
class Solution {
public int make(char c) {
switch(c) {
case ' ': return 0;
case '+':
case '-': return 1;
case '.': return 3;
case 'e': return 4;
default://数字情况
if(c >= 48 && c <= 57) return 2;
}
return -1;
}

public boolean isNumber(String s) {
int state = 0;
//最好分类也比较巧妙
int finals = 0b101101000;
int[][] transfer = new int[][]{{ 0, 1, 6, 2,-1},
{-1,-1, 6, 2,-1},
{-1,-1, 3,-1,-1},
{ 8,-1, 3,-1, 4},
{-1, 7, 5,-1,-1},
{ 8,-1, 5,-1,-1},
{ 8,-1, 6, 3, 4},
{-1,-1, 5,-1,-1},
{ 8,-1,-1,-1,-1}};
char[] ss = s.toCharArray();
for(int i=0; i < ss.length; ++i) {
int id = make(ss[i]);
if (id < 0) return false;
state = transfer[state][id];
if (state < 0) return false;
}
return (finals & (1 << state)) > 0;
}
}

来自于:https://leetcode-cn.com/problems/valid-number/solution/biao-qu-dong-fa-by-user8973/

方法二:

责任链

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
//每个类都实现这个接口
interface NumberValidate {
boolean validate(String s);
}
//定义一个抽象类,用来检查一些基础的操作,是否为空,去掉首尾空格,去掉 +/-
//doValidate 交给子类自己去实现
abstract class NumberValidateTemplate implements NumberValidate{

public boolean validate(String s)
{
if (checkStringEmpty(s))
{
return false;
}

s = checkAndProcessHeader(s);

if (s.length() == 0)
{
return false;
}

return doValidate(s);
}

private boolean checkStringEmpty(String s)
{
if (s.equals(""))
{
return true;
}

return false;
}

private String checkAndProcessHeader(String value)
{
value = value.trim();

if (value.startsWith("+") || value.startsWith("-"))
{
value = value.substring(1);
}


return value;
}



protected abstract boolean doValidate(String s);
}

//实现 doValidate 判断是否是整数
class IntegerValidate extends NumberValidateTemplate{

protected boolean doValidate(String integer)
{
for (int i = 0; i < integer.length(); i++)
{
if(Character.isDigit(integer.charAt(i)) == false)
{
return false;
}
}

return true;
}
}

//实现 doValidate 判断是否是科学计数法
class SienceFormatValidate extends NumberValidateTemplate{

protected boolean doValidate(String s)
{
s = s.toLowerCase();
int pos = s.indexOf("e");
if (pos == -1)
{
return false;
}

if (s.length() == 1)
{
return false;
}

String first = s.substring(0, pos);
String second = s.substring(pos+1, s.length());

if (validatePartBeforeE(first) == false || validatePartAfterE(second) == false)
{
return false;
}


return true;
}

private boolean validatePartBeforeE(String first)
{
if (first.equals("") == true)
{
return false;
}

if (checkHeadAndEndForSpace(first) == false)
{
return false;
}

NumberValidate integerValidate = new IntegerValidate();
NumberValidate floatValidate = new FloatValidate();
if (integerValidate.validate(first) == false && floatValidate.validate(first) == false)
{
return false;
}

return true;
}

private boolean checkHeadAndEndForSpace(String part)
{

if (part.startsWith(" ") ||
part.endsWith(" "))
{
return false;
}

return true;
}

private boolean validatePartAfterE(String second)
{
if (second.equals("") == true)
{
return false;
}

if (checkHeadAndEndForSpace(second) == false)
{
return false;
}

NumberValidate integerValidate = new IntegerValidate();
if (integerValidate.validate(second) == false)
{
return false;
}

return true;
}
}
//实现 doValidate 判断是否是小数
class FloatValidate extends NumberValidateTemplate{

protected boolean doValidate(String floatVal)
{
int pos = floatVal.indexOf(".");
if (pos == -1)
{
return false;
}

if (floatVal.length() == 1)
{
return false;
}

NumberValidate nv = new IntegerValidate();
String first = floatVal.substring(0, pos);
String second = floatVal.substring(pos + 1, floatVal.length());

if (checkFirstPart(first) == true && checkFirstPart(second) == true)
{
return true;
}

return false;
}

private boolean checkFirstPart(String first)
{
if (first.equals("") == false && checkPart(first) == false)
{
return false;
}

return true;
}

private boolean checkPart(String part)
{
if (Character.isDigit(part.charAt(0)) == false ||
Character.isDigit(part.charAt(part.length() - 1)) == false)
{
return false;
}

NumberValidate nv = new IntegerValidate();
if (nv.validate(part) == false)
{
return false;
}

return true;
}
}
//定义一个执行者,我们把之前实现的各个类加到一个数组里,然后依次调用
class NumberValidator implements NumberValidate {

private ArrayList<NumberValidate> validators = new ArrayList<NumberValidate>();

public NumberValidator()
{
addValidators();
}

private void addValidators()
{
NumberValidate nv = new IntegerValidate();
validators.add(nv);

nv = new FloatValidate();
validators.add(nv);

nv = new HexValidate();
validators.add(nv);

nv = new SienceFormatValidate();
validators.add(nv);
}

@Override
public boolean validate(String s)
{
for (NumberValidate nv : validators)
{
if (nv.validate(s) == true)
{
return true;
}
}

return false;
}


}
public boolean isNumber(String s) {
NumberValidate nv = new NumberValidator();
return nv.validate(s);
}

利用责任链的设计模式,会使得写出的算法扩展性以及维护性更高。这里用到的思想就是,每个类只判断一种类型。比如判断是否是正数的类,判断是否是小数的类,判断是否是科学计数法的类,这样每个类只关心自己的部分,出了问题很好排查,而且互不影响。
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

具体可以参考链接:https://leetcode-cn.com/problems/valid-number/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-1-4/

225.用队列实现栈

题目描述

使用队列实现栈的下列操作:

  • push(x) — 元素 x 入栈
  • pop() — 移除栈顶元素
  • top() — 获取栈顶元素
  • empty() — 返回栈是否为空

注意:

  • 你只能使用队列的基本操作— 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。
  • 你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
  • 你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/implement-stack-using-queues
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

两个队列, 压入 - O(n)O(n), 弹出 - O(1)O(1)

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
class MyStack {
private Queue<Integer> q1 = new LinkedList<>();
private Queue<Integer> q2 = new LinkedList<>();
private int top;
/** Initialize your data structure here. */
public MyStack() {

}

/** Push element x onto stack. */
public void push(int x) {
q2.add(x);
top = x;
while (!q1.isEmpty()) {
q2.add(q1.remove());
}
Queue<Integer> temp = q1;
q1 = q2;
q2 = temp;
}

/** Removes the element on top of the stack and returns that element. */
public int pop() {
q1.remove();
int res = top;
if (!q1.isEmpty()) {
top = q1.peek();
}
return res;
}

/** Get the top element. */
public int top() {
return top;
}

/** Returns whether the stack is empty. */
public boolean empty() {
return q1.isEmpty();
}
}

/**
* Your MyStack object will be instantiated and called as such:
* MyStack obj = new MyStack();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.top();
* boolean param_4 = obj.empty();
*/

一个队列, 压入 - O(n)O(n), 弹出 - O(1)O(1)

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
class MyStack {
private LinkedList<Integer> q1 = new LinkedList<>();

// Push element x onto stack.
public void push(int x) {
q1.add(x);
int sz = q1.size();
while (sz > 1) {
q1.add(q1.remove());
sz--;
}
}

// Removes the element on top of the stack.
public int pop() {
return q1.remove();
}

// Return whether the stack is empty.
public boolean empty() {
return q1.isEmpty();
}

// Get the top element.
public int top() {
return q1.peek();
}


}

232.用栈实现队列

题目描述

使用栈实现队列的下列操作:

  • push(x) — 将一个元素放入队列的尾部。

  • pop() — 从队列首部移除元素。

  • peek() — 返回队列首部的元素。

  • empty() — 返回队列是否为空。

示例:

1
2
3
4
5
6
7
MyQueue queue = new MyQueue();

queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false

说明:

  • 你只能使用标准的栈操作 — 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的
  • 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
  • 假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/implement-queue-using-stacks
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
class MyQueue {

Stack<Integer> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
int top;

/** Initialize your data structure here. */
public MyQueue() {

}

/** Push element x to the back of queue. */
public void push(int x) {
if (stack1.empty())
top = x;
while (!stack1.isEmpty())
stack2.push(stack1.pop());
stack2.push(x);
while (!stack2.isEmpty())
stack1.push(stack2.pop());

}

/** Removes the element from in front of queue and returns that element. */
public int pop() {
int res = stack1.pop();
if (!stack1.empty())
top = stack1.peek();
return res;
}

/** Get the front element. */
public int peek() {
return top;
}

/** Returns whether the queue is empty. */
public boolean empty() {
return stack1.isEmpty();
}
}

/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/

比较好理解的版本

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
import java.util.Stack;

public class MyQueue {

private Stack<Integer> stackPush;
private Stack<Integer> stackPop;

/**
* Initialize your data structure here.
*/
public MyQueue() {
stackPush = new Stack<>();
stackPop = new Stack<>();
}

/**
* Push element x to the back of queue.
*/
public void push(int x) {
stackPush.push(x);
}

/**
* 辅助方法:一次性将 stackPush 里的所有元素倒入 stackPop
* 注意:1、该操作只在 stackPop 里为空的时候才操作,否则会破坏出队入队的顺序
* 2、在 peek 和 pop 操作之前调用该方法
*/
private void shift() {
if (stackPop.isEmpty()) {
while (!stackPush.isEmpty()) {
stackPop.push(stackPush.pop());
}
}
}

/**
* Removes the element from in front of queue and returns that element.
*/
public int pop() {
shift();
if (!stackPop.isEmpty()) {
return stackPop.pop();
}
throw new RuntimeException("队列里没有元素");
}

/**
* Get the front element.
*/
public int peek() {
shift();
if (!stackPop.isEmpty()) {
return stackPop.peek();
}
throw new RuntimeException("队列里没有元素");
}

/**
* Returns whether the queue is empty.
*/
public boolean empty() {
return stackPush.isEmpty() && stackPop.isEmpty();
}
}

/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/

树问题总结

总结Leetcode上刷的树问题

二叉树的中序遍历

题目描述

给定一个二叉树,返回它的中序 遍历。

示例:

1
2
3
4
5
6
7
8
输入: [1,null,2,3]
1
\
2
/
3

输出: [1,3,2]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binary-tree-inorder-traversal
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode p = root;
while (p!=null|| !stack.isEmpty()) {
while (p!=null) {
stack.push(p);
p=p.left;
}

if (!stack.isEmpty()) {
p = stack.pop();
res.add(p.val);
p=p.right;
}
}
return res;
}
}

不同的二叉搜索树

题目描述

给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?

示例:

1
2
3
4
5
6
7
8
9
10
输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/unique-binary-search-trees
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

动态规划思想

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public int numTrees(int n) {
int[] G = new int[n + 1];
G[0] = 1;
G[1] = 1;
//第一层循环表示长度
for (int i = 2; i <= n; ++i) {
//第二层循环表示 根从那个开始
for (int j = 1; j <= i; ++j) {
G[i] += G[j - 1] * G[i - j];
}
}
return G[n];
}
}

不同的二叉搜索树II

题目描述

给定一个整数 n,生成所有由 1 … n 为节点所组成的 二叉搜索树

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
输入:3
输出:
[
[1,null,3,2],
[3,2,null,1],
[3,1,null,null,2],
[2,1,3],
[1,null,2,null,3]
]
解释:
以上的输出对应以下 5 种不同结构的二叉搜索树:

1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/unique-binary-search-trees-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<TreeNode> generateTrees(int n) {
if (n == 0) {
return new LinkedList<TreeNode>();
}
return generate_trees(1, n);
}

public LinkedList<TreeNode> generate_trees(int start, int end) {
LinkedList<TreeNode> all_trees = new LinkedList<TreeNode>();
if (start > end) {
all_trees.add(null);
return all_trees;
}

// 选择一个作为根
for (int i = start; i <= end; i++) {
//可能的左子树
LinkedList<TreeNode> left_trees = generate_trees(start, i - 1);

// 可能的右子树
LinkedList<TreeNode> right_trees = generate_trees(i + 1, end);

// 两者联系起来
for (TreeNode l : left_trees) {
for (TreeNode r : right_trees) {
TreeNode current_tree = new TreeNode(i);
current_tree.left = l;
current_tree.right = r;
all_trees.add(current_tree);
}
}
}
return all_trees;
}

}

验证二叉搜索树

题目描述

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含大于当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

1
2
3
4
5
输入:
2
/ \
1 3
输出: true

示例 2:

1
2
3
4
5
6
7
8
9
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/validate-binary-search-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
//这个pre非常巧妙
long pre = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if (root==null) {
return true;
}

if(!isValidBST(root.left)){
return false;
};

if (root.val <= pre) {
return false;
}
pre = root.val;

return isValidBST(root.right);
}
}

相同的树

题目描述

给定两个二叉树,编写一个函数来检验它们是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

示例 1:

1
2
3
4
5
6
7
输入:       1         1
/ \ / \
2 3 2 3

[1,2,3], [1,2,3]

输出: true

示例 2:

1
2
3
4
5
6
7
输入:      1          1
/ \
2 2

[1,2], [1,null,2]

输出: false

示例 3:

1
2
3
4
5
6
7
输入:       1         1
/ \ / \
2 1 1 2

[1,2,1], [1,1,2]

输出: false

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/same-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p==null && q==null) return true;
if(p==null || q==null)return false;

return p.val==q.val && isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
}
}

对称二叉树

题目描述

给定一个二叉树,检查它是否是镜像对称的。

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

1
2
3
4
5
	1
/ \
2 2
/ \ / \
3 4 4 3

但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:

1
2
3
4
5
1
/ \
2 2
\ \
3 3

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/symmetric-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root==null) {
return true;
}
return IsSymmetric(root.left,root.right);
}

boolean IsSymmetric(TreeNode pRoot1,TreeNode pRoot2){
if (pRoot1==null&&pRoot2==null) {
return true;
}

if (pRoot1==null||pRoot2==null) {
return false;
}

return pRoot1.val==pRoot2.val && IsSymmetric(pRoot1.left,pRoot2.right) && IsSymmetric(pRoot1.right,pRoot2.left) ;
}
}

二叉树的层次遍历

题目描述

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。

示例:
二叉树:[3,9,20,null,null,15,7],

1
2
3
4
5
3
/ \
9 20
/ \
15 7

返回其层次遍历结果:

1
2
3
4
5
[
[3],
[9,20],
[15,7]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binary-tree-level-order-traversal
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root==null) {
return res;
}
LinkedList<TreeNode> queue = new LinkedList<>();
queue.add(root);
int nextLevel = 0;
int toBePrinted = 1;
ArrayList<Integer> temp = new ArrayList<>();

while (!queue.isEmpty()) {
TreeNode p =queue.pop();
temp.add(p.val);
toBePrinted--;

if (p.left!=null) {
queue.add(p.left);
nextLevel++;
}

if (p.right!=null) {
queue.add(p.right);
nextLevel++;
}
//说明这一层打印完毕
if (toBePrinted==0) {
res.add(temp);
temp =new ArrayList<>();
toBePrinted = nextLevel;
nextLevel = 0;
}
}
return res;
}
}

二叉树的锯齿形层次遍历

题目描述

给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

例如:
给定二叉树 [3,9,20,null,null,15,7],

1
2
3
4
5
3
/ \
9 20
/ \
15 7

返回锯齿形层次遍历如下:

1
2
3
4
5
[
[3],
[20,9],
[15,7]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

使用双端队列

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
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
if (root == null) {
return new ArrayList<List<Integer>>();
}

List<List<Integer>> results = new ArrayList<List<Integer>>();

// add the root element with a delimiter to kick off the BFS loop
LinkedList<TreeNode> node_queue = new LinkedList<TreeNode>();
node_queue.addLast(root);
//把null作为结束
node_queue.addLast(null);

LinkedList<Integer> level_list = new LinkedList<Integer>();
boolean is_order_left = true;

while (node_queue.size() > 0) {
TreeNode curr_node = node_queue.pollFirst();
if (curr_node != null) {
if (is_order_left)
level_list.addLast(curr_node.val);
else
level_list.addFirst(curr_node.val);

if (curr_node.left != null)
node_queue.addLast(curr_node.left);
if (curr_node.right != null)
node_queue.addLast(curr_node.right);

} else {
// we finish the scan of one level
results.add(level_list);
level_list = new LinkedList<Integer>();
// prepare for the next level
if (node_queue.size() > 0)
node_queue.addLast(null);
is_order_left = !is_order_left;
}
}
return results;
}
}

使用深度优先遍历

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
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
protected void DFS(TreeNode node, int level, List<List<Integer>> results) {
if (level >= results.size()) {
LinkedList<Integer> newLevel = new LinkedList<Integer>();
newLevel.add(node.val);
results.add(newLevel);
} else {
if (level % 2 == 0)
results.get(level).add(node.val);
else
results.get(level).add(0, node.val);
}

if (node.left != null) DFS(node.left, level + 1, results);
if (node.right != null) DFS(node.right, level + 1, results);
}

public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
if (root == null) {
return new ArrayList<List<Integer>>();
}
List<List<Integer>> results = new ArrayList<List<Integer>>();
DFS(root, 0, results);
return results;
}
}

二叉树的最大深度

题目描述

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例:
给定二叉树 [3,9,20,null,null,15,7]

1
2
3
4
5
3
/ \
9 20
/ \
15 7

返回它的最大深度 3 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximum-depth-of-binary-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int maxDepth(TreeNode root) {
if (root==null) {
return 0;
}

TreeNode left = root.left;
TreeNode right = root.right;

return Math.max(maxDepth(left), maxDepth(right))+1 ;
}
}

从前序与中序遍历序列构造二叉树

题目描述

根据一棵树的前序遍历与中序遍历构造二叉树。

注意:
你可以假设树中没有重复的元素。

例如,给出

1
2
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]

返回如下的二叉树:

1
2
3
4
5
3
/ \
9 20
/ \
15 7

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
int[] preorder;
int[] inorder;

public TreeNode buildTree(int[] preorder, int[] inorder) {
if (preorder==null || preorder.length==0) {
return null;
}
this.preorder = preorder;
this.inorder = inorder;
return BuildTree(0,preorder.length-1,0,inorder.length-1);
}

//发现其实这些 end没啥大用,就是为了好理解
public TreeNode BuildTree(int pStart,int pEnd, int inStart,int inEnd){
//第一个是根节点
TreeNode root = new TreeNode(preorder[pStart]);
if (pStart==pEnd) {
return root;
}
int i=inStart;
//从中序遍历中找出根节点,将其分开
for (; i <=inEnd; i++) {
if (preorder[pStart] == inorder[i]) {
break;
}
}
int len = i-inStart;
if (pStart+1<=pStart+len)
root.left = BuildTree(pStart+1,pStart+len,inStart,i-1);
if (pStart+len+1<=pEnd)
root.right = BuildTree(pStart+len+1,pEnd,i+1,inEnd);
return root;
}
}

从中序与后序遍历序列构造二叉树

题目描述

根据一棵树的中序遍历与后序遍历构造二叉树。

注意:
你可以假设树中没有重复的元素。

例如,给出

1
2
中序遍历 inorder = [9,3,15,20,7]
后序遍历 postorder = [9,15,7,20,3]

返回如下的二叉树:

1
2
3
4
5
3
/ \
9 20
/ \
15 7

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
int[] postorder;
int[] inorder;
public TreeNode buildTree(int[] inorder, int[] postorder) {

if (postorder==null || postorder.length==0) {
return null;
}
this.postorder = postorder;
this.inorder = inorder;
return BuildTree(0,postorder.length-1,0,inorder.length-1);
}

public TreeNode BuildTree(int pStart,int pEnd, int inStart,int inEnd){

//最后一个是根节点
TreeNode root = new TreeNode(postorder[pEnd]);
if (pStart==pEnd) {
return root;
}
int i=inStart;
//从中序遍历中找出根节点,将其分开
for (; i <=inEnd; i++) {
if (postorder[pEnd] == inorder[i]) {
break;
}
}
int len = i-inStart;
if (pStart<=pStart+len-1)
root.left = BuildTree(pStart,pStart+len-1,inStart,i-1);
if (pStart+len<=pEnd-1)
root.right = BuildTree(pStart+len,pEnd-1,i+1,inEnd);
return root;
}
}

将有序数组转换为二叉搜索树

题目描述

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

示例:

1
2
3
4
5
6
7
8
9
给定有序数组: [-10,-3,0,5,9],

一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:

0
/ \
-3 9
/ /
-10 5

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
int[] nums;
public TreeNode sortedArrayToBST(int[] nums) {
if (nums==null||nums.length==0) {
return null;
}
this.nums=nums;
return creatTree(0, nums.length-1);
}

TreeNode creatTree(int start,int end){
if (start>end) {
return null;
}
int mid = (start+end)/2;
TreeNode root = new TreeNode(nums[mid]);
if (start!=end) {
root.left = creatTree(start,mid-1);
root.right = creatTree(mid+1,end);
}
return root;
}
}

平衡二叉树

题目描述

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。

示例 1:

给定二叉树 [3,9,20,null,null,15,7]

1
2
3
4
5
6
	3
/ \
9 20
/ \
15 7
返回 true 。

示例 2:

给定二叉树 [1,2,2,3,3,null,null,4,4]

1
2
3
4
5
6
7
   1
/ \
2 2
/ \
3 3
/ \
4 4

返回 false 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/balanced-binary-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isBalanced(TreeNode root) {
return IsBalance(root)!=-1;
}

private int IsBalance(TreeNode root){
if(root==null)return 0;
int left = IsBalance(root.left);
// -1代表不平衡
if (left==-1) {
return -1;
}
int right = IsBalance(root.right);
if (right==-1) {
return -1;
}
return Math.abs(left - right) > 1 ? -1 : 1 + Math.max(left, right);
}
}

二叉树的最小深度

题目描述

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明: 叶子节点是指没有子节点的节点。

示例:

给定二叉树 [3,9,20,null,null,15,7],

1
2
3
4
5
3
/ \
9 20
/ \
15 7

返回它的最小深度 2.

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-depth-of-binary-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int minDepth(TreeNode root) {
if (root==null) {
return 0;
}
//说明是叶子节点
if (root.left==null && root.right==null) {
return 1;
}

int left = root.left==null? Integer.MAX_VALUE : minDepth(root.left);
int right = root.right==null? Integer.MAX_VALUE : minDepth(root.right);

return Math.min(left, right)+1;
}
}

滑动窗口小结

刷LeetCode的时候遇到了两题困难的滑动窗口问题,滑动窗口是典型的双指针问题,思想简单但是编写起来还是有点复杂

串联所有单词的子串

题目描述

给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。

注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。

示例 1:

1
2
3
4
5
6
7
输入:
s = "barfoothefoobarman",
words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。

示例 2:

1
2
3
4
输入:
s = "wordgoodgoodgoodbestword",
words = ["word","good","best","word"]
输出:[]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

采用滑动窗口的方法,窗口的长度就是words总单词的长度

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
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<>();
if (s == null || s.length() == 0 || words == null || words.length == 0) return res;
HashMap<String, Integer> map = new HashMap<>();
int word_length = words[0].length();
int word_num = words.length;
for (String word : words) {
map.put(word, map.getOrDefault(word, 0) + 1);
}
//滑动窗口,分情况
//将所有移动分成 word_length 类情况,对于单词长度进行遍历
for (int i = 0; i < word_length; i++) {
int left = i, right = i, count = 0;
HashMap<String, Integer> tmp_map = new HashMap<>();
//每次移动一个单词长度
while (right + word_length <= s.length()) {
//得到子串
String w = s.substring(right, right + word_length);
right += word_length;
//判断子串
if (!map.containsKey(w)) {
//不能有其他字符
count = 0;
//窗口进行滑动
left = right;
tmp_map.clear();
} else {
tmp_map.put(w, tmp_map.getOrDefault(w, 0) + 1);
count++;
//发现次数多了
//窗口进行滑动,一直移除单词,直到次数符合了
while (tmp_map.getOrDefault(w, 0) > map.getOrDefault(w, 0)) {
String t_w = s.substring(left, left + word_length);
count--;
tmp_map.put(t_w, tmp_map.getOrDefault(t_w, 0) - 1);
left += word_length;
}
if (count == word_num) res.add(left);
}
}
}
return res;
}
}

主要参考https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-6/

这题思路有了之后,就比较简单,但是在编程上还是有一些细节

最小覆盖子串

题目描述

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。

示例:

1
2
输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"

说明:

  • 如果 S 中不存这样的子串,则返回空字符串 “”。
  • 如果 S 中存在这样的子串,我们保证它是唯一的答案。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-window-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

这个跟上面类似,也是一个字符串里面找子串,所有同样可以用滑动窗口完成,同样需要记录T中各个字符出现的次数

官方题解如下

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
class Solution {
//记录原字符中出现的次数
Map<Character, Integer> ori = new HashMap<Character, Integer>();
//记录窗口中单词出现的次数
Map<Character, Integer> cnt = new HashMap<Character, Integer>();

public String minWindow(String s, String t) {
int tLen = t.length();
//初始化
for (int i = 0; i < tLen; i++) {
char c = t.charAt(i);
ori.put(c, ori.getOrDefault(c, 0) + 1);
}
int l = 0, r = -1;
int len = Integer.MAX_VALUE, ansL = -1, ansR = -1;
int sLen = s.length();

while (r < sLen) {
++r;
//如果包含则加入,r右滑动
if (r < sLen && ori.containsKey(s.charAt(r))) {
cnt.put(s.charAt(r), cnt.getOrDefault(s.charAt(r), 0) + 1);
}
//检查成功
while (check() && l <= r) {
if (r - l + 1 < len) {
len = r - l + 1;
ansL = l;
ansR = l + len;
}
//l右滑,缩小窗口
if (ori.containsKey(s.charAt(l))) {
cnt.put(s.charAt(l), cnt.getOrDefault(s.charAt(l), 0) - 1);
}
++l;
}
}
return ansL == -1 ? "" : s.substring(ansL, ansR);
}
//检查l到r是否包含子串
public boolean check() {
Iterator iter = ori.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Character key = (Character) entry.getKey();
Integer val = (Integer) entry.getValue();
if (cnt.getOrDefault(key, 0) < val) {
return false;
}
}
return true;
}
}

然后在评论区看到一个更妙的,使用int[] mp = new int[256]记录字符出现的次数,也比较巧妙,并且速度更快,再字符多的时候说不定更节约内存,方法巧妙,可以借鉴

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 String minWindow(String s, String t) {
//mp相当于哈希表
int[] mp = new int[256];
//记录t中字符出现的次数
for (char c : t.toCharArray()) mp[c] += 1;
int start = 0, end = 0;
int n = s.length(), m = t.length();
int cnt = 0;
int minlen = -1;
String ans = "";
while (end < n) {
char c = s.charAt(end);
mp[c] -= 1;
//所有的t都被包含时
if (mp[c] >= 0) cnt += 1;
while (cnt == m) {
if (minlen == -1 || minlen > end - start + 1) {
ans = s.substring(start, end + 1);
minlen = end - start + 1;
}
c = s.charAt(start);
//次数恢复
mp[c] += 1;
if (mp[c] >= 1) cnt -= 1;
//start右滑
start += 1;
}
//end右滑
end += 1;
}
return ans;
}
}

总结

就这两题滑动窗口来说,都是类似于在一个字符串中寻找子串的问题,总结起来就是需要使用哈希表记录对于字符或者字符串的次数,然后使用双指针维护窗口

背包问题小结

介绍

学算法的时候,背包问题是一个很常见的动态规划问题,像什么01背包、完全背包、多重背包等,当时学的时候就有一些懵懵懂懂的,现在复习的时候又不会了,所以进行总结一下,方便日后查看学习。问题基本上都是lintcode上面的题目,然后在github上还找到一个专门讲背包问题的仓库:https://github.com/tianyicui/pack

背包问题

在n个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为m,每个物品的大小为A[i],题目地址:https://www.lintcode.com/problem/backpack/description

样例

1
2
3
4
5
6
7
8
样例 1:
输入: [3,4,8,5], backpack size=10
输出: 9

样例 2:
输入: [2,3,5,7], backpack size=12
输出: 12

挑战

O(n x m) 的时间复杂度 and O(m) 空间复杂度
如果不知道如何优化空间O(n x m) 的空间复杂度也可以通过.

注意事项

你不可以将物品进行切割。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Solution {
/**
* @param m: An integer m denotes the size of a backpack
* @param A: Given n items with size A[i]
* @return: The maximum size
*/
public int backPack(int m, int[] A) {
if(A==null||A.length<=0||m<=0){
return 0;
}
//当前容量下最大值
int []dp = new int[m+1];


for (int i = 0; i < A.length; i++) {
//逆序
for (int j = dp.length-1; j >=A[i]; j--) {
dp[j] = Math.max(dp[j],dp[j-A[i]]+A[i]);
}
}

return dp[m];
}
}

这是一个01背包问题,但是不是那种特别纯的01背包问题,这个背包没有价值,最终的目的是我们装的越多越好,所以我们可以把重量当作他的价值。01背包主要就是选择还是不选择这个物件的问题,在代码中我们先对物品的重量进行遍历,然后再对容量进行遍历,dp[j]表示当容量为j时,能够装得最满的量。对应的状态转移方程如下:

其实这个状态转移方程并不难得出来,我觉得难的是为什么使用一维数组是逆序遍历,而使用二维数组是正序遍历(当然二维数组逆序也可以)。

一维数组逆序遍历是因为如果采用正序遍历的时候,开始的遍历会影响到后续的遍历结果,就拿上面的样例1为例,当第一层遍历到最后一个物品,大小为5时,第二层循环从5-10遍历,当容量为5时,更新dp[5] = 5(这时5这个大小被使用),继续遍历到容量为10时,根据公式dp[10-5]+A[3]更新dp[10]=10(这时5这个大小又被使用),打破了只能使用一次的条件,所以采用从后向前遍历的方法,成功解决这个问题。

二维数组从前正序遍历,是因为dp[i] [j]使用的是上一轮遍历的结果,从下图的箭头也可以看出,这一轮的遍历根本不会对他产生影响。图来自https://www.cnblogs.com/mfrank/p/10533701.html这篇文章

image-20200701160333021

背包问题 II

n 个物品和一个大小为 m 的背包. 给定数组 A 表示每个物品的大小和数组 V 表示每个物品的价值.

问最多能装入背包的总价值是多大?,题目地址:https://www.lintcode.com/problem/backpack-ii/description

样例

样例 1:

1
2
3
输入: m = 10, A = [2, 3, 5, 7], V = [1, 5, 2, 4]
输出: 9
解释: 装入 A[1] 和 A[3] 可以得到最大价值, V[1] + V[3] = 9

样例 2:

1
2
3
输入: m = 10, A = [2, 3, 8], V = [2, 5, 8]
输出: 10
解释: 装入 A[0] 和 A[2] 可以得到最大价值, V[0] + V[2] = 10

挑战

O(nm) 空间复杂度可以通过, 不过你可以尝试 O(m) 空间复杂度吗?

注意事项

  1. A[i], V[i], n, m 均为整数
  2. 你不能将物品进行切分
  3. 你所挑选的要装入背包的物品的总大小不能超过 m
  4. 每个物品只能取一次

代码

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
public class Solution {
/**
* @param m: An integer m denotes the size of a backpack
* @param A: Given n items with size A[i]
* @param V: Given n items with value V[i]
* @return: The maximum value
*/
public int backPackII(int m, int[] A, int[] V) {
// write your code here

if(A==null||A.length<=0||V==null||V.length<=0||A.length!=V.length||m<=0){
return 0;
}

//容量
int []dp = new int[m+1];


for (int i = 0; i < A.length; i++) {
//逆序遍历
for (int j = dp.length-1; j >=A[i]; j--) {
dp[j] = Math.max(dp[j],dp[j-A[i]]+V[i]);
}
}
return dp[m];
}
}

这是一个01背包问题,和上题相比有了价值这个数组,但是思想一样,所以只需要一点小改动就解决了。

背包问题 III

描述

给出 n 个物品, 以及一个数组, nums[i]代表第i个物品的大小, 保证大小均为正数并且没有重复, 正整数 target 表示背包的大小, 找到能填满背包的方案数。
每一个物品可以使用无数次

样例

1
给出四个物品, 大小为 [2, 3, 5, 7], 价值为 [1, 5, 2, 4], 和一个大小为 10 的背包. 最大的价值为 15.

代码

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
package lintcode;

public class backPackIII {
/**
* @param m: An integer m denotes the size of a backpack
* @param A: Given n items with size A[i]
* @param V: Given n items with value V[i]
* @return: The maximum value
*/
public int BackPackIII(int m, int[] A, int[] V) {
// write your code here

if(A==null||A.length<=0||V==null||V.length<=0||A.length!=V.length||m<=0){
return 0;
}

//容量
int []dp = new int[m+1];


for (int i = 0; i < A.length; i++) {
//正序遍历
for (int j = A[i]; j <dp.length; j++) {
dp[j] = Math.max(dp[j],dp[j-A[i]]+V[i]);
}
}
return dp[m];
}

public static void main(String[] args) {
backPackIII b = new backPackIII();
int m=10;
int[]A = {2,3,5,7};
int[]V = {2,5,2,4};
System.out.println(b.BackPackIII(m,A,V));
}

}

这一题是一个完全背包问题,物品是无限多,因为这题是vip才能写的,所以我只能到网上搜这个题目自己写一下,需要注意的点是这里第二层循环采用正序遍历,因为物品时无穷多的。这一题还可以使用贪婪算法进行求解,就是找性价比最高的那个,我这里的实现方法是采用动态规划。

背包问题 IV

给出 n 个物品, 以及一个数组, nums[i]代表第i个物品的大小, 保证大小均为正数并且没有重复, 正整数 target 表示背包的大小, 找到能填满背包的方案数。
每一个物品可以使用无数次

样例

样例1

1
2
3
4
5
6
输入: nums = [2,3,6,7] 和 target = 7
输出: 2
解释:
方案有:
[7]
[2, 2, 3]

样例2

1
2
3
4
5
6
7
输入: nums = [2,3,4,5] 和 target = 7
输出: 3
解释:
方案有:
[2, 5]
[3, 4]
[2, 2, 3]

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Solution {
/**
* @param nums: an integer array and all positive numbers, no duplicates
* @param target: An integer
* @return: An integer
*/
public int backPackIV(int[] nums, int target) {
// write your code here
if(nums==null||nums.length<=0||target<=0){
return 0;
}
//代表次数
int []dp = new int[target+1];
dp[0] = 1;
for (int i = 0; i < nums.length; i++) {
//正序
for (int j = nums[i]; j<dp.length; j++) {
dp[j] += dp[j-nums[i]];
}
}
return dp[target];
}
}

这也是一个完全背包问题,但是又有点不同,是让我们找出填满背包的方案数,我们将dp设置为次数,然后发现这也是一个有最优子结构性质的问题。

背包问题 V

给出 n 个物品, 以及一个数组, nums[i] 代表第i个物品的大小, 保证大小均为正数, 正整数 target 表示背包的大小, 找到能填满背包的方案数。
每一个物品只能使用一次

样例

给出候选物品集合 [1,2,3,3,7] 以及 target 7

1
2
3
4
结果的集合为:
[7]
[1,3,3]
返回2

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Solution {
/**
* @param nums: an integer array and all positive numbers
* @param target: An integer
* @return: An integer
*/
public int backPackV(int[] nums, int target) {
// write your code here
if(nums==null||nums.length<=0||target<=0){
return 0;
}

int []dp = new int[target+1];
dp[0] = 1;
for (int i = 0; i < nums.length; i++) {
//逆序
for (int j = target; j>=nums[i]; j--) {
dp[j] += dp[j-nums[i]];
}
}
return dp[target];
}
}

有了上面的经验我们只需要将背包问题IV的代码的第二层遍历改为逆序就解决了

总结

目前的问题就总结这么多,后面有再接着添加。

重建二叉树

题目描述

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

解题思路

是数据结构中学习的前序中序确定一颗二叉树,其实思路比较简单,通过遍历前序序列,找到根节点,然后从中序遍历中分出左子树和右子树就行,如下图

灰色:根节点

绿色:左子树

红色:右子树

代码实现

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
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
import java.util.Arrays;
public class Solution {

public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if(pre==null||in==null||in.length==0)
return null;

//寻找到中序遍历中的对应下标
int index = -1;
for (int j = 0; j < in.length; j++) {
if(in[j]==pre[0])
{
index = j;
break;
}
}
if (index<0) {
return null;
}
//重新划分一下
int []leftpre = Arrays.copyOfRange(pre, 1, 1+index);
int []rightpre = Arrays.copyOfRange(pre, 1+index,pre.length);
int []leftin = Arrays.copyOf(in, index);
int []rightin = Arrays.copyOfRange(in,index+1,in.length);

TreeNode parent = new TreeNode(pre[i]);
parent.left = reConstructBinaryTree(leftpre,leftin);
parent.right = reConstructBinaryTree(rightpre,rightin);
return parent;
}


}

总结

这题不算很难,思路比较简单,但是写起来的是时候需要注意下一些小细节,所以记录一下。

链表问题小结

刷LeetCode时有一些奇怪的纯链表问题,主要是锻炼链表指针的变换,小结一下

两数相加

问题描述

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

1
2
3
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/add-two-numbers
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

思路比较简单,直接上代码。使用l了哑节点的技巧,哑节点确实好用

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
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode p1 = l1;
ListNode p2 = l2;
ListNode l3 =new ListNode(-1);
ListNode p3 = l3;
int a,c=0;//a表示相加之后的值,c表示进位
int addnum1 = 0,addnum2=0;
while(p1!=null || p2!=null){
addnum1 = p1==null? 0:p1.val;
addnum2 = p2==null? 0:p2.val;

a = (addnum1+addnum2+c)%10;
c = (addnum1+addnum2+c)/10;
p3.next = new ListNode(a);
p3 = p3.next;
if(p1!=null)p1=p1.next;
if(p2!=null)p2=p2.next;
}

//最后的进位
if (c>0) {
p3.next = new ListNode(c);
}

return l3.next;
}
}

合并两个有序链表

问题描述

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:

1
2
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/merge-two-sorted-lists
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

因为做过很多次这个,所以感觉不是很难。同样使用了哑节点

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
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1==null && l2==null) {
return null;
}
ListNode p1 =l1;
ListNode p2 =l2;
ListNode head = new ListNode(-1);
ListNode p3 = head;
while (p1!=null || p2!=null) {
int v1 = p1==null?Integer.MAX_VALUE:p1.val;
int v2 = p2==null?Integer.MAX_VALUE:p2.val;
if (v1<v2) {
p3.next = p1;
p1=p1.next;
}else{
p3.next=p2;
p2=p2.next;
}
p3 = p3.next;

}

return head.next;
}
}

当然也可以使用递归进行,代码看上去更少

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
}
else if (l2 == null) {
return l1;
}
else if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
}
else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
}

合并k个排序链表

问题描述

合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

示例:

1
2
3
4
5
6
7
输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/merge-k-sorted-lists
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

这题最好的方法就是分治进行两两合并,利用到上面的两个两个进行合并

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
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
ListNode[] lists;
public ListNode mergeKLists(ListNode[] lists) {
this.lists = lists;
if(lists!=null && lists.length>0) return dfs(0,lists.length-1);
return null;
}
//分治思想
public ListNode dfs(int left,int right){
if (left==right) return lists[left];

int mid = (left+right)/2;
ListNode leftList= dfs(left,mid);
ListNode rightList= dfs(mid+1,right);
return MergeTwoLists(leftList,rightList);
}

//合并两条链表
public ListNode MergeTwoLists(ListNode l1, ListNode l2) {
if (l1==null && l2==null) {
return null;
}
ListNode p1 =l1;
ListNode p2 =l2;
ListNode head = new ListNode(-1);
ListNode p3 = head;
while (p1!=null || p2!=null) {
int v1 = p1==null?Integer.MAX_VALUE:p1.val;
int v2 = p2==null?Integer.MAX_VALUE:p2.val;
if (v1<v2) {
p3.next = p1;
p1=p1.next;
}else{
p3.next=p2;
p2=p2.next;
}
p3 = p3.next;

}

return head.next;
}
}

两两交换链表中的节点

问题描述

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例:

1
给定 1->2->3->4, 你应该返回 2->1->4->3.

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/swap-nodes-in-pairs
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

采用递归的方式,比较简洁,而且容易看懂,感觉还挺巧妙的

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
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
if ((head == null) || (head.next == null)) {
return head;
}

ListNode firstNode = head;
ListNode secondNode = head.next;


// 进行交换
firstNode.next = swapPairs(secondNode.next);
secondNode.next = firstNode;
// 返回
return secondNode;
}
}

迭代形式,参考链接:https://leetcode-cn.com/problems/swap-nodes-in-pairs/solution/liang-liang-jiao-huan-lian-biao-zhong-de-jie-di-19/。官方答案弄了一个哑节点,比较巧妙,我以前进行链表判断的时候总是需要对头节点前面一个进行判断,加上哑节点可以节省这个判断比较完美。

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
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {

//dummy 哑节点
ListNode dummy = new ListNode(-1);
dummy.next = head;

ListNode prevNode = dummy;

while ((head != null) && (head.next != null)) {

// Nodes to be swapped
ListNode firstNode = head;
ListNode secondNode = head.next;

// Swapping
prevNode.next = secondNode;
firstNode.next = secondNode.next;
secondNode.next = firstNode;

// Reinitializing the head and prevNode for next swap
prevNode = firstNode;
head = firstNode.next; // jump
}

// Return the new head node.
return dummy.next;
}
}

k个一组翻转链表

问题描述

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

示例:

给你这个链表:1->2->3->4->5

当 k = 2 时,应当返回: 2->1->4->3->5

当 k = 3 时,应当返回: 3->2->1->4->5

说明:

  • 你的算法只能使用常数的额外空间。
  • 你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reverse-nodes-in-k-group
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

这题是上一题的加深版,同样思路也可以使用递归进行

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
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
if (head==null) return null;

int length = 0;
ListNode p =head;
while (p!=null && length<k) {
p=p.next;
++length;
}

if (length<k || length==1) {
return head;
}

p=head;
ListNode lastNode = null;
ListNode nextNode = head.next;
length = 0;
while (length<k) {
p.next = lastNode;
lastNode = p;
p = nextNode;
if(nextNode!=null)
nextNode = nextNode.next;
length++;
}
head.next = reverseKGroup(p, k);
return lastNode;
}
}

当然也可以采用迭代的方法进行,同样的使用哑节点小技巧,官方答案链接:https://leetcode-cn.com/problems/reverse-nodes-in-k-group/solution/k-ge-yi-zu-fan-zhuan-lian-biao-by-leetcode-solutio/

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
class Solution {
public:
// 翻转一个子链表,并且返回新的头与尾
pair<ListNode*, ListNode*> myReverse(ListNode* head, ListNode* tail) {
ListNode* prev = tail->next;
ListNode* p = head;
while (prev != tail) {
ListNode* nex = p->next;
p->next = prev;
prev = p;
p = nex;
}
return {tail, head};
}

ListNode* reverseKGroup(ListNode* head, int k) {
ListNode* hair = new ListNode(0);
hair->next = head;
ListNode* pre = hair;

while (head) {
ListNode* tail = pre;
// 查看剩余部分长度是否大于等于 k
for (int i = 0; i < k; ++i) {
tail = tail->next;
if (!tail) {
return hair->next;
}
}
ListNode* nex = tail->next;
// 这里是 C++17 的写法,也可以写成
// pair<ListNode*, ListNode*> result = myReverse(head, tail);
// head = result.first;
// tail = result.second;
tie(head, tail) = myReverse(head, tail);
// 把子链表重新接回原链表
pre->next = head;
tail->next = nex;
pre = tail;
head = tail->next;
}
return hair->next;
}
};

旋转链表

问题描述

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

示例 1:

1
2
3
4
5
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL

示例 2:

1
2
3
4
5
6
7
输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/rotate-list
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

这题自己写的还没有官方题解解的妙,主要放下官方题解。官方题解是将其变成环形链表,让后再解开

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
class Solution {
public ListNode rotateRight(ListNode head, int k) {
// 特殊情况
if (head == null) return null;
if (head.next == null) return head;

//
ListNode old_tail = head;
int n;
//得到长度
for(n = 1; old_tail.next != null; n++)
old_tail = old_tail.next;
//变成环形链表
old_tail.next = head;

// find new tail : (n - k % n - 1)th node
// and new head : (n - k % n)th node
ListNode new_tail = head;
for (int i = 0; i < n - k % n - 1; i++)
new_tail = new_tail.next;
ListNode new_head = new_tail.next;

// 断开环
new_tail.next = null;

return new_head;
}
}

删除排序链表中的重复元素

问题描述

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

示例 1:

1
2
输入: 1->1->2
输出: 1->2

示例 2:

1
2
输入: 1->1->2->3->3
输出: 1->2->3

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

思路简单

1
2
3
4
5
6
7
8
9
10
11
public ListNode deleteDuplicates(ListNode head) {
ListNode current = head;
while (current != null && current.next != null) {
if (current.next.val == current.val) {
current.next = current.next.next;
} else {
current = current.next;
}
}
return head;
}

删除排序链表中的重复元素II

问题描述

给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。

示例 1:

1
2
输入: 1->2->3->3->4->4->5
输出: 1->2->5

示例 2:

1
2
输入: 1->1->1->2->3
输出: 2->3

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

思路也比较简单

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
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head==null)return null;
if(head.next==null)return head;
ListNode last = null;
ListNode curent = head;
ListNode next = head.next;
ListNode newHead = null;

while (next!=null) {
if (curent.val==next.val) {
//如果 curent = next

while(next!=null && curent.val==next.val) {
curent = curent.next;
next = next.next;
}
//需要判断 last,如果等于null
//说明从头开始就重复,如 1->1->1->2->2->3
if (last!=null) {
last.next = next;
}
curent.next = null;
curent = next;
if(next!=null) next =next.next;
if (next==null &&newHead==null ) {
newHead = curent;
}
}else{
//不相等
last = curent;
curent = next;
next = next.next;
if (newHead==null) {
newHead = last;
}
}
}
return newHead;
}
}

分割链表

问题描述

给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。

你应当保留两个分区中每个节点的初始相对位置。

示例:

1
2
输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/partition-list
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

使用的方法类似于快排

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
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode partition(ListNode head, int x) {
if (head == null) {
return null;
}
//多加一个指针,方便后续操作
ListNode first = new ListNode(x);
first.next = head;
//添加快排操作
ListNode pivtpos = first;
ListNode pre = first;
ListNode p = first.next;
while(p!=null){

if (p.val<first.val) {
if (pre!=pivtpos) {
// 指针交换
pre.next = p.next;
p.next = pivtpos.next;
pivtpos.next = p;

p=pre.next;
}else{
pre = pre.next;
p=p.next;
}
pivtpos=pivtpos.next;
}else{
pre = pre.next;
p=p.next;
}
}
head = first.next;
first.next = null;
return head;
}
}

标准答案使用了类似于摘取法,也比较巧妙

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
class Solution {
public ListNode partition(ListNode head, int x) {

// before and after are the two pointers used to create the two list
// before_head and after_head are used to save the heads of the two lists.
// All of these are initialized with the dummy nodes created.
ListNode before_head = new ListNode(0);
ListNode before = before_head;
ListNode after_head = new ListNode(0);
ListNode after = after_head;

while (head != null) {

// If the original list node is lesser than the given x,
// assign it to the before list.
if (head.val < x) {
before.next = head;
before = before.next;
} else {
// If the original list node is greater or equal to the given x,
// assign it to the after list.
after.next = head;
after = after.next;
}

// move ahead in the original list
head = head.next;
}

// Last node of "after" list would also be ending node of the reformed list
after.next = null;

// Once all the nodes are correctly assigned to the two lists,
// combine them to form a single list which would be returned.
before.next = after_head.next;

return before_head.next;
}
}

反转链表II

问题描述

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。

说明:
1 ≤ m ≤ n ≤ 链表长度。

示例:

1
2
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reverse-linked-list-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseBetween(ListNode head, int m, int n) {
if (head==null) {
return null;
}
//哑节点
ListNode dummy = new ListNode(-1);
dummy.next = head;

//p1为 m的前一个节点
ListNode p1 = dummy;
// m代表的节点
ListNode mNode = head;
//n代表的节点
ListNode NNode = head;

// p2 为n之后的节点
ListNode p2 = head;

int i = 0;
while (true) {
i++;
if (i==m-1) {
p1 = NNode;
}

if (i==m) {
mNode = NNode;
}
if (i==n) {
break;
}
NNode = NNode.next;
}
p2 = NNode.next;
NNode.next = null;

//将 m 到 n节点进行翻转
p1.next = reverseList(mNode);
mNode.next = p2;
return dummy.next;
}

public ListNode reverseList(ListNode head) {
ListNode cur = head;
ListNode lastNode = null;
ListNode nextNode = head.next;

while (cur!=null) {
cur.next = lastNode;
lastNode = cur;
cur = nextNode;
if(nextNode!=null)
nextNode = nextNode.next;
}

return lastNode;
}

}

零钱兑换

在刷Leetcode的时候发现了零钱兑换的两题,两题都是属于背包问题,但是又有所不同,虽然背包问题已经总结了一下但还是想总结一下这两题

322.零钱兑换

题目描述

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

示例 1:

1
2
3
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1

示例 2:

1
2
输入: coins = [2], amount = 3
输出: -1

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

主要写一下背包思想的解法,其他的可以参考:https://leetcode-cn.com/problems/coin-change/solution/dong-tai-gui-hua-shi-yong-wan-quan-bei-bao-wen-ti-/

可以看的出来这个零钱兑换很像完全背包问题

类似的地方:

  • 完全背包问题背包有体积,这里对应着总金额amount
  • 完全背包中有各种物品,这里对应着硬币coins
  • 完全背包中各种物品数量无限,在这里硬币的数量也无限

不同之处:

  • 完全背包的目的是在体积允许的范围内,拿走价值更多的物品;这里是在amount允许的范围里,需要硬币数量最少
  • 完全背包最终背包不需要被填满,这里最终硬币总金额需要等于amount

所以最终我们模仿完全背包的公式,dp[i] 表示amount为i时,可以凑成总金额所需的最少的硬币个数(与背包中dp[i]表示价格最大形成对比),然后就可以得出答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int coinChange(int[] coins, int amount) {
// dp[i] 表示amount为i时,可以凑成总金额所需的最少的硬币个数。
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);
dp[0] = 0;

for (int coin : coins) {
for (int i = coin; i <= amount; i++) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}

if (dp[amount] == amount + 1) {
dp[amount] = -1;
}
return dp[amount];
}
}

一开始我就想借鉴背包思想解决这题,但是我过于纠结体积和价值,一直用的是max导致最终没能完成,其实现在跳出来想想这个min才是恰到好处,也体现着背包思想,是我自己太过死板。

518.零钱兑换II

题目描述

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

示例 1:

1
2
3
4
5
6
7
输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

1
2
3
4
5
6
7
输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额2的硬币不能凑成总金额3。
示例 3:

输入: amount = 10, coins = [10]
输出: 1

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change-2
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

这题就比较简单了,因为在做背包问题的时候刚好做过,也是类似填表格的方法,详情可以看:https://www.cnblogs.com/zhou-ning/articles/13222220.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public int change(int amount, int[] coins) {
if (coins == null || coins.length == 0) {
if (amount==0) {
return 1;
}
return 0;
}
// 代表次数
int[] dp = new int[amount + 1];
dp[0] = 1;
for (int i = 0; i < coins.length; i++) {
// 正序
for (int j = coins[i]; j < dp.length; j++) {
dp[j] += dp[j - coins[i]];
}
}
return dp[amount];
}
}

java中==、equals、hashCode

1、== 与 equals

== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型==比较的是值,而引用数据类型比较的是内存地址)。

equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:

  • 情况 1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
  • 情况 2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class test1 {
public static void main(String[] args) {
String a = new String("ab"); // a 为一个引用
String b = new String("ab"); // b为另一个引用,对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
if (aa == bb) // true
System.out.println("aa==bb");
if (a == b) // false,非同一对象
System.out.println("a==b");
if (a.equals(b)) // true
System.out.println("aEQb");
if (42 == 42.0) { // true
System.out.println("true");
}
}
}

说明:

  • String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
  • 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。

2、hashCode 与 equals

hashCode()介绍

hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。

散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)

为什么要有 hashCode:

我们先以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode: 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与该位置其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的 Java 启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。

通过我们可以看出:hashCode() 的作用就是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()在散列表中才有用,在其它情况下没用。在散列表中 hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

hashCode()与 equals()的相关规定
  1. 如果两个对象相等,则 hashcode 一定也是相同的
  2. 两个对象相等,对两个对象分别调用 equals 方法都返回 true
  3. 两个对象有相同的 hashcode 值,它们也不一定是相等的
  4. 因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
  5. hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

转载自:https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Java基础知识.md

java中String、StringBuffer、StringBuilder

问题:String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?

简单的来说:String 类中使用 final 关键字修饰字符数组来保存字符串,private final char value[],所以String 对象是不可变的。

在java8中,String 的实现

1
2
3
4
5
6

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}

所以说String是不变的,并且会存放在常量池中

在 Java 9 之后,String 类的实现改用 byte 数组存储字符串 private final byte[] value;

StringBuilderStringBuffer 的构造方法都是调用父类构造方法也就是AbstractStringBuilder 实现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;

/**
* The count is the number of characters used.
*/
int count;

AbstractStringBuilder(int capacity) {
value = new char[capacity];
}}

所以说StringBuilderStringBuffer 是可以变的

线程安全性

String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilderStringBuilderStringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacityappendinsertindexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁(synchronized),所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

性能

每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

对于三者使用的总结:

  1. 操作少量的数据: 适用 String
  2. 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
  3. 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer

转载自:https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Java基础知识.md