返回 LeetCode 刷题

Markdown File

回溯算法

回溯算法.md

1回溯算法(第一章 基础)
2
3这一章我们只讲思想,不做复杂题。
4
51. 前提条件
6
7回溯(Backtracking)不是一种数据结构。
8
9也不是一种具体算法。
10
11它是一种:算法设计思想
12
13主要解决:
14
15枚举所有方案
16
17搜索所有可能
18
19找满足条件的方案
20
21 剪枝优化
22
23典型题:
24
25子集
26
27组合
28
29排列
30
31N皇后
32
33数独
34
35组合总和
36
372. 回溯到底是什么?
38
39试一条路
40
41
42如果不行
43
44
45
46退回来
47
48
49换另一条路
50
51所以:Backtracking
52
53
54
55回退
56
57
58
59回溯
60
61例如:
62
63走迷宫:
64
65 A
66
67 / \
68
69 B C
70
71 /
72
73 D
74
75走:
76
77A
78
79
80
81B
82
83
84
85D
86
87发现:
88
89死路
90
91于是:
92
93退回B
94
95
96
97退回A
98
99
100
101走C
102
103这就是:
104
105回溯。
106
1073. 回溯和DFS区别
108
109DFS
110
111是一种遍历方式。
112
113例如:
114
115树:
116
1170
118
119
120
1211
122
123
124
1252
126
127DFS:访问所有节点。
128
129而回溯:
130
131不是为了遍历。
132
133而是枚举所有可能。
134
135例如:
136123
137
138所有排列
139
140DFS:
141
142只是访问:
143
1441
145
1462
147
1483
149
150回溯:
151
152123
153
154132
155
156213
157
158231
159
160312
161
162321
163
164完全不是一个目标。
165
1664. 回溯为什么是DFS升级?
167
168因为它也是:
169
170一路走到底。
171
172例如:
173
174组合:
175
1761
177
178
179
1802
181
182
183
1843
185
186但是:
187
188DFS到底结束。
189
190直接:return
191
192然而回溯:
193
194到底以后
195
196还要撤销刚刚的选择。
197
198例如:
199
200path
201
202
203
204[1]
205
206
207
208[1,2]
209
210
211
212[1,2,3]
213
214结束。
215
216不是直接结束。
217
218而是:
219
220删除3
221
222
223
224删除2
225
226
227
228继续尝试其它数字
229
230所以:
231
232回溯:
233
234比DFS:
235
236多了一步恢复现场。
237
2385. 回溯的四步
239
240① 做选择
241
242
243
244② 递归
245
246
247
248③ 撤销选择
249
250
251
252④ 换另一种选择
253也即是:
254选择
255
256
257
258递归
259
260
261
262恢复
263
264
265
266继续
267
2686. 回溯为什么必须恢复?
269
270举个例子。
271
272例如:
273
274求:
275
276123
277
278所有排列
279
280开始:
281
282path
283
284[]
285
286
287选择:
288
2891
290
291path:
292
293[1]
294
295继续:
296
297选择:
298
2992
300
301path:
302
303[1,2]
304
305继续:
306
3073
308
309得到:
310
311[1,2,3]
312
313记录答案。结束。
314
315现在如果不恢复path:
316
317还是[1,2,3]
318
319那么以后根本没法尝试:
320
3211
322
3233
324
3252
326
327所以必须删掉3
328
329变[1,2]
330
331继续。
332
3337. 回溯固定模板
334
335void dfs(...)
336{
337 if(结束条件)
338 {
339 保存答案;
340 return;
341 }
342
343 for(...)
344 {
345 做选择;
346
347 dfs(...);
348
349 撤销选择;
350 }
351}
352
353
3548. 一个例子
355
356例如:
357
358从:
359
3601
361
3622
363
3643
365
366任选。
367
368代码:
369
370void dfs()
371{
372 for(int i=1;i<=3;i++)
373 {
374 printf("%d ",i);
375 }
376}
377
378这不是回溯。
379
380因为没有恢复。
381
382真正回溯:
383
384path.push(i);
385
386dfs();
387
388path.pop();
389
390这里:
391
392push
393
394
395
396递归
397
398
399
400pop
401
402就是回溯。
403
4049. 回溯代码模板
405void dfs(...)
406{
407 if(满足条件)
408 {
409 保存答案;
410 return;
411 }
412
413 for(...)
414 {
415 //① 做选择
416 path[pathSize++] = 当前元素;
417
418 //② 递归
419 dfs(...);
420
421 //③ 撤销选择
422 pathSize--;
423 }
424}
425
426pathSize--
427
428不是删除数组。
429
430而是逻辑删除。
431
432以后都会这样写
433
43410. 回溯为什么不用真的删除?
435
436例如数组:
437
438path
439
440
441
4421
443
4442
445
4463
447
448现在:
449
450pathSize=3;
451
452删除:
453
4543
455
456其实代码只有:
457
458pathSize--;
459
460变成:
461
462pathSize=2
463
464数组实际上还是:
465
4661
467
4682
469
4703
471
472只是以后只看前2个。
473
474所以效率:非常高。
475
47611. 回溯总结
477Backtracking
478
479核心思想:
480
481 做选择
482
483
484
485 递归搜索
486
487
488
489 撤销选择
490
491
492
493 尝试下一种可能
494
495与DFS区别:
496
497DFS:
498
499 遍历
500
501Backtracking:
502
503 搜索所有方案
504
505固定模板:
506
507for
508
509
510
511选择
512
513
514
515递归
516
517
518
519恢复
520
521
522
523继续
Rendered Preview

