数学建模社区-数学中国

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

作者: 杨利霞    时间: 2020-4-24 17:58
标题: 算法越学越扎心,有没啥破解之法?
3 D5 m2 q7 M4 O  @  U% c' s' {
算法越学越扎心,有没啥破解之法?
" A8 G7 s! D& V$ }1 L, T7 L$ x算法越学越扎心,有没啥破解之法?# v7 h, y/ Z6 A
. n1 X  x3 V! d
对于算法的学习,我也是从一个小白一步步走来,当然,现在仍然很菜,,,不过,鉴于我觉得还有一些人比我更菜了,我决定谈谈我算法学习过程走过的坑,以及自己总结的一些经验。
. |8 Q2 |4 I3 }( ^- v  S% @; e& h6 M" M' O+ L3 R5 F
切勿盲目刷题:刷题前的知识积累
% @  u/ y6 e5 P. ~4 H$ a# N) K
# L5 r5 V% C0 ]% W说实话,想要提高自己的算法,真的没啥捷径,我觉得最好的捷径就是脚踏实地着多动手去刷题,多刷题。* B# H( t- Z1 e, G! O! \7 f
: T: r9 ]. d" J& u# ]( G( G
但是,我必须提醒的是,如果你是小白,也就是说,你连常见的数据结构,如链表、树以及常见的算法思想,如递归、枚举、动态规划这些都没学过,那么,我不建议你盲目疯狂着去刷题的。而是先去找本书先去学习这些必要的知识,然后再去刷题。/ x" S) j8 d0 r- n$ s: ]

; Y- C- n- B! z6 |因为,如果这些基础都不懂的话,估计一道题做了几个小时,然后看答案都看不懂,做题没有任何思路,这是很难受的。久而久之,估计没啥动力了,我刚开始就是这样,一道题答案看一天,然而还是不大懂,什么回溯啊,暴力啊,还不知道是啥意思。; N; [% \% F0 m# F

0 B; z1 M3 }+ o, E也就是说,假如你要去诸如leetcode这些网站刷题,那么,你要先具备一定的基础,这些基础包括:
( g7 \- ]+ p3 h% Z, _! W4 G$ n* x
2 f/ k9 _+ f3 v$ x) T! a1、常见数据结构:链表、树(如二叉树)。(是的,链表和二叉树是重点,图这些可以先放着)5 s4 P; z# U8 K$ V- \
" }+ B( u: L( p
2、常见算法思想:贪婪法、分治法、穷举法、动态规划,回溯法。(贪婪、穷举、分治是基础,动态规划有难度,可以先放着)
8 A0 E. x- g+ H, X# Z/ z) j3 T! }7 p# c. w
以上列出来的算是最基本的吧。就是说你刷题之前,要把这些过一遍再去刷题。如果你连这些最基本的都不知道的话,那么你再刷题的过程中,会很难受的,思路也会相对比较少。8 T# e0 F# b- W* r

