数学建模社区-数学中国

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

作者: 杨利霞    时间: 2020-4-24 17:58
标题: 算法越学越扎心,有没啥破解之法?

3 j. v; c; C4 }& i. L) q0 z算法越学越扎心,有没啥破解之法?: q3 C' N" l4 [" T, g9 O, E
算法越学越扎心,有没啥破解之法?* i$ m5 ^% l" I* J5 b6 Z+ }: k$ c

3 H- {3 b# _  D对于算法的学习,我也是从一个小白一步步走来,当然,现在仍然很菜,,,不过,鉴于我觉得还有一些人比我更菜了,我决定谈谈我算法学习过程走过的坑,以及自己总结的一些经验。% w9 f; R; ~& w) J$ ^4 l. ]0 F
# s! a* @6 C+ A' M* n  O% |+ i9 \  u
切勿盲目刷题:刷题前的知识积累( C( a; @1 ?0 |; y) Q" ?! `/ M, K

/ A) m7 F% B6 R& F, e2 A0 ~4 o* F说实话,想要提高自己的算法,真的没啥捷径,我觉得最好的捷径就是脚踏实地着多动手去刷题,多刷题。2 g6 S6 r2 \& I5 ]0 W

* z. C* [' U% P9 h0 |- G' K但是,我必须提醒的是,如果你是小白,也就是说,你连常见的数据结构,如链表、树以及常见的算法思想,如递归、枚举、动态规划这些都没学过,那么,我不建议你盲目疯狂着去刷题的。而是先去找本书先去学习这些必要的知识,然后再去刷题。; {. A$ c, x9 z" ~' q3 G$ O$ ~0 @

# E$ e/ k) v1 _8 [) m' P: e+ G$ G因为,如果这些基础都不懂的话,估计一道题做了几个小时,然后看答案都看不懂,做题没有任何思路,这是很难受的。久而久之,估计没啥动力了,我刚开始就是这样,一道题答案看一天,然而还是不大懂,什么回溯啊,暴力啊,还不知道是啥意思。
' {  K; y* ^( a/ c, H' x5 `; u2 B7 F7 d! C
也就是说,假如你要去诸如leetcode这些网站刷题,那么,你要先具备一定的基础,这些基础包括:
6 v( e! P* g2 R9 x1 a1 k, E# l/ B' l. E( k, z$ ]8 q
1、常见数据结构:链表、树(如二叉树)。(是的,链表和二叉树是重点,图这些可以先放着)
% c' v+ J! }) U/ N8 n
' Y! j; ?& ?9 X! `2、常见算法思想:贪婪法、分治法、穷举法、动态规划,回溯法。(贪婪、穷举、分治是基础,动态规划有难度,可以先放着)2 z% Q/ [: {+ E0 n1 k

2 F% U* m# B9 x" `7 @( n以上列出来的算是最基本的吧。就是说你刷题之前,要把这些过一遍再去刷题。如果你连这些最基本的都不知道的话,那么你再刷题的过程中,会很难受的,思路也会相对比较少。' \! Y9 }9 p  e+ O$ n6 B

# a/ I2 ~6 n# c4 k' S总之,千万不要急,先把这些基本的过一遍,力求理解,再去刷题。1 S7 J0 g% u2 K3 z% h
9 s; o9 N# k" U& P. [; }
在这里,我推荐基本我大一时看过的书籍吧,感觉还是非常不错的,如果对于数据结构时零基础的话,那么我建议你可以看《数据结构与算法分析:C语言描述版》这本书,这本书自认为真的很 nice,当时我把这本书里面的全部都看了,并且 coding 了一遍,感觉整个人有了质的飞跃。
/ [' q7 u5 c  A3 d4 @) E" J! o1 ?- Q' }9 d! ]/ R
后面我时在一些学校的OJ刷题,当时看的一本书叫做《挑战程序设计大赛》,日本作家写的,我觉得这本书也很nice,里面有分初级,中级和高级三个模块,基础比较差的可以从初级开始看起。" b$ p. f5 m6 f) w  j8 E

# o; W9 h6 s( ^( C) j9 @) X5 _当然,这两本书,你可以在这个Github上找到:https://github.com/iamshuaidi/CS-Book* g' C1 L3 s0 t" A- Z
总结下:. Y8 Q3 F0 X. X; s
) _% }+ v9 b9 i9 q& }. h
提高数据结构与算法没啥捷径,最好的捷径就是多刷题。但是,刷题的前提是你要先学会一些基本的数据结构与算法思想。' s4 R1 |: s1 U5 g( M8 V( \4 ]
2 a; i/ `/ {  A0 |
AC不是目的,我们要追求完美
- x) J" u, U0 t2 n( S; q8 T# A8 N% g; |  Y4 \7 r9 r
如何刷题?如何对待一道算法题?
+ H9 Q7 x* i, M
2 p7 H- R3 }& }) [& c+ h我觉得,在做题的时候,一定要追求完美,千万不要把一道题做出来之后,提交通过,然后就赶紧下一道。我认为这意义不大,因为一道题的解法太多了,有些解法态粗糙了,我们应该要寻找最优的方法。
9 ^+ B* P0 d  Y! S) C* A
' h! C6 P, [* L. F8 s" e# l( P8 l6 D算法能力的提升和做题的数量是有一定的关系,但并不是线性关系。也就是说,在做题的时候,要力求一题多解,如果自己实在想不出来其他办法了,可以去看看别人是怎么做的,千万不要觉得模仿别人的做法是件丢人的事。4 E8 \, n; d1 C' V3 \" [, M
* ]. s; `0 q/ j9 b+ R
我做题的时候,我一看到一道题,可能第一想法就是用很粗糙的方式做,因为很多题采用暴力法都会很容易做,就是时间复杂度很高。之后,我就会慢慢思考,看看有没其他方法来降低时间复杂度或空间复杂度。最后,我会去看一下别人的做法,当然,并不是每道题都会这样执行。. J6 x) W, }+ C  t5 _/ x" l# z( E' a$ W