回溯算法(第一章 基础)

这一章我们只讲思想,不做复杂题。

  1. 前提条件

回溯(Backtracking)不是一种数据结构。

也不是一种具体算法。

它是一种:算法设计思想

主要解决:

枚举所有方案

搜索所有可能

找满足条件的方案

剪枝优化

典型题:

子集

组合

排列

N皇后

数独

组合总和

  1. 回溯到底是什么?

试一条路


如果不行

退回来

换另一条路

所以:Backtracking

回退

回溯

例如:

走迷宫:

    A

  /   \

 B     C

/

D

走:

A

B

D

发现:

死路

于是:

退回B

退回A

走C

这就是:

回溯。

  1. 回溯和DFS区别

DFS

是一种遍历方式。

例如:

树:

0

1

2

DFS:访问所有节点。

而回溯:

不是为了遍历。

而是枚举所有可能。

例如:
123

所有排列

DFS:

只是访问:

1

2

3

回溯:

123

132

213

231

312

321

完全不是一个目标。

  1. 回溯为什么是DFS升级?

因为它也是:

一路走到底。

例如:

组合:

1

2

3

但是:

DFS到底结束。

直接:return

然而回溯:

到底以后

还要撤销刚刚的选择。

例如:

path

[1]

[1,2]

[1,2,3]

结束。

不是直接结束。

而是:

删除3

删除2

继续尝试其它数字

所以:

回溯:

比DFS:

多了一步恢复现场。

  1. 回溯的四步

① 做选择

② 递归

③ 撤销选择

④ 换另一种选择
也即是:
选择

递归

恢复

继续

  1. 回溯为什么必须恢复?

举个例子。

例如:

求:

123

所有排列

开始:

path

[]

选择:

1

path:

[1]

继续:

选择:

2

path:

[1,2]

继续:

3

得到:

[1,2,3]

记录答案。结束。

现在如果不恢复path:

还是[1,2,3]

那么以后根本没法尝试:

1

3

2

所以必须删掉3

变[1,2]

继续。

  1. 回溯固定模板

void dfs(...)
{
if(结束条件)
{
保存答案;
return;
}

for(...)
{
    做选择;

    dfs(...);

    撤销选择;
}

}

  1. 一个例子

例如:

从:

1

2

3

任选。

代码:

void dfs()
{
for(int i=1;i<=3;i++)
{
printf("%d ",i);
}
}

这不是回溯。

因为没有恢复。

真正回溯:

path.push(i);

dfs();

path.pop();

这里:

push

递归

pop

就是回溯。

  1. 回溯代码模板
    void dfs(...)
    {
    if(满足条件)
    {
    保存答案;
    return;
    }

    for(...)
    {
    //① 做选择
    path[pathSize++] = 当前元素;

     //② 递归
     dfs(...);
    
     //③ 撤销选择
     pathSize--;
    

    }
    }

pathSize--

不是删除数组。

而是逻辑删除。

以后都会这样写

  1. 回溯为什么不用真的删除?

例如数组:

path

1

2

3

现在:

pathSize=3;

删除:

3

其实代码只有:

pathSize--;

变成:

pathSize=2

数组实际上还是:

1

2

3

只是以后只看前2个。

所以效率:非常高。

  1. 回溯总结
    Backtracking

核心思想:

做选择

递归搜索

撤销选择

尝试下一种可能

与DFS区别:

DFS:

遍历

Backtracking:

搜索所有方案

固定模板:

for

选择

递归

恢复

继续