0 ?( O' k6 c1 t5 e# a总之,千万不要急,先把这些基本的过一遍,力求理解,再去刷题。
! A6 O; J3 t' ]- Z7 i- H1 H5 ~% E! ]! N2 v$ [
在这里,我推荐基本我大一时看过的书籍吧,感觉还是非常不错的,如果对于数据结构时零基础的话,那么我建议你可以看《数据结构与算法分析:C语言描述版》这本书,这本书自认为真的很 nice,当时我把这本书里面的全部都看了,并且 coding 了一遍,感觉整个人有了质的飞跃。
3 F% c- S- [* {: [1 X$ C
# y. K+ [3 {& r5 T$ }后面我时在一些学校的OJ刷题,当时看的一本书叫做《挑战程序设计大赛》,日本作家写的,我觉得这本书也很nice,里面有分初级,中级和高级三个模块,基础比较差的可以从初级开始看起。
5 \1 [. V1 P! l
& T6 J0 ]: [; N当然,这两本书,你可以在这个Github上找到:https://github.com/iamshuaidi/CS-Book  v( D/ V. q, u0 Y
总结下:' {  ~- O& P# N  k6 \
. z& W  d7 S5 T; j0 j& l6 s
提高数据结构与算法没啥捷径,最好的捷径就是多刷题。但是,刷题的前提是你要先学会一些基本的数据结构与算法思想。
) S: i/ Y( U* `' k2 C2 R) J% O
; V, i( w4 a1 {$ SAC不是目的,我们要追求完美! `7 d3 n4 F6 I& p7 t1 K' i# F2 s, y

9 x, d: {0 V( Y: |8 j  l如何刷题?如何对待一道算法题?) l* B. D" x' l

- Y$ E* w; x9 |2 F我觉得,在做题的时候,一定要追求完美,千万不要把一道题做出来之后,提交通过,然后就赶紧下一道。我认为这意义不大,因为一道题的解法太多了,有些解法态粗糙了,我们应该要寻找最优的方法。
5 a8 P, m8 g( W
9 t8 i7 I2 Z. E. d* E2 }% @算法能力的提升和做题的数量是有一定的关系,但并不是线性关系。也就是说,在做题的时候,要力求一题多解,如果自己实在想不出来其他办法了,可以去看看别人是怎么做的,千万不要觉得模仿别人的做法是件丢人的事。2 C* i# i" n" B2 k) U2 x4 ]3 k9 i

* w0 F8 D) H4 [' C& l4 \; k7 R3 x我做题的时候,我一看到一道题,可能第一想法就是用很粗糙的方式做,因为很多题采用暴力法都会很容易做,就是时间复杂度很高。之后,我就会慢慢思考,看看有没其他方法来降低时间复杂度或空间复杂度。最后,我会去看一下别人的做法,当然,并不是每道题都会这样执行。1 x5 ]' P* Q2 T5 `* O. o
( D4 C3 A+ m$ W) f
衡量一道算法题的好坏无非就是时间复杂度和空间复杂度,所以我们要力求完美,就要把这两个降到最低,令他们相辅相成。& v- Z6 s5 T2 \$ l

/ k' q; I( X1 x, Q- u  Y7 Q* r我举道例题吧:$ z9 O% y6 |" W# _& T+ U
1 E/ ?) t7 B) R6 q6 \
问题: 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法?
$ Y  Q2 w$ Q/ P1 o
5 V3 j) |! v" A# }这道题我在以前的分章分析过,不懂的可以先看下之前写的:递归与动态规划—基础篇1% t6 N% M( h6 Y) ~3 W; d8 l* C7 B

7 C( s( N1 Q3 N7 M7 x' C方法1::暴力递归4 e! m# @( f" Y
: B4 Y) P( {+ R7 F+ A% h
这道题不难,或许你会采取下面的做法:. l$ L" v0 ?: C$ k1 h( y. _2 ?
, Q- _0 L; O) B4 ^0 g
public int solve(int n){
7 n" J/ Y7 a9 R/ i    if(n <= 2){/ G. t* g- T5 n& K! v: O1 `) s1 e8 M
        return n;# [8 b) G5 ]3 {7 J6 b! o
    }else{" T( Z0 z( H3 @1 t( N" _
        return solve(n-1) + solve(n-2);% b4 I0 h& n0 Z. J" K6 S
    }2 k$ K+ X& d" G& N/ ~0 I8 z& J2 R0 Y
}
5 q3 }- a1 S' v+ _0 g; z( {' A1  _* M) g3 ^7 _3 z# z' o
2
5 w0 e0 T2 E" \. e  s- H& n: \5 t3
$ x& o* x8 S/ l4
3 I/ |, ^% [$ G' i# W5
' A9 g& X3 ?5 l7 w7 O6
  f3 N) E8 B/ ~% i4 Y, O7
' v! T3 c! P; `这种做法的时间复杂度很高,指数级别了。但是如果你提交之后侥幸通过了,然后你就接着下一道题了,那么你就要好好想想了。3 i' ^7 f$ l. I" V% I
: F$ }- {2 v& x  ~0 B' d1 X
方法二:空间换时间
# a! G1 l6 O. W8 R- D. n1 _
  k9 V, m7 i$ s0 D6 G* J2 |! |力求完美,我们可以考虑用空间换时间:这道题如何你去仔细想一想,会发现有很多是重复执行了。不行你可以画个图7 E3 ]" g: {) g9 G' ^