0 G- T7 L/ t* F衡量一道算法题的好坏无非就是时间复杂度和空间复杂度,所以我们要力求完美,就要把这两个降到最低,令他们相辅相成。! C  E5 l. B& h: k
8 M% `+ |. Q# `
我举道例题吧:# H  }' u! j# v, l' b; K& c" ~7 Q. i

, w" |$ `) U& x9 h问题: 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法?2 A8 L' B7 w! G* @. n! y5 N
/ m0 k; L9 p0 O
这道题我在以前的分章分析过,不懂的可以先看下之前写的:递归与动态规划—基础篇10 J9 u$ j/ `0 i5 `5 [+ u
  X* |& Q5 n' [9 Z* u
方法1::暴力递归( m) @6 \1 q% z5 V0 A
& e: n0 _, t0 B; G
这道题不难,或许你会采取下面的做法:
' [5 V" u2 a. j& l3 Y2 n
; e- Y1 v& \9 y& ~+ q1 F7 opublic int solve(int n){, Z8 U/ ^8 @- _  f0 I4 G
    if(n <= 2){
( U5 Y$ T. F2 {0 w+ P8 W        return n;
& ^  B% V5 n  ]8 I* p9 o    }else{% }1 ?0 ?) p: \6 m8 N
        return solve(n-1) + solve(n-2);
. Z# ?1 z; L. ]1 u6 V    }' |% T: U' n1 X% ~' O$ r
}
5 [3 n" l  d+ [14 ]$ ]# h/ z7 U
2% E) `0 ]3 ]7 V* P
3
2 N3 H2 N+ ?$ g( o! X8 j48 T; z" M0 N" ?3 ^& O* Y
5
8 |. Q7 D; w; i3 Q) Y6 v1 U6
3 ~$ M6 c5 \+ p# I7
& v: G! R5 @! Q! l$ w/ A这种做法的时间复杂度很高,指数级别了。但是如果你提交之后侥幸通过了,然后你就接着下一道题了,那么你就要好好想想了。
/ M& Y  Y! J4 f. U& T
; O% |0 \$ a6 S方法二:空间换时间( {4 {3 a. g" @! W3 ]4 O

, `2 z, B; j8 E- |! t力求完美,我们可以考虑用空间换时间:这道题如何你去仔细想一想,会发现有很多是重复执行了。不行你可以画个图4 Z, }5 e( O: J+ X" u

# M+ z! C8 g5 r- }. l所以可以采取下面的方法:
- {! y( y5 |! m' c4 {# b. y. \0 [; o0 w  [5 @" D1 |4 Q
//用一个HashMap来保存已经计算过的状态# r% E  d$ w9 Y# R  u) Y- w
static Map<Integer,Integer> map = new HashMap();, {/ l+ {* d5 D0 m! }0 W
public static int solve(int n){
, [! G% B. R! D3 N" b1 w4 o         if(n <= 2){
8 l9 ?7 x) v% X5 w$ r/ m. I8 g4 x        return n;) b3 W9 ~" I' V9 V
    }else{//是否计算过
" E( t) ~* E. P. E) c2 @( v7 l# E        if(map.containsKey(n)){$ B8 J$ e6 d! I% ]: j
            return map.get(n);1 p# I% M# m7 N. \7 f- g
        }else{" Q8 W" u' P4 H4 Q4 {
            int m = solve(n-1) + solve(n-2);, t7 w5 t% |/ t# c% u; S% w" c
            map.put(n, m);1 j$ \" X- Q% |) F
            return m;) d# V# T& L& y4 @
        }7 s. p) p# u+ D" ]9 }7 s# r
    }
