数学建模社区-数学中国

标题: 算法越学越扎心,有没啥破解之法? [打印本页]

作者: 杨利霞    时间: 2020-4-24 17:58
标题: 算法越学越扎心,有没啥破解之法?
5 _/ W3 g0 J; x4 h6 p
算法越学越扎心,有没啥破解之法?) n) Y7 T4 f* e. m$ p0 @
算法越学越扎心,有没啥破解之法?1 M& j8 G: S  K& x! ?: C, k

' Q4 ~' S# Q5 O6 R/ H0 s对于算法的学习,我也是从一个小白一步步走来,当然,现在仍然很菜,,,不过,鉴于我觉得还有一些人比我更菜了,我决定谈谈我算法学习过程走过的坑,以及自己总结的一些经验。% z! A, i& s, E$ n# J1 |- o

. U# P8 g7 k+ W* _, u5 ^切勿盲目刷题:刷题前的知识积累
7 H. u5 D$ I4 Q# h0 m! C3 s0 T' I) B. s: s
说实话,想要提高自己的算法,真的没啥捷径,我觉得最好的捷径就是脚踏实地着多动手去刷题,多刷题。
0 A% {9 H7 ~4 D2 ~: u' A/ a2 W: D% c/ G# A' J' V, ?
但是,我必须提醒的是,如果你是小白,也就是说,你连常见的数据结构,如链表、树以及常见的算法思想,如递归、枚举、动态规划这些都没学过,那么,我不建议你盲目疯狂着去刷题的。而是先去找本书先去学习这些必要的知识,然后再去刷题。
  e3 u0 G, w# j$ n' T. d8 u
! j: T1 f/ `, N因为,如果这些基础都不懂的话,估计一道题做了几个小时,然后看答案都看不懂,做题没有任何思路,这是很难受的。久而久之,估计没啥动力了,我刚开始就是这样,一道题答案看一天,然而还是不大懂,什么回溯啊,暴力啊,还不知道是啥意思。
; b6 O7 h) D" T& y
) ~- s7 j7 E* I6 W/ B也就是说,假如你要去诸如leetcode这些网站刷题,那么,你要先具备一定的基础,这些基础包括:% M! V2 [5 W* }5 H

( D: C* @. y* N4 {9 u1、常见数据结构:链表、树(如二叉树)。(是的,链表和二叉树是重点,图这些可以先放着)
2 M8 ?9 n  b5 Y6 y+ K6 L
% |  _  V' M$ X2、常见算法思想:贪婪法、分治法、穷举法、动态规划,回溯法。(贪婪、穷举、分治是基础,动态规划有难度,可以先放着): }; m6 t+ W9 z; d7 ~, |; A: W

  }/ p' c* R0 Y# d% m. h以上列出来的算是最基本的吧。就是说你刷题之前,要把这些过一遍再去刷题。如果你连这些最基本的都不知道的话,那么你再刷题的过程中,会很难受的,思路也会相对比较少。
4 b1 @, a! D* |/ b
! c( {- R2 f5 v# _总之,千万不要急,先把这些基本的过一遍,力求理解,再去刷题。( ]* `8 Q9 k$ ^5 S% }

4 ~5 h2 q1 R; [; G, R4 E; v在这里,我推荐基本我大一时看过的书籍吧,感觉还是非常不错的,如果对于数据结构时零基础的话,那么我建议你可以看《数据结构与算法分析:C语言描述版》这本书,这本书自认为真的很 nice,当时我把这本书里面的全部都看了,并且 coding 了一遍,感觉整个人有了质的飞跃。
1 J! \2 Q$ m+ t: r
/ e- a& p4 p8 L3 U* a6 r后面我时在一些学校的OJ刷题,当时看的一本书叫做《挑战程序设计大赛》,日本作家写的,我觉得这本书也很nice,里面有分初级,中级和高级三个模块,基础比较差的可以从初级开始看起。( K0 g9 z3 n6 @5 E4 w$ r
5 U2 Q4 m0 M8 y1 }
当然,这两本书,你可以在这个Github上找到:https://github.com/iamshuaidi/CS-Book2 x6 ^, x& c, E3 l
总结下:0 F2 }' Y& ~! \, f- E
7 q: E1 h9 W  I
提高数据结构与算法没啥捷径,最好的捷径就是多刷题。但是,刷题的前提是你要先学会一些基本的数据结构与算法思想。, U4 I- A6 O$ b9 |' |. w6 T2 m3 T

