数学建模社区-数学中国

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

作者: 杨利霞    时间: 2020-4-24 17:58
标题: 算法越学越扎心,有没啥破解之法?
( q, ?$ C6 q9 I/ t8 x
算法越学越扎心,有没啥破解之法?7 ~3 ^% h% ~5 m$ ]$ u7 S
算法越学越扎心,有没啥破解之法?
; Y* J5 t0 {3 @( p6 x7 r7 i/ U  A$ ~" Y; |$ K2 R2 _7 r/ l' _
对于算法的学习,我也是从一个小白一步步走来,当然,现在仍然很菜,,,不过,鉴于我觉得还有一些人比我更菜了,我决定谈谈我算法学习过程走过的坑,以及自己总结的一些经验。
. d0 Z) U# }3 y0 N
0 k$ v" T. [/ x( x6 D, l" @5 g切勿盲目刷题:刷题前的知识积累
! J: Z" D3 }2 H, Y. S, a% R+ S
说实话,想要提高自己的算法,真的没啥捷径,我觉得最好的捷径就是脚踏实地着多动手去刷题,多刷题。5 h& m- }) [% o- J. N

6 Z) O( I, y) ^  n5 M5 ^但是,我必须提醒的是,如果你是小白,也就是说,你连常见的数据结构,如链表、树以及常见的算法思想,如递归、枚举、动态规划这些都没学过,那么,我不建议你盲目疯狂着去刷题的。而是先去找本书先去学习这些必要的知识,然后再去刷题。# V4 [& X" a: d