7 Y$ U, d# k- j3 l/ s3 w& |5 j所以可以采取下面的方法:
/ z( l/ A0 V" e, `! _/ T0 t2 {
( ]! x5 A- m3 n. {; {//用一个HashMap来保存已经计算过的状态
) |, @$ S4 w# ostatic Map<Integer,Integer> map = new HashMap();
5 l3 B1 b- b5 L+ f8 L3 ?+ \public static int solve(int n){# V, c- Y4 f7 u0 h
         if(n <= 2){
7 A/ r( R- X0 n: y9 A        return n;
0 D1 \. T$ z6 G) P/ [) H. m    }else{//是否计算过
, `8 c, k; [8 k        if(map.containsKey(n)){7 Q! t  i% [% F& p% k' S3 Y4 H; _
            return map.get(n);
2 ~6 U3 Q4 T, s: I+ h( P        }else{  u2 a. a: ~6 Y, ^5 s; ]3 U
            int m = solve(n-1) + solve(n-2);
, Y8 h4 Y$ Z- l8 I, w            map.put(n, m);' O  B7 Z. r3 s
            return m;9 f/ E) [3 N; o% ~; ?
        }
$ s- [) R$ `% S; S! E/ @& |    }
: B" T5 \- u( z}
; j7 P) e; k8 M- o( X
5 f( f6 b/ g& d1
, z  T# ^: m) j3 {/ X# ^& M/ i$ x" z2
1 d$ [! d, `, ?9 V3% j4 v+ O/ k2 ^) h
41 \- O) N& Z. T) c6 S/ A
5/ J8 X4 O6 r  `
6
! j. R# q) {+ J% W$ y/ |73 \% @8 Z2 x. R9 z6 C4 {
8# q* v, ]6 V4 {
9
8 L, K+ g/ V8 T# a10
* g# t" I- l: t11
7 @. }9 g4 c* k12
% [; p. V: a, J, h- o9 @13, t* Q) z! |4 f; `. k8 N
14# U# q3 p& s0 G
15
2 b3 s) b* p9 Y9 \9 F16/ }0 r* W' e' ^
这样,可以大大缩短时间。也就是说,当一道题你做了之后,发现时间复杂度很高,那么可以考虑下,是否有更好的方法,是否可以用空间换时间。- T' g, D& F& f: v  O; d
6 E) G4 {9 A0 M) r  X! W. Y
方法三:斐波那契数列
! j; l6 D; \9 F, |" w* ~' ]0 g1 q/ e+ H
实际上,我们可以把空间复杂度弄的更小,不需要HashMap来保存状态:
! O3 ]: z  s' s0 ]
* p+ d  O0 y* _8 ^+ _. [4 A& wpublic static int solve(int n){
/ a* k2 g6 l7 X2 |    if(n <= 2){) K8 b5 H7 D8 d5 X' Y# d$ }
        return n;% j4 m6 S, ?5 y  B
    }
3 G0 X+ [+ B1 f' S/ a; j% p    int f1 = 0;
8 ~( L7 P. h- S7 `" d2 l: m    int f2 = 1;8 J7 \8 S  _( V# T! s
    int sum = 0;
4 X9 a" u( b7 x( V    for(int i = 1; i<= n; i++){
' m- N/ N# @) V        sum = f1 + f2;! Y9 w* r8 Z+ o' _% Q; ^
        f1 = f2;
: D7 b9 P, G& {9 |+ T        f2 = sum;4 w: M4 T" Y. ?* a/ a
    }$ L. w. L! q8 R
    return sum;2 r6 ~' A7 S  m; b2 v6 W  ]  p% q
}: r/ T7 {9 T7 `2 g& Q: y# _0 h
13 z# L- C: I$ b( m; N8 s. q
2% a" R  _: q1 y0 [
3& Z- K7 P3 |3 y( q9 x: \2 T5 h0 d
4% n% |5 Q+ `) V. e1 R3 M6 h) p
5
7 A& U$ S  Y/ h' \$ ~( W6
8 D  B4 X- X  A! t8 S/ W0 {7
5 R& q, ]& \7 k! {$ ?8
5 Z5 y. m% J% u* \9 n9
2 E6 z, b% M7 T+ a, c- E10
) [5 ?/ e3 u  }% t; T4 j' U11
( T% A0 y( t- y, u12& O8 ~. j/ w. q. V8 v
13$ P" O0 Y' k/ G; l" U
14, l! B$ U3 |. ?: |
我弄这道题给你们看,并不是在教你们这道题怎么做,而是有以下目的:
/ Q8 L; l* W2 w9 N1 f9 x! Q2 p- ]6 Z8 N! a+ I
1、在刷题的时候,我们要力求完美。
7 H4 G4 k& x6 Q4 h
7 S3 ]3 s9 K4 c* V, H9 v% u; g2、我想不到这些方法啊,怎么办?那么你就可以去看别人的做法,之后,遇到类似的题,你就会更有思路,更知道往哪个方向想。
+ D. a4 I* Y. {1 n
, `) n- p4 o' }2 c2 w1 y3、可以从简单暴力入手做一道题,在考虑空间与时间之间的衡量,一点点去优化。9 J: `: }( [6 ~9 c# Y8 @4 a9 Z1 C( }! ~

. F: ?  q+ t6 J. A! n: j% _挑战自己,跳出舒适区0 {+ Y4 K. @6 ~
2 c2 h' U1 ]( z8 ~
什么叫舒适区?在刷题的时候,可能有一类题是你比较懂的,你每次一看就有思路,然后半个小时就撸好代码,提交代码,然后通过了,然后,哇,又多刷了一道题,心里很舒服。
4 G0 B3 Z% E# U2 S  ]. g! _/ t0 l9 w  B1 l0 X# d6 v
但是,记住,前期你可以多刷这种题练手,提升自己的乐趣,但,我还是建议你慢慢跳出舒适区,去做一些自己不擅长的题,并且找段时间一直刷这种题。例如,我觉得我在递归方面的题还是挺强的,
5 p) v" w  i: p1 Y8 Z但是,我对动态规划的题,很菜,每次都要想好久,每次遇到这种题都有点害怕,没什么信心。不过有段时间我觉得只刷动态规划的题,直接在 leetcode 选定专题,连续做了四五十道,刚开始很难受,后来就慢慢知道了套路了,一道题从两三个小时最后缩到半小时,简单的十几分钟就搞定。感觉自己对这类型的题也不惧怕的。6 Q& A/ Y& h/ U7 i

0 n" T8 a0 W* y3 u* c& T6 r; v  l! j当然,对于动态规划的学习,大家也可以看我这篇广受好评的文章:为什么你学不过动态规划?告别动态规划,谈谈我的经验3 X7 ?* q5 k! [8 y( p
6 k% U$ G4 B4 `; e7 z! M
所以,建议你,一定要学好跳出自己的舒适区。
/ o  b) d% r- f4 D" H1 o' M7 i1 }( n/ i% b6 Q. {
一定要学会分类总结2 L' z, j' B0 a' w; N0 X1 }

+ L* K9 p0 |9 K" z" M7 r' j" k有些人以为 leetcode 的题刷的越多,就一定能越厉害,其实不然,leetcode 虽然有 1000 多道题,但题型就那么几类,我们前期在刷的时候,我是建议按照题型分类刷题的,例如我这整理刷二叉树相关,然后刷链表相关,然后二分法,然后递归等等,每刷一种题型,都要研究他们的套路,如果你愿意去总结,那么 leetcode 的题,其实你刷几百道,有目的、挑选的刷,我觉得就差不多了。. t. O: P* t- q
$ {1 C3 Y$ c0 O6 n% _4 ]% t3 I
我看过一本书,叫做《程序员代码面试指南:IT 名企算法与数据结构题目最优解》,这本书就非常不错,里面按照栈,队列,链表,二叉树,字符串等一个专题一个专题来刷的,并且每道题都给出了最优解,而且里面的题有一定的难度,感兴趣的,真心不错,如果你把这本书的题全部搞定,并且总结相关套路,那么你的算法一定有很大的提升。
2 E# Y/ D! q: b% y7 Y! @$ ^$ N" z6 ~
# @, a6 x) c$ A3 q( v4 l( B+ s推荐一些刷题网站
. Y9 N' @, j; }5 c$ }! ^2 k6 h1 \2 e7 c/ F7 ^7 u2 k+ I
我一般是在leetcode和牛客网刷题,感觉挺不错,题目难度不是很大。8 w* M) d! u1 }

' q* o: M/ Y0 S8 q, U! y5 \6 \: C在牛客网那里,我主要刷剑指Offer,不过那里也有个在线刷leetcode,不过里面的题量比较少。牛客网刷题有个非常方便的地方就是有个讨论区,那里会有很多大佬分享他们的解题方法,不用我们去百度找题解。所以你做完后,实在想不出,可以很方便着去看别人是怎么做的。6 u+ r1 j0 K" p$ H; o7 c1 q! Z4 o, v
4 b" v; z& g( \5 t: k  ^
至于leetcode,也是大部分题目官方都有给出答案,也是个不错的刷题网站。你们可以两个挑选一个,或者两个都刷。. T4 e4 X# u) v
' s* C5 e1 w+ H, j1 h4 p
当然,还有其他刷题的网站,不过,其他网站没刷过,不大清除如何。
1 j. m1 W$ M9 m7 y5 v" T. l7 p, `0 n% X- F
至于leetcode,有中文版和英文版
5 M5 Q) _. N' |5 H0 T* o  h1 \4 Y; p% A& f5 F0 V( F
leetcode有中文版
$ B% S4 a( ]  f: ~# ~
, K' a/ w8 g+ z; X' Z: B( G- Y英文版
  y* n# U, L8 R; l  ~: t* Q0 w& s2 Y$ o; F7 ?
根据自己的兴趣选。
: a. O+ p' j# T  ^0 \* N, W% }+ v8 F% p! k- X
学习一些解题技巧
0 V6 @7 _  F4 d- E7 Q  d0 p6 U# M& ~1 p# m
说实话,有些题在你没看别人的解法前,你好不知道有这么美妙优雅的解法,看了之后,卧槽,居然还可以这样。而我们在刷题的过程中,就要不断累积这些技巧,当你累计多了,你就会形成一种+ A, g& u  C4 D
神经反应,一下子就想到了某种方法。解题技巧很多,例如数组下标法、位图法、双指针等等,我自己也分享过一篇总结一些算法技巧的文章' a! f* ~+ c* ]

2 U7 V" U7 f4 P3 l# o' M推荐阅读:一些常用的算法技巧总结' n8 A: N; x3 b4 r. I4 a
% X, }8 n; d: \5 l' M
例如在刷题的时候,我们要学会巧用双指针、数组下标法、位运算等等技巧来解决问题,可能会有意想不到的效果。我给你再找点我之前写文章的一些例子吧:! J# h3 d9 b. q" G. C: X2 }- @
9 q2 w) Q1 i# j9 B2 O) V5 D
分享一道解法巧妙的算法题
/ p+ W: Y: A, [2 b! z$ A. F2 K
1 x( }( f7 y% w2 K+ Z- z【算法技巧】位运算装逼指南
- G0 ?% D/ O) \& T, }1 G. Y0 ^) |' l3 M- v) C2 |
这是个长期累积的过程,我自己也精彩在我的公众号里分享一些解题的文章,感兴趣的可以关注我的公众号:帅地玩编程。
2 o4 h- r$ o/ [& T1 j% u0 G/ |3 |8 N
: t  V, K: @7 m6 k# o% _( q& }再说数据结构发重要性1 c- p6 O( {' ?' o) e+ P
# v7 E. n$ ~, H* v1 Z8 u' F& w8 `
前面我主要是说了我平时都是怎么学习算法的。在数据结构方法,我只是列举了你们一定要学习链表和树(二叉堆),但这是最基本的,刷题之前要掌握的,对于数据结构,我列举下一些比较重要的:% l; j0 R: o' o$ C, Z1 _5 c7 n' B

; _4 n6 Z4 ^3 ]' V0 d1、链表(如单向链表、双向链表)。
9 y3 G/ u0 ^1 K2 r& W1 E
, G3 j9 Y! _) @6 e/ ^8 l2、树(如二叉树、平衡树、红黑树)。7 Y: V  v, \. ~# C