8 H* q$ T) @) a7 i: c0 Q* Q9 sAC不是目的,我们要追求完美
2 r; B0 e8 n- Y9 z) Y) h% U  l: i4 s6 T; E2 |
如何刷题?如何对待一道算法题?- D0 ^  t2 l+ `8 m
  ?" Z; d+ w7 M
我觉得,在做题的时候,一定要追求完美,千万不要把一道题做出来之后,提交通过,然后就赶紧下一道。我认为这意义不大,因为一道题的解法太多了,有些解法态粗糙了,我们应该要寻找最优的方法。
# k! l* t/ [7 s( E! h
+ F" D" @5 M# s) u% ?; M! Q6 a  w算法能力的提升和做题的数量是有一定的关系,但并不是线性关系。也就是说,在做题的时候,要力求一题多解,如果自己实在想不出来其他办法了,可以去看看别人是怎么做的,千万不要觉得模仿别人的做法是件丢人的事。
0 U4 ]9 m/ y- U& `
' E  Q) |- u+ M; ?我做题的时候,我一看到一道题,可能第一想法就是用很粗糙的方式做,因为很多题采用暴力法都会很容易做,就是时间复杂度很高。之后,我就会慢慢思考,看看有没其他方法来降低时间复杂度或空间复杂度。最后,我会去看一下别人的做法,当然,并不是每道题都会这样执行。
  A% A$ o. r0 X! k4 ^* y; y9 _1 h
8 Q3 t; q2 f" u5 _4 F3 H9 I- [衡量一道算法题的好坏无非就是时间复杂度和空间复杂度,所以我们要力求完美,就要把这两个降到最低,令他们相辅相成。1 G9 P5 N+ B8 N5 ~
0 R7 |5 }- N& j! V! o+ [
我举道例题吧:
  D& D& A6 {" s% M6 H$ g
+ ^7 N' ^% {6 g4 g2 O9 N, j1 N问题: 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法?" P$ w6 s; D; z% M% Q

! H( k* Z. B3 v! e6 p4 @这道题我在以前的分章分析过,不懂的可以先看下之前写的:递归与动态规划—基础篇1! O0 N) C6 t' K% u5 M- t- M* B

% m9 O& _; ?! y+ y' K7 l" A% s方法1::暴力递归8 }+ f9 \! K5 H' E* y1 M

0 ?4 H) ~! o; |这道题不难,或许你会采取下面的做法:
: C9 w- L) F0 u; @" _( N5 h# O
- l* p9 F! s; k5 C" ]* Xpublic int solve(int n){
$ O. A6 b  q3 ]0 }1 c" F2 C+ n    if(n <= 2){$ _+ o9 m5 {# |& q" E/ X
        return n;