* h) |' j! H1 g# {: i' i因为,如果这些基础都不懂的话,估计一道题做了几个小时,然后看答案都看不懂,做题没有任何思路,这是很难受的。久而久之,估计没啥动力了,我刚开始就是这样,一道题答案看一天,然而还是不大懂,什么回溯啊,暴力啊,还不知道是啥意思。7 a, K5 {. S0 g
# ~- H1 ~( g/ a7 W  W  D
也就是说,假如你要去诸如leetcode这些网站刷题,那么,你要先具备一定的基础,这些基础包括:
) r( c: M6 b( w
. M: A/ H3 u2 n* C6 `  t% W7 s1、常见数据结构:链表、树(如二叉树)。(是的,链表和二叉树是重点,图这些可以先放着)
( Q3 o0 Z" J( L: [! [
3 T2 x# R2 b; L% W- |2、常见算法思想:贪婪法、分治法、穷举法、动态规划,回溯法。(贪婪、穷举、分治是基础,动态规划有难度,可以先放着)
1 [- J0 @% }% l! G2 t" T  r: R& G, P4 P+ {7 Z" @9 H
以上列出来的算是最基本的吧。就是说你刷题之前,要把这些过一遍再去刷题。如果你连这些最基本的都不知道的话,那么你再刷题的过程中,会很难受的,思路也会相对比较少。9 [' x4 m+ W  \0 N7 w, ]9 X: ~
  u/ v  _# Q; X- \
总之,千万不要急,先把这些基本的过一遍,力求理解,再去刷题。# N5 n" C' W: z$ E2 C" R; o0 x

! U( }5 x2 H& U" _! g- L1 e在这里,我推荐基本我大一时看过的书籍吧,感觉还是非常不错的,如果对于数据结构时零基础的话,那么我建议你可以看《数据结构与算法分析:C语言描述版》这本书,这本书自认为真的很 nice,当时我把这本书里面的全部都看了,并且 coding 了一遍,感觉整个人有了质的飞跃。/ l# y' m4 q# s

' ^* J' b, Y7 }7 S3 g! N' c, q后面我时在一些学校的OJ刷题,当时看的一本书叫做《挑战程序设计大赛》,日本作家写的,我觉得这本书也很nice,里面有分初级,中级和高级三个模块,基础比较差的可以从初级开始看起。4 H; }' G3 K+ Y0 [' M
9 w* p  T0 \. Q: x+ C8 p
当然,这两本书,你可以在这个Github上找到:https://github.com/iamshuaidi/CS-Book4 j8 y( [2 t3 |5 \0 i: x5 m
总结下:4 \0 n! Q, Z( Z; n" X9 g
! u& j: }2 Y/ T/ a: H
提高数据结构与算法没啥捷径,最好的捷径就是多刷题。但是,刷题的前提是你要先学会一些基本的数据结构与算法思想。
5 e! }" h% @! Q6 A  w% N  q! v/ N1 t9 g0 p
AC不是目的,我们要追求完美5 C6 S- [5 I( P7 @" u

) l4 s. e3 f8 _; ]- r7 {, n  s如何刷题?如何对待一道算法题?
6 i* _- q% Q& Z0 Z( ~9 e! m. M5 W  x# j: k7 E: g/ S2 F9 i
我觉得,在做题的时候,一定要追求完美,千万不要把一道题做出来之后,提交通过,然后就赶紧下一道。我认为这意义不大,因为一道题的解法太多了,有些解法态粗糙了,我们应该要寻找最优的方法。
2 z& @- X0 x5 {  L6 m) k
& z. u, ]3 ^8 U8 K算法能力的提升和做题的数量是有一定的关系,但并不是线性关系。也就是说,在做题的时候,要力求一题多解,如果自己实在想不出来其他办法了,可以去看看别人是怎么做的,千万不要觉得模仿别人的做法是件丢人的事。
( L: s7 v" X7 d! S1 b* C' y, y" R, o0 Y* E+ D# Y
我做题的时候,我一看到一道题,可能第一想法就是用很粗糙的方式做,因为很多题采用暴力法都会很容易做,就是时间复杂度很高。之后,我就会慢慢思考,看看有没其他方法来降低时间复杂度或空间复杂度。最后,我会去看一下别人的做法,当然,并不是每道题都会这样执行。
8 e. P8 F. \( I7 {3 [) e/ g
" {: ?( ?: }# P衡量一道算法题的好坏无非就是时间复杂度和空间复杂度,所以我们要力求完美,就要把这两个降到最低,令他们相辅相成。% y: H7 E8 `$ c  B: u3 m2 s* Y  g

, {+ w; o; \3 B; h1 E+ R! E; Z我举道例题吧:
( J$ d% _! P+ X( ?2 E+ G7 d
* ]/ [" P" Y( R问题: 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法?
& Y5 z* Z6 }  O9 V( p/ Z/ i$ }* X) @6 U$ H: W* f
这道题我在以前的分章分析过,不懂的可以先看下之前写的:递归与动态规划—基础篇1/ F* L5 B$ b' a: l

2 y$ Q. \6 C' I方法1::暴力递归
5 o$ w6 j. N& w% d. D& `4 ~  n
- B! p6 w9 C! s0 [; a这道题不难,或许你会采取下面的做法:& r( E0 D$ _" q* F! x  c

& p6 q' X+ Z4 V  Kpublic int solve(int n){5 Z( g2 `( e9 e( d1 o
    if(n <= 2){" Y8 z7 G4 j% `  \  Y! f
        return n;1 W; @9 n' s, L( |, j; O, K) B+ P
    }else{
. g1 U/ V4 n9 h) r( [: w' Z        return solve(n-1) + solve(n-2);
$ B; b: b$ C0 V9 Z6 Z1 x    }
0 Q7 ?8 h+ v1 k, F}1 Q$ e% T7 s4 o9 B7 O& o: m
1
( |) Y) f9 g9 M  @" h2# W, g1 M, w! q6 w
34 z4 n- g# e: [) Z/ `2 q0 n
4
+ f0 T9 g" ^' @5
, `! j" O- R- i  a- s- z6( {, m. `/ C& n2 s7 M" i, c: a
7
3 G- M9 D  _3 f; ?2 Z这种做法的时间复杂度很高,指数级别了。但是如果你提交之后侥幸通过了,然后你就接着下一道题了,那么你就要好好想想了。- L: @, s9 T: X+ [4 T- `5 J: s

% x1 {: e$ B, i3 J( k$ U' D方法二:空间换时间/ q3 L- v, v+ b' [) Y; m! X
/ G" y" _( P! @2 d
力求完美,我们可以考虑用空间换时间:这道题如何你去仔细想一想,会发现有很多是重复执行了。不行你可以画个图7 j& o3 l2 v9 I5 J, o# V, q9 F+ t* q
& P* q5 _4 s, c7 K/ u/ W2 P7 N/ z% N
所以可以采取下面的方法:
  w3 d' t: w# n
, l$ B$ s9 C6 j! F# w//用一个HashMap来保存已经计算过的状态
" Y- y$ X& \4 d0 n' b4 A9 xstatic Map<Integer,Integer> map = new HashMap();, M( Z  m! y% b8 e7 S/ H( B
public static int solve(int n){; \& D- o" X6 [- C. n% ~
         if(n <= 2){+ {! H) i9 j" q4 E! `9 B
        return n;. K" ~1 z0 I' @
    }else{//是否计算过
6 [1 V. u* `* P, @- Q        if(map.containsKey(n)){7 p0 f: A) }6 a# x  d) {8 }/ n
            return map.get(n);
' d. N; B0 P9 a) Q8 K1 C7 w: X8 `        }else{
/ h- ]# O# W. p1 d! r: L$ _            int m = solve(n-1) + solve(n-2);
& ~% `8 i% i0 d* p" m" S) ~1 ^4 f            map.put(n, m);+ R# Z; d0 d$ i7 E  R# z: }
            return m;8 O. O& T( t7 s* F5 M+ R$ H
        }
+ l* b7 G) l, o  _    }6 c8 z; J- G' B& ~
}& o" p# x' B+ K  S: p  }2 m

4 B) M- I  p* m: y10 z5 `. m8 o9 k, d2 f; X2 E+ y% A
2
; c. l. {! |( q34 d5 Z& K* m8 Y8 a
4/ \( h  n1 X% T( K0 Q  E
5
; F$ Q/ t1 L1 H* G6
( ?: b! ?; @5 |0 S, w( ~7' q! I4 Y* h2 v7 o9 a* D8 ?+ i
8  K" x$ V, }! b
9
0 v: t7 w  h$ g* L! ~104 X# a, G' M) I" S$ m& [
117 G1 W5 |, |8 r2 M' Y
122 S# R4 ^0 d/ X, s9 Y
136 m  ]  Q7 Z( d, W& q; ~
14
  {2 i$ z# {: r15
/ U3 k5 b- K6 _16$ P6 Z$ }  x# Z. Q6 w7 g( A
这样,可以大大缩短时间。也就是说,当一道题你做了之后,发现时间复杂度很高,那么可以考虑下,是否有更好的方法,是否可以用空间换时间。
% _+ w& J3 i' x; m) _( y1 P# C
3 S3 \9 [$ {! q. z% K方法三:斐波那契数列
4 g9 `. i+ Q1 @
; B, M* W- x: F0 h, w' e8 I实际上,我们可以把空间复杂度弄的更小,不需要HashMap来保存状态:: q/ }5 K" S% V

  z1 l7 k; q% w2 W& g/ fpublic static int solve(int n){$ v/ ^, W  q' Y. W7 U
    if(n <= 2){
3 K7 ]0 ]7 M, N4 n        return n;" g$ f0 v0 b+ h7 I8 H
    } $ F7 E0 y/ E( _
    int f1 = 0;
1 o2 L1 R; p- H, e1 ]5 H3 c    int f2 = 1;
: D0 L, s! p& Z/ \& a0 ^    int sum = 0;
5 n5 M  ]. O3 K' E( {5 [/ f    for(int i = 1; i<= n; i++){- x- V! o' [3 f* A. G3 y: M
        sum = f1 + f2;
% ?- j; s9 w  n5 D        f1 = f2;* r) x5 Y. J& i0 Q- i6 F+ U  J3 v
        f2 = sum;
) Q4 Q8 ]9 _, [8 @3 ?9 y# b    }( ]' l8 U8 X$ I# C; ~$ g  ~4 t
    return sum;9 A9 d( M- X- {# v6 R+ L. _
}
! Y$ @7 ~6 D  V3 `& V$ B1( X) d0 h) l/ S6 k
2* f; b' R, c* Z3 K" h
3* m' t9 o  O9 g# {  x, a( H
4
' t7 v3 x/ g. H9 o5
; ^, U3 \/ P' w0 Z6
5 e- @0 D) z$ g$ h: k. T, C7
; S! e) z4 T9 c5 y( ~2 D88 Q6 S) a3 |. N: ^" e- o& ?
9
, V6 A3 ~* r$ ?: z10
1 z: H/ R1 J) v5 U114 N& L1 Z' V# m
12- A0 i/ |9 l( B: w1 |6 z/ I6 c
13
7 a% ~) `. V+ R' T0 f14; P( Y. Y# O5 @* M' D2 v- {
我弄这道题给你们看,并不是在教你们这道题怎么做,而是有以下目的:
7 o/ w& P/ L) F3 d+ U+ a% I
% f/ j; C) ]3 h$ R0 u: W( S# S' m* [5 K1、在刷题的时候,我们要力求完美。
3 Y# V  Z) L7 |9 q2 N. ^! C; g3 `" x* R/ j
2、我想不到这些方法啊,怎么办?那么你就可以去看别人的做法,之后,遇到类似的题,你就会更有思路,更知道往哪个方向想。
# _) w/ }, R. h, v; }
& @0 [& O9 x, u- g4 X  Z1 c7 m3、可以从简单暴力入手做一道题,在考虑空间与时间之间的衡量,一点点去优化。
  Y* F0 ~8 J) k& S# M' U' e2 \& N1 g: P
挑战自己,跳出舒适区
' @1 M$ \0 C9 t0 h  g& k+ V; f& Z5 j+ G  S8 L
什么叫舒适区?在刷题的时候,可能有一类题是你比较懂的,你每次一看就有思路,然后半个小时就撸好代码,提交代码,然后通过了,然后,哇,又多刷了一道题,心里很舒服。3 l( W& X6 ]/ A9 n9 W
5 B% s( x6 E: b1 N. c/ g1 Y# Y
但是,记住,前期你可以多刷这种题练手,提升自己的乐趣,但,我还是建议你慢慢跳出舒适区,去做一些自己不擅长的题,并且找段时间一直刷这种题。例如,我觉得我在递归方面的题还是挺强的,- z  A% x& h$ i8 ?: m# v% ~
但是,我对动态规划的题,很菜,每次都要想好久,每次遇到这种题都有点害怕,没什么信心。不过有段时间我觉得只刷动态规划的题,直接在 leetcode 选定专题,连续做了四五十道,刚开始很难受,后来就慢慢知道了套路了,一道题从两三个小时最后缩到半小时,简单的十几分钟就搞定。感觉自己对这类型的题也不惧怕的。& B  E# u6 [9 A! Z+ y9 w

3 D3 T- H6 B( z当然,对于动态规划的学习,大家也可以看我这篇广受好评的文章:为什么你学不过动态规划?告别动态规划,谈谈我的经验' e, x' B3 Q- o- k
- |. F# a( s& g+ W! {' u4 s' m
所以,建议你,一定要学好跳出自己的舒适区。2 S9 d( j. T4 ~1 n
' q- |2 N- Z  _
一定要学会分类总结
; v4 E0 S# F$ z% R/ w5 a! n! M6 E
有些人以为 leetcode 的题刷的越多,就一定能越厉害,其实不然,leetcode 虽然有 1000 多道题,但题型就那么几类,我们前期在刷的时候,我是建议按照题型分类刷题的,例如我这整理刷二叉树相关,然后刷链表相关,然后二分法,然后递归等等,每刷一种题型,都要研究他们的套路,如果你愿意去总结,那么 leetcode 的题,其实你刷几百道,有目的、挑选的刷,我觉得就差不多了。& H2 i, L) S% j6 }- j( {9 o: g
, w8 G+ K8 k. N" f
我看过一本书,叫做《程序员代码面试指南:IT 名企算法与数据结构题目最优解》,这本书就非常不错,里面按照栈,队列,链表,二叉树,字符串等一个专题一个专题来刷的,并且每道题都给出了最优解,而且里面的题有一定的难度,感兴趣的,真心不错,如果你把这本书的题全部搞定,并且总结相关套路,那么你的算法一定有很大的提升。
0 M3 p! w- e5 @" C& j& H; w4 a8 Z$ [& b$ Y! h- H8 k6 S" ~
推荐一些刷题网站
, S3 z0 B5 Y# M6 f6 ]) S# p% C
. x+ u5 A% W* E6 J- o! `9 i我一般是在leetcode和牛客网刷题,感觉挺不错,题目难度不是很大。
1 U0 ^1 I7 k% _( D2 Y3 V* @" L8 s% Q% |9 F1 w% v( V, A$ C
在牛客网那里,我主要刷剑指Offer,不过那里也有个在线刷leetcode,不过里面的题量比较少。牛客网刷题有个非常方便的地方就是有个讨论区,那里会有很多大佬分享他们的解题方法,不用我们去百度找题解。所以你做完后,实在想不出,可以很方便着去看别人是怎么做的。
  C1 A  V+ E# t! w( |/ l% Z& ]4 I# O4 u9 r' H2 X; U
至于leetcode,也是大部分题目官方都有给出答案,也是个不错的刷题网站。你们可以两个挑选一个,或者两个都刷。
, {/ j/ q: V; A4 e& @5 ^9 N0 i' t, e* g$ I0 k% j6 k
当然,还有其他刷题的网站,不过,其他网站没刷过,不大清除如何。
! i' I$ D5 H! P8 f! C6 M/ @2 q; C- ], A3 b+ E. A$ o6 ]% B
至于leetcode,有中文版和英文版
6 S9 a3 G1 s8 D; r8 p* a: a
9 F2 ?( f0 \5 k" d4 x* a' Wleetcode有中文版
! E  H' F" K) U7 {+ ]. P0 S2 o' [  ?$ {) e+ @1 C5 j# s. M
英文版# y6 t* Y4 u, a* b

& b( H) T- a, p: |3 @8 T# q( N/ Z根据自己的兴趣选。
& j. H1 E! R' s" m) Z* w2 O+ m& q6 V# L
学习一些解题技巧7 M" G, j0 T, r$ Y+ W
$ N  n% ]) ?  R% x( Q: _
说实话,有些题在你没看别人的解法前,你好不知道有这么美妙优雅的解法,看了之后,卧槽,居然还可以这样。而我们在刷题的过程中,就要不断累积这些技巧,当你累计多了,你就会形成一种
* I" v, K3 X2 r/ G( L9 P神经反应,一下子就想到了某种方法。解题技巧很多,例如数组下标法、位图法、双指针等等,我自己也分享过一篇总结一些算法技巧的文章! D6 |6 O, l3 S4 \4 `
" t# t! E7 G! x5 d& `# v
推荐阅读:一些常用的算法技巧总结
, y7 c! o/ j$ b+ R5 `0 i) c: H) s  H* {( Q; T: m4 }# d( t
例如在刷题的时候,我们要学会巧用双指针、数组下标法、位运算等等技巧来解决问题,可能会有意想不到的效果。我给你再找点我之前写文章的一些例子吧:
0 W+ W0 @' z3 l' I) d, r1 u$ F* D, v9 f
分享一道解法巧妙的算法题
- n+ g* D( O. E" X" |( X, [5 p' c4 ]
4 [: T" \/ a0 W4 ]4 H* h【算法技巧】位运算装逼指南/ x6 }0 ^6 q0 h1 s  T* F7 p
" ?! V0 o9 w1 D/ f
这是个长期累积的过程,我自己也精彩在我的公众号里分享一些解题的文章,感兴趣的可以关注我的公众号:帅地玩编程。
- g1 o8 U% Q, g/ I
, ~9 k1 A" g! C+ B4 _再说数据结构发重要性
* A  p/ _2 L4 ~4 K( h! S; b9 M
% Z5 I4 z1 b# }  n4 d9 R前面我主要是说了我平时都是怎么学习算法的。在数据结构方法,我只是列举了你们一定要学习链表和树(二叉堆),但这是最基本的,刷题之前要掌握的,对于数据结构,我列举下一些比较重要的:
, f7 C* A1 V7 [
, d. |: N% v+ v, {% V" [; i1、链表(如单向链表、双向链表)。
) Q- ~2 q! h9 f- f
  Q2 x% [  H; w. B$ E8 t2 k2、树(如二叉树、平衡树、红黑树)。0 ]+ D$ |& P* Y0 K5 c

' [: X3 F6 z( p) o) b9 B1 k6 I3、图(如最短路径的几种算法)。* d7 X9 p7 o, u

6 `  i( N- Z7 t" d$ g, [2 v- _4 n4、队列、栈、矩阵。# |: w6 p- _" a( t# h0 [7 g; y

6 W7 t# z0 ~2 T$ `对于这些,自己一定要动手实现一遍。你可以看书,也可以看视频,新手可以先看视频,不过前期可以看视频,之后我建议是一定要看书。& N; g' B' g2 U* S# e

' j3 Q8 _  [$ w3 K例如对于平衡树,可能你跟着书本的代码实现之后,过阵子你就忘记,不过这不要紧,虽然你忘记了,但是如果你之前用代码实现过,理解过,那么当你再次看到的时候,会很快就记起来,很快就知道思路,而且你的抽象能力等等会在不知不觉中提升起来。之后再学习红黑树啊,什么数据结构啊,都会学的很快。
, d5 Y# R7 V& C8 g2 k9 ^1 y; ?8 D, U5 L3 M2 ^. z
对于有哪些值得学习的算法,我之前也总结过,这里推荐给大家程序员必须掌握的核心算法有哪些?,这篇文章居然 40多万阅读量了,有点受宠若惊。
) X1 ]" F# ]+ m& x" h1 K6 s' \7 x* v% p" I: Y/ r- O
最最重要
; i# c& f1 {) O/ Y7 k4 X$ w) a* P1 G5 F; B8 G9 U8 M6 f( ~7 A0 y
动手去做,动手去做,动手去做。重要的话说三遍。
5 l8 P9 G1 o$ p1 Y5 V5 v0 K( R; ?, [' ]- [( w, l
千万不要找了一堆资源,订好了学习计划,我要留到某某天就来去做…
+ A( z# @  ^. \& `/ L$ a) B& w  U6 z& Z9 X4 ?$ k
千万不要这样,而是当你激情来的时候,就马上去干,千万不要留到某个放假日啊什么鬼了,很多这种想法的人,最后会啥也没做的。- \$ g, n+ Y$ V- i! @. f
3 a( B9 c  x: b. T! ~
也不要觉得要学习的有好多啊,不知道从哪学习起。我上面说了,可以先学习最基本的,然后刷题,刷题是一个需要长期坚持的事情,一年,两年。在刷题的过程中,可以穿插和学习其他数据结构。
# n1 Z2 a8 d8 @, T# t1 X4 i' P& {( P" h8 p% g" `1 Z; s
总结一下吧6 s8 T6 e( V; S4 o
. |, ~  d  Z' y7 s& b
所以我给大家的建议就是,先学习基本的数据结构以及算法思想,不要盲目刷题,接着刷题的过程中,不能得过且过,尽量追求最优解,还有就是要跳出舒适区,逼自己成长,刷题的过程中,要学会分类总结。
5 {( o1 `" B8 }
5 A" ]* {) P6 |当然,最重要的,就是你去动手了,不然,一切免谈!
+ P8 ?& f) N$ j' j, u- l
# p$ d2 ?% d6 G) w0 O1 |- o看在熬夜写过的份上,送我个赞呗,嘻嘻。
& s' H7 U( T5 ?# V0 q) Y————————————————
+ `6 ?% r6 w2 x+ o3 J4 q1 j# a" @; P版权声明:本文为CSDN博主「帅地」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。8 r& n9 A" v- F8 Z2 e
原文链接:https://blog.csdn.net/m0_37907797/article/details/104765116
7 J/ \* G% V) m6 p8 c% h, j! C) i$ K8 Z7 _& e) A

% a- C0 l* H: A1 t- k: A& x& {+ t




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