数学建模社区-数学中国
标题:
算法越学越扎心,有没啥破解之法?
[打印本页]
作者:
杨利霞
时间:
2020-4-24 17:58
标题:
算法越学越扎心,有没啥破解之法?
3 U( ^9 ^( l: M8 S9 p! c
算法越学越扎心,有没啥破解之法?
8 S& K3 _, `% [! J$ _# U" V
算法越学越扎心,有没啥破解之法?
# I& k# K( ?, i8 q
- _& J5 K/ y; s. r
对于算法的学习,我也是从一个小白一步步走来,当然,现在仍然很菜,,,不过,鉴于我觉得还有一些人比我更菜了,我决定谈谈我算法学习过程走过的坑,以及自己总结的一些经验。
3 b' O* o9 a( V/ q1 q5 F* }, G/ Z3 p
8 g M0 G& n% [: o2 ?/ M* G b
切勿盲目刷题:刷题前的知识积累
& {# Y2 F+ }% Z
4 G) U) O: U. D, p# U
说实话,想要提高自己的算法,真的没啥捷径,我觉得最好的捷径就是脚踏实地着多动手去刷题,多刷题。
& k7 P$ ~& [6 C7 u
3 X0 q y$ J% F+ a3 B
但是,我必须提醒的是,如果你是小白,也就是说,你连常见的数据结构,如链表、树以及常见的算法思想,如递归、枚举、动态规划这些都没学过,那么,我不建议你盲目疯狂着去刷题的。而是先去找本书先去学习这些必要的知识,然后再去刷题。
2 ]+ ~' J3 k2 N, O) ?
. j- U& b! g f& Y2 Y/ \
因为,如果这些基础都不懂的话,估计一道题做了几个小时,然后看答案都看不懂,做题没有任何思路,这是很难受的。久而久之,估计没啥动力了,我刚开始就是这样,一道题答案看一天,然而还是不大懂,什么回溯啊,暴力啊,还不知道是啥意思。
+ n. k0 T+ R8 h, e& y9 L
9 |( h3 |- i2 }8 T- _5 ?+ D; |
也就是说,假如你要去诸如leetcode这些网站刷题,那么,你要先具备一定的基础,这些基础包括:
1 t$ f) J$ {* c% x4 e% a
% V/ ^& k0 X# P( V4 s
1、常见数据结构:链表、树(如二叉树)。(是的,链表和二叉树是重点,图这些可以先放着)
p4 W2 b1 m+ ]1 D
/ b+ k0 i' @8 D. X- L
2、常见算法思想:贪婪法、分治法、穷举法、动态规划,回溯法。(贪婪、穷举、分治是基础,动态规划有难度,可以先放着)
" R* \ S, R/ s9 l3 C: n
/ X6 o# f! q, y* l; Q& `. B
以上列出来的算是最基本的吧。就是说你刷题之前,要把这些过一遍再去刷题。如果你连这些最基本的都不知道的话,那么你再刷题的过程中,会很难受的,思路也会相对比较少。
# q7 j6 I4 p8 c l3 q+ m2 v
( U* p# @5 W* P' q2 w/ `
总之,千万不要急,先把这些基本的过一遍,力求理解,再去刷题。
0 t( X" ?1 |# \5 V* z! L! c
& X7 ^. L4 r/ [
在这里,我推荐基本我大一时看过的书籍吧,感觉还是非常不错的,如果对于数据结构时零基础的话,那么我建议你可以看《数据结构与算法分析:C语言描述版》这本书,这本书自认为真的很 nice,当时我把这本书里面的全部都看了,并且 coding 了一遍,感觉整个人有了质的飞跃。
: \ ]8 c; i7 N! u
: l x7 O7 }6 `9 c3 w: _
后面我时在一些学校的OJ刷题,当时看的一本书叫做《挑战程序设计大赛》,日本作家写的,我觉得这本书也很nice,里面有分初级,中级和高级三个模块,基础比较差的可以从初级开始看起。
; s4 E$ N( B) ^( x, O6 D
4 Z# ?" Y" |' F2 e2 _( @9 k* V
当然,这两本书,你可以在这个Github上找到:https://github.com/iamshuaidi/CS-Book
6 ~6 z0 t2 E# n% m+ C9 G |$ g
总结下:
# y3 W R l( V* E1 k: W
2 E9 l% K- J! s1 ^6 A3 U
提高数据结构与算法没啥捷径,最好的捷径就是多刷题。但是,刷题的前提是你要先学会一些基本的数据结构与算法思想。
# o ^7 D" d5 M3 p5 d& g/ f
4 E) g1 l& W, Z- r3 A$ r
AC不是目的,我们要追求完美
0 x5 i/ m( Z2 W/ n- W
% }& Q! q4 {7 {. R& }
如何刷题?如何对待一道算法题?
- z O3 `. N( b% e+ y" d
- q: @, O8 i7 h: ]% u6 r
我觉得,在做题的时候,一定要追求完美,千万不要把一道题做出来之后,提交通过,然后就赶紧下一道。我认为这意义不大,因为一道题的解法太多了,有些解法态粗糙了,我们应该要寻找最优的方法。
5 M0 `& y* C. ?% I4 i
) ]* c* x. ]. |8 K; a6 t9 f7 y1 t
算法能力的提升和做题的数量是有一定的关系,但并不是线性关系。也就是说,在做题的时候,要力求一题多解,如果自己实在想不出来其他办法了,可以去看看别人是怎么做的,千万不要觉得模仿别人的做法是件丢人的事。
s7 l) Y8 s2 H e1 h) J
9 s0 r$ Q4 t* G; V/ z' _. n1 d
我做题的时候,我一看到一道题,可能第一想法就是用很粗糙的方式做,因为很多题采用暴力法都会很容易做,就是时间复杂度很高。之后,我就会慢慢思考,看看有没其他方法来降低时间复杂度或空间复杂度。最后,我会去看一下别人的做法,当然,并不是每道题都会这样执行。
0 h$ p ?- K N; k6 ^0 |+ n
# n. `& L, B a- s; p
衡量一道算法题的好坏无非就是时间复杂度和空间复杂度,所以我们要力求完美,就要把这两个降到最低,令他们相辅相成。
/ r7 H3 k! d9 ]" z
+ j8 |: l% k* G) c& m; b0 w- _# |
我举道例题吧:
" W% z' u( T6 \4 q
! l7 i4 r9 `$ q: B/ z3 D# |1 f
问题: 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法?
, F# D- ]: _2 l9 s7 k0 w" ^" h$ q
! M" f {, l& o; E( ?7 B/ [& `
这道题我在以前的分章分析过,不懂的可以先看下之前写的:递归与动态规划—基础篇1
+ ]6 h9 o! t1 L- P; Y
8 T" O* R7 Y% z( `
方法1::暴力递归
" w5 }+ a5 ], W3 O0 ^; n9 B7 A
# T4 \& `2 Y+ C" k0 x' P1 Y$ O8 j
这道题不难,或许你会采取下面的做法:
4 F" L r8 e: t# U& t
8 p, {' F" Z: R/ f8 Z t( e
public int solve(int n){
# I! o0 f. F! Q( R) w( u2 B/ [' h
if(n <= 2){
c0 p. F e# e- _: G5 F$ r" l
return n;
& Y+ T1 {3 J/ L4 g& x8 r, h
}else{
. Q n4 j, ^# ?- u+ f
return solve(n-1) + solve(n-2);
* n( k& p2 b' F& j0 x8 h
}
* v$ W- y3 G+ ^1 l) M5 x, q- J9 `" ^
}
# O0 ^& N" b9 |4 h0 v1 K& e
1
. Z: X, |7 _$ t$ b( Q+ M
2
$ l! x+ O$ N% f
3
, G3 R% x" W" S$ A0 i
4
* j3 y0 W. H2 k3 \5 y4 @$ l) Q) \
5
7 G" v0 }& n- ?7 L* R% K
6
+ N* B5 d) r- g. J
7
$ q, G7 p) z# [+ w
这种做法的时间复杂度很高,指数级别了。但是如果你提交之后侥幸通过了,然后你就接着下一道题了,那么你就要好好想想了。
% H* i7 A; s2 m' c2 K& f/ z
8 |; y' v0 @$ `
方法二:空间换时间
3 B( O, F6 \+ Q& t1 N, p* x
$ H7 q8 v. ], C( s( e" d
力求完美,我们可以考虑用空间换时间:这道题如何你去仔细想一想,会发现有很多是重复执行了。不行你可以画个图
% F* Y- S) a0 L/ K/ `2 o4 I% S
8 Q, x8 R- h+ C) T* k: h. ^+ X
所以可以采取下面的方法:
( u0 i5 n4 |0 X) Z
$ T% M3 ~; e: G& {! T
//用一个HashMap来保存已经计算过的状态
" g/ r# f& Z/ }/ m$ x
static Map<Integer,Integer> map = new HashMap();
; t5 H2 p% b9 K0 Y! H5 H
public static int solve(int n){
6 G0 Y5 J- I7 R( @6 Q$ \
if(n <= 2){
. \% h4 C5 B: J9 B# {
return n;
1 k/ }7 i% z2 x R
}else{//是否计算过
4 D; _% E; a& ^' U
if(map.containsKey(n)){
. s* a, b2 }: m X9 p) |, S
return map.get(n);
1 }( r9 k8 S3 W- g9 Y7 Q
}else{
* S2 C+ O; t2 V- s+ m. F
int m = solve(n-1) + solve(n-2);
, P P2 Z! _4 z% V3 ` y: M
map.put(n, m);
+ E6 Z$ b" S6 S1 d2 `, _. i
return m;
* M- y0 b8 I, \
}
2 G8 S* l* _0 Q/ L4 S. p
}
; H+ a2 b: s) O7 p) R2 X' m) h
}
& B4 r1 y8 O. {/ T
1 Z$ ? f1 h9 Q8 G9 M+ _4 n
1
& _5 _- P' B; m% w
2
6 g7 C$ _+ m- f: n
3
2 y, c) @1 R" P& |0 B+ j& D F
4
% u7 |- o6 G7 {& C. z0 S* k
5
8 Z; D9 q5 ]1 K" [# M' u$ @
6
+ O& T& I9 `. P+ ~) Y; Y5 o
7
* r- {3 k. e9 i3 I7 ~
8
$ F6 h5 n6 C% z# L e& t- Q: @
9
q2 \! l. o# k5 w# f7 B8 u2 ^
10
& h1 K6 ?2 R) p2 b8 v& y% v
11
. W4 ^% J. {1 o3 J& ]6 n' c9 [! p
12
" H( B5 @4 h6 |- @. \! h
13
: X3 J. _9 f x' j
14
' t- g- T, Y& ~- r% `2 ^$ j
15
; D$ Q) U3 K) v/ s; @; D
16
* y0 d/ |* C$ J |+ }4 E
这样,可以大大缩短时间。也就是说,当一道题你做了之后,发现时间复杂度很高,那么可以考虑下,是否有更好的方法,是否可以用空间换时间。
& I& |. k( s" ]( F
J- \, g w' q
方法三:斐波那契数列
- y- |0 L5 T! n6 T. r" H
9 h( y/ x/ E, B
实际上,我们可以把空间复杂度弄的更小,不需要HashMap来保存状态:
' V! Q0 U. }7 ]0 J! ~
& ~5 a- o( ]: t! Y! F
public static int solve(int n){
3 Y1 ]: j: [8 R* M* D. |, w& r
if(n <= 2){
# ], w' x0 P* t4 T% ~
return n;
/ t/ z3 Q* n. [2 S6 x. p5 b
}
% v& ?2 u- n' ^6 d! n' \0 T& z
int f1 = 0;
: X0 H$ `: D3 ?; q% Q9 a- k1 W c
int f2 = 1;
0 I6 p& {+ E* M2 i1 b$ u( ~) g, g* [
int sum = 0;
, O, r; c4 g9 r0 c# \. \
for(int i = 1; i<= n; i++){
, b% N% p) N( v5 v q3 S! |# E0 ?: ]
sum = f1 + f2;
& K9 N4 {% R- D7 {
f1 = f2;
) n2 j/ @# Q$ F
f2 = sum;
8 ?) k0 C. k0 z
}
8 i9 W9 R: P0 S Y$ a
return sum;
V3 ?* c4 p9 m# c
}
: T; G( j, O* N% K0 D5 E
1
% f% U- k1 }3 |0 J
2
; m' Z/ }& c4 A6 l. i W6 h& g
3
( ]2 L. t! C* L! l$ s0 \8 X
4
6 z2 W* i ]* @ |" F
5
; a. Y0 k: V1 p% i2 E
6
. \) x$ |$ r1 P, m" r) m0 Z5 `
7
2 p) ]3 l8 Y4 m7 l1 x2 \
8
. w+ B s) I4 {, }; ^
9
7 ~) p- @8 F5 Y& R3 v: f% X, O$ m
10
3 ~9 r8 T9 B" ]8 c3 I' q3 M
11
' @( p% p; r" R6 D
12
. e$ M1 o" C$ n
13
3 `) k) C0 G0 D# l" J# ^
14
- U& g7 k! j0 ]2 ], C. J* S
我弄这道题给你们看,并不是在教你们这道题怎么做,而是有以下目的:
1 Z4 E5 I) M" X0 i
( l6 ~$ z i* V* G8 B2 H
1、在刷题的时候,我们要力求完美。
) u7 D# ?2 M- Y# O
X& K; _$ I, Q' o: d. c
2、我想不到这些方法啊,怎么办?那么你就可以去看别人的做法,之后,遇到类似的题,你就会更有思路,更知道往哪个方向想。
4 F% c9 H, ^* ?) \6 p
4 O w j+ |. w d# H6 `5 R
3、可以从简单暴力入手做一道题,在考虑空间与时间之间的衡量,一点点去优化。
5 d5 l3 c- t/ I0 N3 n
, G) y( \9 ~3 T3 t+ n! }1 F+ d2 u0 `
挑战自己,跳出舒适区
) n; M0 v9 B4 o8 J
# i/ {$ H2 N' b9 a) T. f% n4 H
什么叫舒适区?在刷题的时候,可能有一类题是你比较懂的,你每次一看就有思路,然后半个小时就撸好代码,提交代码,然后通过了,然后,哇,又多刷了一道题,心里很舒服。
! o% d5 k- ]7 O! b4 P2 D" M
4 t8 J' `6 f1 B/ g2 Z4 P* v) M
但是,记住,前期你可以多刷这种题练手,提升自己的乐趣,但,我还是建议你慢慢跳出舒适区,去做一些自己不擅长的题,并且找段时间一直刷这种题。例如,我觉得我在递归方面的题还是挺强的,
3 v a, y. K# y4 B8 Y& u' P# B @0 B
但是,我对动态规划的题,很菜,每次都要想好久,每次遇到这种题都有点害怕,没什么信心。不过有段时间我觉得只刷动态规划的题,直接在 leetcode 选定专题,连续做了四五十道,刚开始很难受,后来就慢慢知道了套路了,一道题从两三个小时最后缩到半小时,简单的十几分钟就搞定。感觉自己对这类型的题也不惧怕的。
1 ^' a* d% k) Z& i" z3 r
5 \& B4 m* `; W/ Q& r$ K6 e7 h
当然,对于动态规划的学习,大家也可以看我这篇广受好评的文章:为什么你学不过动态规划?告别动态规划,谈谈我的经验
; n" l& i" a0 ?6 O$ S
X" D; @0 u5 D
所以,建议你,一定要学好跳出自己的舒适区。
6 i) R. \, {! u4 _2 b3 |4 h
; S4 S6 d, C3 O z
一定要学会分类总结
$ T. L' w) l# Z- q2 }8 m) _
% @3 U3 T: A: C% @
有些人以为 leetcode 的题刷的越多,就一定能越厉害,其实不然,leetcode 虽然有 1000 多道题,但题型就那么几类,我们前期在刷的时候,我是建议按照题型分类刷题的,例如我这整理刷二叉树相关,然后刷链表相关,然后二分法,然后递归等等,每刷一种题型,都要研究他们的套路,如果你愿意去总结,那么 leetcode 的题,其实你刷几百道,有目的、挑选的刷,我觉得就差不多了。
, Z* H3 o7 P4 a4 q# M# r
3 S5 r- p f% [9 q8 F) ]+ C& Q
我看过一本书,叫做《程序员代码面试指南:IT 名企算法与数据结构题目最优解》,这本书就非常不错,里面按照栈,队列,链表,二叉树,字符串等一个专题一个专题来刷的,并且每道题都给出了最优解,而且里面的题有一定的难度,感兴趣的,真心不错,如果你把这本书的题全部搞定,并且总结相关套路,那么你的算法一定有很大的提升。
' k9 c# h9 Z% _3 R
; w/ W7 Z0 W2 S2 n
推荐一些刷题网站
* H$ K2 g9 {, T( E& F3 X+ i0 w* ?
3 m+ |. h1 |4 j! P1 n
我一般是在leetcode和牛客网刷题,感觉挺不错,题目难度不是很大。
( |) [/ P2 |5 i$ g
: t) }9 h" {8 ]0 S2 W$ @; @
在牛客网那里,我主要刷剑指Offer,不过那里也有个在线刷leetcode,不过里面的题量比较少。牛客网刷题有个非常方便的地方就是有个讨论区,那里会有很多大佬分享他们的解题方法,不用我们去百度找题解。所以你做完后,实在想不出,可以很方便着去看别人是怎么做的。
7 `/ E6 }- ?$ ~
" f! X, _0 L/ ^8 {8 X% h- J" ]6 h
至于leetcode,也是大部分题目官方都有给出答案,也是个不错的刷题网站。你们可以两个挑选一个,或者两个都刷。
( Y5 Q$ w! ]; r
9 Z1 k @: @% j# E
当然,还有其他刷题的网站,不过,其他网站没刷过,不大清除如何。
* j+ f" \0 A) g! p* A2 d
* V0 Q5 x% n0 d# Q
至于leetcode,有中文版和英文版
- S) L4 w! u/ h. J6 d
3 m t4 V, S6 @& W8 X' F5 ~
leetcode有中文版
9 D3 O% R% e) T9 h% Z3 p
! Y3 b& c' y5 t4 ]# N$ n
英文版
6 d* }. b( Z5 s( o* r z/ n5 t y
1 I7 T0 P2 ?" m% T! \ T; q8 Z+ X+ ?
根据自己的兴趣选。
! h' j- ~' y4 E, E: X( j
6 u! b5 A' |+ l: x) P3 C& s4 X; W0 ?
学习一些解题技巧
; q) W8 g# w; F: u( B" A
0 c$ o+ u" N8 u( N' t
说实话,有些题在你没看别人的解法前,你好不知道有这么美妙优雅的解法,看了之后,卧槽,居然还可以这样。而我们在刷题的过程中,就要不断累积这些技巧,当你累计多了,你就会形成一种
! J" t, j8 }! b% F
神经反应,一下子就想到了某种方法。解题技巧很多,例如数组下标法、位图法、双指针等等,我自己也分享过一篇总结一些算法技巧的文章
2 x/ N7 A/ A) E' e& Q/ }4 B
4 g& w9 _, G* R2 e
推荐阅读:一些常用的算法技巧总结
% h) h1 q4 B4 [3 J
" u0 r6 v2 J2 y* }4 A
例如在刷题的时候,我们要学会巧用双指针、数组下标法、位运算等等技巧来解决问题,可能会有意想不到的效果。我给你再找点我之前写文章的一些例子吧:
& Z8 ? {9 j4 o- g" t
! ]1 l8 ^) X1 u7 n
分享一道解法巧妙的算法题
. U% h# w0 L# @4 I$ `% V
, {2 @" N: ]* o4 c& j
【算法技巧】位运算装逼指南
8 m$ y- Y4 Y9 J+ Z1 N8 g: ]6 D
; H, ^0 a( P) t4 r- j: q2 q
这是个长期累积的过程,我自己也精彩在我的公众号里分享一些解题的文章,感兴趣的可以关注我的公众号:帅地玩编程。
" a; _3 z4 ` j0 t8 E6 _
~; V" f0 x5 z u7 O# E
再说数据结构发重要性
2 v0 P3 b& r- X. j- Z" P
. c4 m6 j: s8 X" v8 I/ P
前面我主要是说了我平时都是怎么学习算法的。在数据结构方法,我只是列举了你们一定要学习链表和树(二叉堆),但这是最基本的,刷题之前要掌握的,对于数据结构,我列举下一些比较重要的:
+ ?% A6 x& m/ H3 f
! u% U% F2 r4 b( {/ {. p
1、链表(如单向链表、双向链表)。
3 L$ U3 h3 D2 _5 a" `' N) `1 K
/ q4 T) F) L, x3 A, }# w
2、树(如二叉树、平衡树、红黑树)。
! ^: e' l& D5 _
1 {9 S& J8 J# y! j# ~) R
3、图(如最短路径的几种算法)。
/ ?4 K" O; H& s: }' |3 `! c) L' K
+ E2 Y7 ~- p# @
4、队列、栈、矩阵。
( I" D% k8 Y$ d- M K% g& c
. E2 b. h2 M- o' L% k* Y
对于这些,自己一定要动手实现一遍。你可以看书,也可以看视频,新手可以先看视频,不过前期可以看视频,之后我建议是一定要看书。
" L. q6 F" W/ A6 ]
1 x5 u! p; w& K" J
例如对于平衡树,可能你跟着书本的代码实现之后,过阵子你就忘记,不过这不要紧,虽然你忘记了,但是如果你之前用代码实现过,理解过,那么当你再次看到的时候,会很快就记起来,很快就知道思路,而且你的抽象能力等等会在不知不觉中提升起来。之后再学习红黑树啊,什么数据结构啊,都会学的很快。
+ B7 A! s5 E, U, K5 ~0 C4 \
- h% }/ b$ q y! F1 ^7 ^
对于有哪些值得学习的算法,我之前也总结过,这里推荐给大家程序员必须掌握的核心算法有哪些?,这篇文章居然 40多万阅读量了,有点受宠若惊。
c7 T9 X. P$ w" ~6 p
; p7 C9 s0 {' O7 e! V2 ?9 P% b
最最重要
" W( n* q! F6 m% F* q9 K; |
) C: V- `: S7 Q; r B0 y
动手去做,动手去做,动手去做。重要的话说三遍。
5 v( ~; B/ A6 u3 z; {
, a3 C. A" B. x* \% K
千万不要找了一堆资源,订好了学习计划,我要留到某某天就来去做…
7 w( U+ H- F# f$ ~* e
( s5 o- r$ B+ z2 \5 y5 d2 j" I9 \% Z
千万不要这样,而是当你激情来的时候,就马上去干,千万不要留到某个放假日啊什么鬼了,很多这种想法的人,最后会啥也没做的。
: F$ u: ?$ Z' B) p. u0 B
4 k: Q" p n8 ? M6 u2 Q+ q) a# k
也不要觉得要学习的有好多啊,不知道从哪学习起。我上面说了,可以先学习最基本的,然后刷题,刷题是一个需要长期坚持的事情,一年,两年。在刷题的过程中,可以穿插和学习其他数据结构。
! H- s) Q& u) @8 S. ?
l( T" H, a) ^9 ?) }
总结一下吧
* t4 G" n+ S3 L+ r1 B1 X! W
" E" M. F- ?# G9 t% m! \
所以我给大家的建议就是,先学习基本的数据结构以及算法思想,不要盲目刷题,接着刷题的过程中,不能得过且过,尽量追求最优解,还有就是要跳出舒适区,逼自己成长,刷题的过程中,要学会分类总结。
+ e% v" @ \4 \0 t; u. M, W
- K" ?: U: u1 @7 F
当然,最重要的,就是你去动手了,不然,一切免谈!
( s0 ]' e9 U7 O* O: q
- h6 h( e4 q% W+ J
看在熬夜写过的份上,送我个赞呗,嘻嘻。
: n2 B5 r* _) O9 { b' G5 f
————————————————
8 z' H) H4 i7 ?& d
版权声明:本文为CSDN博主「帅地」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
( ]5 a) Z7 k0 E/ X! g
原文链接:https://blog.csdn.net/m0_37907797/article/details/104765116
3 x& S9 y* T) H. L* F8 ~ A/ m1 z) n
' ?4 b! N2 E) m" u! {
5 V: [5 }( p. m% ^
欢迎光临 数学建模社区-数学中国 (http://www.madio.net/)
Powered by Discuz! X2.5