8 S/ F6 W, N  |2 D7 N; ~, |: \    }else{% c& m5 T( N! `0 D# S' C
        return solve(n-1) + solve(n-2);
) j$ ?, @7 m  R  C6 z9 S0 L  A    }7 j' g) Z! H# L) m. R
}. q" c5 E) e9 y' l, Q" T! t) o
18 r5 x/ F) o& E: K  S/ f: R
27 `$ w& ~/ i8 P7 d5 ]' ?4 W! F
3
2 f$ j) _/ G6 f. q# D4
- t* V; s+ O: C59 \6 x7 p6 }5 G" [5 g
6
5 k  w3 C9 @  o6 ^( Y. k' j6 J7
( w: ?" |0 J8 C3 m  w! j这种做法的时间复杂度很高,指数级别了。但是如果你提交之后侥幸通过了,然后你就接着下一道题了,那么你就要好好想想了。
2 E/ Y7 r' n7 G7 c' v$ S! [
% F2 u+ L2 B3 O2 g. \3 u方法二:空间换时间
0 }5 j. d: |0 k. s- K' {+ I  e" s; h) }8 j3 n- S1 i9 m
力求完美,我们可以考虑用空间换时间:这道题如何你去仔细想一想,会发现有很多是重复执行了。不行你可以画个图5 A  w% V$ J# n5 S
. S) N  l7 x6 s* A* Y
所以可以采取下面的方法:& O2 f  G1 a2 u# w
. {* B* h- L- a
//用一个HashMap来保存已经计算过的状态
! J5 h$ C1 x8 J0 t' \$ Fstatic Map<Integer,Integer> map = new HashMap();  X" A9 q) L0 b) M# P( X. u9 H
public static int solve(int n){; E* |1 I* r+ R% U' K. g
         if(n <= 2){
' l! Q" F$ q+ P        return n;
& [5 T4 P* t7 R5 n- S    }else{//是否计算过
* d: s2 x) i' K' ~3 s4 {        if(map.containsKey(n)){
- E& q" t' w" N9 y  u9 a            return map.get(n);, u0 K/ a1 [. ], S+ u7 k
        }else{
" c, [5 ?+ L, F- o2 E7 H! b& {            int m = solve(n-1) + solve(n-2);
& g: D2 A2 f. b8 }8 Z/ i0 ~            map.put(n, m);* X' }1 ~7 \1 y4 ~6 h. {# T) b
            return m;7 f3 A. B, i0 h" S; A* Y
        }6 a' `  `/ ?, k5 E& b7 j
    }
' l) Q  G7 |6 E0 x) V}
1 C- U+ A3 t' ]( P. i9 I/ o- G$ {% L4 u: s! S; v: C' x2 Z
1; a1 R( r, o! t+ |
24 {4 x5 `! [0 u  G; }$ P$ \
3* Y: \  }- E) z6 S( ^; O6 x
4
1 \0 h1 Z0 b3 K0 m1 P+ b. R1 P5
( |9 q2 g1 h; U( j6 i! P3 `65 f8 x$ y# K# }* I: g
7# X3 }* E" ~& r0 ]6 ?- C0 j2 K, |
8$ ~4 m9 L) T2 j$ ~' f0 Q
9
2 h$ k, E6 j- l109 K+ P4 Z7 t" u0 S# w  |. B- A6 v
11
: Y1 c( S* _! [; b& @5 T12
5 x$ a2 I# y$ z. `# d13
. D$ \' V, h! ?- Z" z  J3 P* C14, K* |/ \$ N- y( J, c6 ~: |' H& c
15
: h" A/ C: k* Z4 R# |0 @16
' R" _3 S+ v, f( }4 ~* }这样,可以大大缩短时间。也就是说,当一道题你做了之后,发现时间复杂度很高,那么可以考虑下,是否有更好的方法,是否可以用空间换时间。
, {" }' _4 Q" h/ L5 j* O4 P% K- R: g3 Y2 \1 W- t
方法三:斐波那契数列7 I( }/ W, T) j5 _* y
- u7 X: E7 h- O; e3 g0 p1 W
实际上,我们可以把空间复杂度弄的更小,不需要HashMap来保存状态:5 m2 F& C4 V) H1 I! x* E
" y7 S  j! c  w
public static int solve(int n){
2 o+ I7 o7 ?0 m" X! d% f4 w: ?5 `    if(n <= 2){
9 J: k. P2 a, t/ ^( l2 Y        return n;
# q% `3 `! N% i; P% j; v    }
* Z5 e5 E1 N' G2 x+ L+ m0 Z/ o    int f1 = 0;
" `, u, Q0 y0 W6 C, t' E    int f2 = 1;  E- w# p3 b  T& ]" x$ {
    int sum = 0;, F. W/ n. p1 t7 A8 U4 ?; |
    for(int i = 1; i<= n; i++){
' W9 R" L9 x" j. _3 H" j' x        sum = f1 + f2;
" N0 l+ D1 E5 W5 f! d4 h- N4 |% P        f1 = f2;
  F% i6 M" o: _, i        f2 = sum;
7 O. }# O" ]9 O    }  v; E1 N2 P# F; a; R
    return sum;
% `6 z7 f6 Y3 `}( M+ G6 }: ]; @8 w& i
1$ \% X. ]! W7 m$ i8 `1 L5 U
2! Q4 J! ?) n, O' F' y
3& @, [3 Y  m$ `. C; r% M
4- V8 y5 i/ o! ^. q
5% ^( i  }8 }8 T% e
6, P) t) c/ G9 p& {- N
75 E" o1 Z* \6 q$ Y" U4 n9 i. _3 T
84 t( Y' T! y% D- o, @! l6 _* G' i
9
6 u! F' `  }3 m0 M! w1 N10% O8 v* q/ G2 d" g! E% @5 Q0 j+ D
11
% x8 p# F+ R; ?  F* \12
/ `- R) D4 f' [+ b# Q- e: p13+ s. d* X5 }2 d: y
14
7 g5 W2 U8 y7 j  J我弄这道题给你们看,并不是在教你们这道题怎么做,而是有以下目的:% j! `) u+ u8 v, \/ ?  ^  H

" t, @# f/ H4 f! l2 B" }1、在刷题的时候,我们要力求完美。8 ~5 @8 M4 t7 z$ j

. D0 c5 P4 b. a1 i( d2、我想不到这些方法啊,怎么办?那么你就可以去看别人的做法,之后,遇到类似的题,你就会更有思路,更知道往哪个方向想。  l% v( A+ A: {
( X/ X& o8 J5 A, \: P: ^; b/ Z; d9 l, x
3、可以从简单暴力入手做一道题,在考虑空间与时间之间的衡量,一点点去优化。
! u; ]$ w& K  c
  t. d7 `4 Z; U" M) G挑战自己,跳出舒适区& `/ O1 N; a% s; U4 `: n4 t
  H/ O+ T0 \2 h/ m0 |+ s
什么叫舒适区?在刷题的时候,可能有一类题是你比较懂的,你每次一看就有思路,然后半个小时就撸好代码,提交代码,然后通过了,然后,哇,又多刷了一道题,心里很舒服。4 J  \' n1 D$ M# S

7 X! x) J6 c9 [  x) R2 k但是,记住,前期你可以多刷这种题练手,提升自己的乐趣,但,我还是建议你慢慢跳出舒适区,去做一些自己不擅长的题,并且找段时间一直刷这种题。例如,我觉得我在递归方面的题还是挺强的,# A( _6 |8 s  `3 o! f' N+ ^4 m
但是,我对动态规划的题,很菜,每次都要想好久,每次遇到这种题都有点害怕,没什么信心。不过有段时间我觉得只刷动态规划的题,直接在 leetcode 选定专题,连续做了四五十道,刚开始很难受,后来就慢慢知道了套路了,一道题从两三个小时最后缩到半小时,简单的十几分钟就搞定。感觉自己对这类型的题也不惧怕的。
6 d* y" @3 s0 A: b& O/ z
; n' s6 o, i7 ^9 {; g  \当然,对于动态规划的学习,大家也可以看我这篇广受好评的文章:为什么你学不过动态规划?告别动态规划,谈谈我的经验
" A- L: |; i0 i* p* \' g6 s& A( Y2 R3 H0 \; Z5 B% U( D
所以,建议你,一定要学好跳出自己的舒适区。7 B' \3 d- e5 C" C2 n
+ w/ K4 F8 N& v
一定要学会分类总结, I& P! u8 j4 F/ H# A8 e9 }* m+ m( E2 u( I
; q: n, [' U$ x  B$ ]8 N' h1 e
有些人以为 leetcode 的题刷的越多,就一定能越厉害,其实不然,leetcode 虽然有 1000 多道题,但题型就那么几类,我们前期在刷的时候,我是建议按照题型分类刷题的,例如我这整理刷二叉树相关,然后刷链表相关,然后二分法,然后递归等等,每刷一种题型,都要研究他们的套路,如果你愿意去总结,那么 leetcode 的题,其实你刷几百道,有目的、挑选的刷,我觉得就差不多了。
& e( |9 k" F* c2 u5 L5 _
5 W# Y1 b* q2 q; o7 m; W我看过一本书,叫做《程序员代码面试指南:IT 名企算法与数据结构题目最优解》,这本书就非常不错,里面按照栈,队列,链表,二叉树,字符串等一个专题一个专题来刷的,并且每道题都给出了最优解,而且里面的题有一定的难度,感兴趣的,真心不错,如果你把这本书的题全部搞定,并且总结相关套路,那么你的算法一定有很大的提升。
2 c5 r) w; H9 ^& h$ k8 J1 v
7 ]2 U# B( O' @# {, `推荐一些刷题网站$ V9 \" s9 ]6 Q
9 E) U6 n* d2 H
我一般是在leetcode和牛客网刷题,感觉挺不错,题目难度不是很大。
" L) {5 ]% _  T, q* [
' Z1 `, R) f6 B5 F- V在牛客网那里,我主要刷剑指Offer,不过那里也有个在线刷leetcode,不过里面的题量比较少。牛客网刷题有个非常方便的地方就是有个讨论区,那里会有很多大佬分享他们的解题方法,不用我们去百度找题解。所以你做完后,实在想不出,可以很方便着去看别人是怎么做的。
- f# o9 t4 u; S# }+ B) l% ]' p3 v$ D
至于leetcode,也是大部分题目官方都有给出答案,也是个不错的刷题网站。你们可以两个挑选一个,或者两个都刷。7 C8 u" U0 _; X" p  V
; \0 U" p' l2 N$ W
当然,还有其他刷题的网站,不过,其他网站没刷过,不大清除如何。% b4 M+ h# P8 N
: ?, G4 X8 P7 j' |
至于leetcode,有中文版和英文版
! u" L7 i* I% U9 Z  B7 n' V2 R/ J" L0 U
leetcode有中文版2 M& C" O  l. b  |

% Q5 i" A7 [' h, e8 S; E英文版
2 x$ L9 v3 z% ]
4 z2 E. D; t! s- K" r根据自己的兴趣选。& D% S3 m4 ]; `, P
% l6 M+ S$ K' s9 e5 M$ Z- K+ s
学习一些解题技巧
) O8 H; b% W. G& A  B/ ^' R: \2 J& P6 U/ K! r
说实话,有些题在你没看别人的解法前,你好不知道有这么美妙优雅的解法,看了之后,卧槽,居然还可以这样。而我们在刷题的过程中,就要不断累积这些技巧,当你累计多了,你就会形成一种  _+ I& ?6 `! f
神经反应,一下子就想到了某种方法。解题技巧很多,例如数组下标法、位图法、双指针等等,我自己也分享过一篇总结一些算法技巧的文章  x" D' m9 ~  g9 K  {) o& {
2 N8 w2 {. O: S/ N% T$ k; x5 Q
推荐阅读:一些常用的算法技巧总结$ c% P1 M- m; ~

( N& S" J7 _! L9 q例如在刷题的时候,我们要学会巧用双指针、数组下标法、位运算等等技巧来解决问题,可能会有意想不到的效果。我给你再找点我之前写文章的一些例子吧:# i, q% f* B. a

1 p6 L9 m* h, `) T5 `分享一道解法巧妙的算法题
+ n" ^& S3 g0 J3 Q: W) }3 @0 v
3 R5 d& t. i) P1 |' Q8 y: {  @, w! b【算法技巧】位运算装逼指南  Y) C: H7 e8 F
7 f7 ^' j6 Z& I, m( Y# ^0 _' O
这是个长期累积的过程,我自己也精彩在我的公众号里分享一些解题的文章,感兴趣的可以关注我的公众号:帅地玩编程。3 i# b3 `# V- o3 F
" L5 s6 ^4 d2 z0 f5 I
再说数据结构发重要性. g' x$ O0 T  t

7 p; _, s& ]3 V' ^. a$ e前面我主要是说了我平时都是怎么学习算法的。在数据结构方法,我只是列举了你们一定要学习链表和树(二叉堆),但这是最基本的,刷题之前要掌握的,对于数据结构,我列举下一些比较重要的:2 x6 s" }5 v) |8 z

