数学建模社区-数学中国
标题:
算法越学越扎心,有没啥破解之法?
[打印本页]
作者:
杨利霞
时间:
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
这道题我在以前的分章分析过,不懂的可以先看下之前写的:递归与动态规划—基础篇1
0 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 o
public 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+ [
1
4 ]$ ]# h/ z7 U
2
% E) `0 ]3 ]7 V* P
3
2 N3 H2 N+ ?$ g( o! X8 j
4
8 T; z" M0 N" ?3 ^& O* Y
5
8 |. Q7 D; w; i3 Q) Y6 v1 U
6
3 ~$ M6 c5 \+ p# I
7
& 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, ~
1
4 ~ P0 O( v, y/ ^: S$ Y
2
8 N" y" p7 F2 |. ~ v9 z
3
( H5 ]% O, m7 g; T3 F: @
4
1 a/ W' ^8 C" w: [2 j8 m; s
5
# A2 l0 G% U! S/ b1 K
6
6 ?# p3 y3 K% f' {1 n
7
. p& f; H" D) n, ~4 [
8
& e4 b, e& E( Z( g4 N Z
9
4 T( D: n" L3 O' s3 s7 g
10
% \6 s# ?+ z; m- u( w$ a" g* T! w
11
7 ]# D6 l% _5 a; N/ Y% C M
12
" z, y) }. g4 A4 n+ \5 ?% X7 H
13
3 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; `! O
1
# b7 Y& E0 Y* a3 {1 q9 S( w! q
2
& h/ d4 `* }) H P
3
0 q- F0 p; {' x6 E0 Z9 J7 v. V
4
1 P/ r }, E6 O6 H
5
6 H/ s4 |( G% {3 T2 n
6
, t7 g8 ]. f+ l9 L) R1 a H
7
5 y& W$ }! F7 E) v3 q! {
8
8 Z, F1 N6 h7 `& D! L
9
0 k4 ?+ l9 s9 \2 Z. Q
10
( E6 y& v7 s+ d3 ]+ q& k+ z c
11
?) I3 s/ R% y" w! Y. G
12
9 q2 s: q1 s: B& z
13
% 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 e
3、可以从简单暴力入手做一道题,在考虑空间与时间之间的衡量,一点点去优化。
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, n
leetcode有中文版
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 @$ {$ A
1、链表(如单向链表、双向链表)。
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 r
3、图(如最短路径的几种算法)。
% \) V) n# h! G9 ?0 \ b0 U
- @+ o5 R8 b" O0 s; G, J7 j
4、队列、栈、矩阵。
, 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, p
5 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