" i" J8 l0 v% ^& A! \3、图(如最短路径的几种算法)。) J( X: {3 y- Z# J  ~/ w

" l& C: B0 u6 B. Y, |; K) n4 X8 L# ]4、队列、栈、矩阵。% d/ o$ m5 x* i7 `
5 D7 g; `% ~7 h- U$ E# V
对于这些,自己一定要动手实现一遍。你可以看书,也可以看视频,新手可以先看视频,不过前期可以看视频,之后我建议是一定要看书。
" z. W- O9 F; g4 K4 ~8 a
. m. K4 ^2 ~2 F: H例如对于平衡树,可能你跟着书本的代码实现之后,过阵子你就忘记,不过这不要紧,虽然你忘记了,但是如果你之前用代码实现过,理解过,那么当你再次看到的时候,会很快就记起来,很快就知道思路,而且你的抽象能力等等会在不知不觉中提升起来。之后再学习红黑树啊,什么数据结构啊,都会学的很快。% c! J8 W+ |" |; @* n

" `" \0 T8 [; e2 A" ]对于有哪些值得学习的算法,我之前也总结过,这里推荐给大家程序员必须掌握的核心算法有哪些?,这篇文章居然 40多万阅读量了,有点受宠若惊。+ E& h3 i- P9 p. f: B' h) i
$ V5 s, M$ ^" k! A8 O( u
最最重要
" Z3 j6 f/ O# T2 U, j8 [# [* l1 Q2 t9 N$ C; h- z9 O
动手去做,动手去做,动手去做。重要的话说三遍。$ J4 v- b5 W& e& K# ]/ F

/ B+ _2 Y' D5 ]8 ^2 F* E千万不要找了一堆资源,订好了学习计划,我要留到某某天就来去做…
$ @( P. q! l. ]2 B8 y& z
; `6 q4 k, [, J5 i千万不要这样,而是当你激情来的时候,就马上去干,千万不要留到某个放假日啊什么鬼了,很多这种想法的人,最后会啥也没做的。
# d/ u! L6 h7 F- P) ]/ e; [; X$ V1 f3 T& }$ i
也不要觉得要学习的有好多啊,不知道从哪学习起。我上面说了,可以先学习最基本的,然后刷题,刷题是一个需要长期坚持的事情,一年,两年。在刷题的过程中,可以穿插和学习其他数据结构。' d0 G8 i' B# ^% p

& O- X* I# a$ t7 ^% a. B总结一下吧
. x2 m* I; k3 x% n7 q# Z* y: P! p: A' r3 r  Y
所以我给大家的建议就是,先学习基本的数据结构以及算法思想,不要盲目刷题,接着刷题的过程中,不能得过且过,尽量追求最优解,还有就是要跳出舒适区,逼自己成长,刷题的过程中,要学会分类总结。
$ b. U2 p/ x) J" z( J$ {
3 c3 c+ U( m2 z当然,最重要的,就是你去动手了,不然,一切免谈!
& ^# h. a& }4 C) w( b* b. q# _
% _% O* E% G- E3 W3 n8 I看在熬夜写过的份上,送我个赞呗,嘻嘻。
. O* J: s2 I  \' I————————————————+ V: ], w7 E4 p" I$ e
版权声明:本文为CSDN博主「帅地」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。& C! ^% M% L4 {" n8 D
原文链接:https://blog.csdn.net/m0_37907797/article/details/104765116
) ~4 z, n4 E# C" _! J* e3 Q% E8 K( }7 Y2 R" _5 z2 l: L* [6 z/ n

3 V1 W5 V3 P6 h) T0 b8 B/ q




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