2 @; }* K9 z, H) j& N9 T1、链表(如单向链表、双向链表)。9 L' ]' P) P! N& m

/ ?5 q; Y5 [0 l2、树(如二叉树、平衡树、红黑树)。
) _* ^0 ?6 r7 f4 O; F6 ~6 p: z1 W) O' i6 ^7 s
3、图(如最短路径的几种算法)。
) T( l, |: O. f" E
9 h- Z5 z. j' _3 g4、队列、栈、矩阵。7 x' h0 Q1 o& @
& t9 I' |4 p( g# T4 s; w
对于这些,自己一定要动手实现一遍。你可以看书,也可以看视频,新手可以先看视频,不过前期可以看视频,之后我建议是一定要看书。
! m) Z( d1 K; N8 R* G- j. [3 s+ [, a9 c
例如对于平衡树,可能你跟着书本的代码实现之后,过阵子你就忘记,不过这不要紧,虽然你忘记了,但是如果你之前用代码实现过,理解过,那么当你再次看到的时候,会很快就记起来,很快就知道思路,而且你的抽象能力等等会在不知不觉中提升起来。之后再学习红黑树啊,什么数据结构啊,都会学的很快。5 t/ y' i; j& w0 l
, p# D% ^, k7 d; o
对于有哪些值得学习的算法,我之前也总结过,这里推荐给大家程序员必须掌握的核心算法有哪些?,这篇文章居然 40多万阅读量了,有点受宠若惊。7 E3 N2 j" v+ K. z1 C3 n

7 l1 |) H) j8 l4 V$ E1 B) l最最重要9 @& @: X! k# W+ U