% u" Z6 G1 m* e# Q}
3 T3 l4 h$ V' i2 ]% X
! L$ v- j" V6 ^; }' Q5 W, ~14 ~  P0 O( v, y/ ^: S$ Y
28 N" y" p7 F2 |. ~  v9 z
3( H5 ]% O, m7 g; T3 F: @
41 a/ W' ^8 C" w: [2 j8 m; s
5
# A2 l0 G% U! S/ b1 K66 ?# p3 y3 K% f' {1 n
7. p& f; H" D) n, ~4 [
8& e4 b, e& E( Z( g4 N  Z
94 T( D: n" L3 O' s3 s7 g
10% \6 s# ?+ z; m- u( w$ a" g* T! w
117 ]# D6 l% _5 a; N/ Y% C  M
12" z, y) }. g4 A4 n+ \5 ?% X7 H
133 o1 S2 b; b8 I; F4 B) h8 j/ ~- Q
14. X* M3 ?2 {# f5 N  D
15
- \# w' f3 V$ E1 O% [16( P- A3 U- q' }; x
这样,可以大大缩短时间。也就是说,当一道题你做了之后,发现时间复杂度很高,那么可以考虑下,是否有更好的方法,是否可以用空间换时间。8 V$ {7 ~& Q% h" c

$ C/ K( t3 j2 j! h/ i/ N方法三:斐波那契数列
& Q5 R( s: t7 x7 C( ~8 d& I# u% ^8 G9 ?; ^" Y3 q: y% @: e
实际上,我们可以把空间复杂度弄的更小,不需要HashMap来保存状态:& s' N8 F: J( {) Z6 G
, T/ ~' h0 t: }: u3 i! k
public static int solve(int n){
. S# Q4 o+ ~9 @" z) P9 m4 V    if(n <= 2){+ B- u6 S& o9 I# o* W; \* f
        return n;( p( b/ D8 Z! i# [6 Q
    }
1 ]) W1 e) z0 l! Z3 r    int f1 = 0;$ \( k0 H9 Q8 X4 [3 Y1 i7 F
    int f2 = 1;% m' h+ n0 _" w2 u/ P; z4 q: c* o
    int sum = 0;
$ ~3 g! t' D( T  U" X    for(int i = 1; i<= n; i++){
+ U" |; y- y( S  ]" J9 x        sum = f1 + f2;2 o) K/ t% P2 c- I
        f1 = f2;; l- J8 L5 o# t  \
        f2 = sum;
' J7 X- G8 h# y; s& L6 y    }0 k7 v- }+ P/ N4 K1 M
    return sum;
5 Z/ Y8 P5 k4 [) {}
  a8 }5 o: R/ _: T; `! O1
# b7 Y& E0 Y* a3 {1 q9 S( w! q2
& h/ d4 `* }) H  P3
0 q- F0 p; {' x6 E0 Z9 J7 v. V4
1 P/ r  }, E6 O6 H5
6 H/ s4 |( G% {3 T2 n6
, t7 g8 ]. f+ l9 L) R1 a  H7
5 y& W$ }! F7 E) v3 q! {88 Z, F1 N6 h7 `& D! L
9
0 k4 ?+ l9 s9 \2 Z. Q10( E6 y& v7 s+ d3 ]+ q& k+ z  c
11
  ?) I3 s/ R% y" w! Y. G12
9 q2 s: q1 s: B& z13% y0 W# J) L! u1 \+ ?/ B2 V
14/ ]7 p! ~7 Q& v$ _) I6 G
我弄这道题给你们看,并不是在教你们这道题怎么做,而是有以下目的:
2 x, Z' o6 r0 `& i+ J
! A$ u3 |6 m. c( f! p* ~1、在刷题的时候,我们要力求完美。
/ T  o* k$ l" B% T8 u" q  ?$ k/ C' C, E' {
2、我想不到这些方法啊,怎么办?那么你就可以去看别人的做法,之后,遇到类似的题,你就会更有思路,更知道往哪个方向想。( g9 j8 `; {6 t, k+ A/ m! d

4 j# F5 t: H7 n8 e3、可以从简单暴力入手做一道题,在考虑空间与时间之间的衡量,一点点去优化。
1 L) b$ J: W  M$ D- R! ~2 J: s" h* D( e0 e0 l
挑战自己,跳出舒适区) L& a1 S3 R: w/ m

( N. h9 c7 C" F3 N什么叫舒适区?在刷题的时候,可能有一类题是你比较懂的,你每次一看就有思路,然后半个小时就撸好代码,提交代码,然后通过了,然后,哇,又多刷了一道题,心里很舒服。- S& |0 L& K( p8 h

9 o( D0 O4 k& r0 P/ a1 q0 W但是,记住,前期你可以多刷这种题练手,提升自己的乐趣,但,我还是建议你慢慢跳出舒适区,去做一些自己不擅长的题,并且找段时间一直刷这种题。例如,我觉得我在递归方面的题还是挺强的,- @9 }4 d; C9 ]) U* f& \5 }, \
但是,我对动态规划的题,很菜,每次都要想好久,每次遇到这种题都有点害怕,没什么信心。不过有段时间我觉得只刷动态规划的题,直接在 leetcode 选定专题,连续做了四五十道,刚开始很难受,后来就慢慢知道了套路了,一道题从两三个小时最后缩到半小时,简单的十几分钟就搞定。感觉自己对这类型的题也不惧怕的。/ G# d: U% L+ B0 z% B" I. M* k2 \

/ c2 s. [9 l# z3 \+ S$ Z% o1 V" `当然,对于动态规划的学习,大家也可以看我这篇广受好评的文章:为什么你学不过动态规划?告别动态规划,谈谈我的经验. [1 }" T" L- {! O( P

2 C& O, h$ q/ V$ K4 V5 u( P所以,建议你,一定要学好跳出自己的舒适区。/ u! B1 T) Q7 m

1 S3 Q* J8 F2 B( L6 ]1 K0 ~一定要学会分类总结* x* E" v' V  H, m

7 J/ X+ i: r4 G5 f& P7 E有些人以为 leetcode 的题刷的越多,就一定能越厉害,其实不然,leetcode 虽然有 1000 多道题,但题型就那么几类,我们前期在刷的时候,我是建议按照题型分类刷题的,例如我这整理刷二叉树相关,然后刷链表相关,然后二分法,然后递归等等,每刷一种题型,都要研究他们的套路,如果你愿意去总结,那么 leetcode 的题,其实你刷几百道,有目的、挑选的刷,我觉得就差不多了。$ v: r8 v8 O  G2 y& Y' V# B
9 |* j! O. R- g' x( N3 v( o: g
我看过一本书,叫做《程序员代码面试指南:IT 名企算法与数据结构题目最优解》,这本书就非常不错,里面按照栈,队列,链表,二叉树,字符串等一个专题一个专题来刷的,并且每道题都给出了最优解,而且里面的题有一定的难度,感兴趣的,真心不错,如果你把这本书的题全部搞定,并且总结相关套路,那么你的算法一定有很大的提升。
: g' n' O. E9 h$ h! J4 V/ I4 R; r
3 h/ ^* N9 v3 t3 t8 A# C推荐一些刷题网站$ ?1 o0 Q% I  p0 @9 `, T
& @, N3 U2 a( C  ?6 [
我一般是在leetcode和牛客网刷题,感觉挺不错,题目难度不是很大。# q  V2 V4 h$ m' @# x. d
9 d# q! o9 E& Y
在牛客网那里,我主要刷剑指Offer,不过那里也有个在线刷leetcode,不过里面的题量比较少。牛客网刷题有个非常方便的地方就是有个讨论区,那里会有很多大佬分享他们的解题方法,不用我们去百度找题解。所以你做完后,实在想不出,可以很方便着去看别人是怎么做的。1 M8 Z4 V9 _& a8 w

1 d! d* j/ M$ y2 P至于leetcode,也是大部分题目官方都有给出答案,也是个不错的刷题网站。你们可以两个挑选一个,或者两个都刷。! `& S. c3 S, }  R" s
, s8 J9 [0 j$ F
当然,还有其他刷题的网站,不过,其他网站没刷过,不大清除如何。4 S0 f9 L2 V) n" G; J5 ^6 O7 [
+ W0 t3 v% h  y: v, O- Q0 e
至于leetcode,有中文版和英文版
+ ]! H/ F# c% S  B
# y' `- B" q: m6 S" E5 X7 n, nleetcode有中文版
2 r& @/ I2 j( n! t5 h
; ^9 k" F2 c* }* O# q: T, u- A英文版! Y0 D- W7 `0 k. J
) L5 o  a- }8 ~2 J. q+ j# Q
根据自己的兴趣选。  ]* K# x& ?; C9 ~) |

, A4 E+ n/ S$ U- k6 p" G4 v7 g学习一些解题技巧
/ ^! |3 ?6 ]" c" a# u( D1 M" O6 B" ~1 g/ U
说实话,有些题在你没看别人的解法前,你好不知道有这么美妙优雅的解法,看了之后,卧槽,居然还可以这样。而我们在刷题的过程中,就要不断累积这些技巧,当你累计多了,你就会形成一种
5 v7 g  @+ O6 A, U0 ]; _! F, |神经反应,一下子就想到了某种方法。解题技巧很多,例如数组下标法、位图法、双指针等等,我自己也分享过一篇总结一些算法技巧的文章6 n& L3 Z# O8 p5 W' `- x3 ~
: y. B9 z( [: G. {2 d  q. `
推荐阅读:一些常用的算法技巧总结
+ X0 {8 r0 i# x/ E" W- o
7 J% m4 q6 F6 K! h- Y% h例如在刷题的时候,我们要学会巧用双指针、数组下标法、位运算等等技巧来解决问题,可能会有意想不到的效果。我给你再找点我之前写文章的一些例子吧:2 G' o8 \* E& k

- x  F$ M) e# p: [8 m, Y分享一道解法巧妙的算法题
; T7 E5 j% ]8 I6 P4 o. X& W
1 \/ D) z2 O. V* ], e- ?0 K【算法技巧】位运算装逼指南
  u! r4 W( }- m" Z0 x6 I  F# I+ \
* L2 ~- {: F3 Y* R# w! ]1 M这是个长期累积的过程,我自己也精彩在我的公众号里分享一些解题的文章,感兴趣的可以关注我的公众号:帅地玩编程。6 K1 a: x) n& {; ]9 m

/ S: O! e' |2 H; P4 p+ F1 Q再说数据结构发重要性
" p# V7 x: q9 S/ C* a8 R$ Z
7 a9 e! O" \  r4 G) |) W1 Q& F前面我主要是说了我平时都是怎么学习算法的。在数据结构方法,我只是列举了你们一定要学习链表和树(二叉堆),但这是最基本的,刷题之前要掌握的,对于数据结构,我列举下一些比较重要的:
* F4 v3 g- X4 c3 U% J: z! w' h$ E
6 _" s8 `* c0 @$ {$ A1、链表(如单向链表、双向链表)。1 q. D' L  `. ~1 ]# z+ V1 }2 G
9 F' R: Q- K) O! O( Z( p
2、树(如二叉树、平衡树、红黑树)。
6 ~8 X4 u1 r" [: `. f
# H  ?! O4 @% h7 r3、图(如最短路径的几种算法)。
% \) V) n# h! G9 ?0 \  b0 U
- @+ o5 R8 b" O0 s; G, J7 j4、队列、栈、矩阵。, M) X& v, U2 m; L, v9 b

. v) A# y4 ?7 F$ c* D对于这些,自己一定要动手实现一遍。你可以看书,也可以看视频,新手可以先看视频,不过前期可以看视频,之后我建议是一定要看书。8 O+ C' C' Z/ f- Q0 f
+ V: L/ q& ]8 H9 W: X' E
例如对于平衡树,可能你跟着书本的代码实现之后,过阵子你就忘记,不过这不要紧,虽然你忘记了,但是如果你之前用代码实现过,理解过,那么当你再次看到的时候,会很快就记起来,很快就知道思路,而且你的抽象能力等等会在不知不觉中提升起来。之后再学习红黑树啊,什么数据结构啊,都会学的很快。
. v4 s' o  W  \4 V# l  v, p5 h+ {; A6 n0 l' d) N0 G
对于有哪些值得学习的算法,我之前也总结过,这里推荐给大家程序员必须掌握的核心算法有哪些?,这篇文章居然 40多万阅读量了,有点受宠若惊。
+ b7 `7 v; f$ M" n. I3 W# b3 F: o5 n& y
最最重要
2 [! J0 E2 P. d+ E0 ?0 M( b8 p# k4 w5 A" b
动手去做,动手去做,动手去做。重要的话说三遍。* x9 R5 o$ j- Y8 F; g

& Y* V6 f2 H! }! U( a7 y" [9 \" ]% ]千万不要找了一堆资源,订好了学习计划,我要留到某某天就来去做…% `7 k& b' K/ a

5 \2 g6 i' k- M8 F  V; }, w千万不要这样,而是当你激情来的时候,就马上去干,千万不要留到某个放假日啊什么鬼了,很多这种想法的人,最后会啥也没做的。% e  X) Z' P. h  S0 d- {
# z- D! t0 l9 [2 N% d
也不要觉得要学习的有好多啊,不知道从哪学习起。我上面说了,可以先学习最基本的,然后刷题,刷题是一个需要长期坚持的事情,一年,两年。在刷题的过程中,可以穿插和学习其他数据结构。
$ V1 Z. ~7 [& t2 L" ?. J1 `& a) l3 f* g0 Q" q0 `
总结一下吧
$ i3 a6 d% O2 B4 P  y% A- k  ]/ O: w* E
所以我给大家的建议就是,先学习基本的数据结构以及算法思想,不要盲目刷题,接着刷题的过程中,不能得过且过,尽量追求最优解,还有就是要跳出舒适区,逼自己成长,刷题的过程中,要学会分类总结。. c# ^% ~1 e# d: s. q' l2 f

( x; }9 p/ N% ^% o6 c当然,最重要的,就是你去动手了,不然,一切免谈!
5 S; r4 @0 b  j% O- `. Q" V0 u" ]+ c2 @9 M, o4 e+ R; h. |
看在熬夜写过的份上,送我个赞呗,嘻嘻。
! v8 u! ~8 o% I2 R————————————————' J+ v4 u) H! z
版权声明:本文为CSDN博主「帅地」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
8 W4 F$ w  @, n* u, e8 b$ ]原文链接:https://blog.csdn.net/m0_37907797/article/details/104765116
. e$ `2 F2 {( M3 {% s# d5 C* k/ G: k2 S& g7 I
' C$ `9 F' Q1 c/ O





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