9 S. p! M6 R) l3 ?" t6 ~2 b动手去做,动手去做,动手去做。重要的话说三遍。5 P9 s" s; P! y2 n5 q
; Y7 S5 j- W$ L- V' I; v
千万不要找了一堆资源,订好了学习计划,我要留到某某天就来去做…* E1 M( S3 a1 n% l. _
- s0 D' T2 A% B& z. j+ s
千万不要这样,而是当你激情来的时候,就马上去干,千万不要留到某个放假日啊什么鬼了,很多这种想法的人,最后会啥也没做的。
- t8 a- H- E0 g' j; H' t1 b
: T- ?/ W% X7 x+ R' ]# ]$ Q8 Q( ?也不要觉得要学习的有好多啊,不知道从哪学习起。我上面说了,可以先学习最基本的,然后刷题,刷题是一个需要长期坚持的事情,一年,两年。在刷题的过程中,可以穿插和学习其他数据结构。3 d5 ~4 {- J3 a$ k

' Y. F3 Y: ~- s' x$ s& A总结一下吧
# B5 V* _! O% y5 M
1 t/ N1 r4 K% v, M/ T1 e' w/ Q所以我给大家的建议就是,先学习基本的数据结构以及算法思想,不要盲目刷题,接着刷题的过程中,不能得过且过,尽量追求最优解,还有就是要跳出舒适区,逼自己成长,刷题的过程中,要学会分类总结。
4 {" M$ a1 i; |& x2 Z- l/ ?" `5 j* _. q5 a  ?( Z$ E
当然,最重要的,就是你去动手了,不然,一切免谈!
# p* @. J( r0 e! H3 G; S+ [! D/ I( I9 e: `1 {& _! B, x0 g7 n
看在熬夜写过的份上,送我个赞呗,嘻嘻。
! O% K, r8 I* v4 n9 V/ h; g————————————————
9 l$ T& D8 C+ {# s版权声明:本文为CSDN博主「帅地」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
& ]- R/ Z& t" s, M5 r" A原文链接:https://blog.csdn.net/m0_37907797/article/details/104765116
+ q# y' u2 E' y% I, q( O7 ~! O1 Y" K+ g& C0 N
; G1 s2 {* a/ Y- i0 C3 L





欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5