数学建模社区-数学中国
标题:
算法越学越扎心,有没啥破解之法?
[打印本页]
作者:
杨利霞
时间:
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! a
1、常见数据结构:链表、树(如二叉树)。(是的,链表和二叉树是重点,图这些可以先放着)
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' ]- Z
7 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 {$ S
AC不是目的,我们要追求完美
! `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( {' A
1
_* M) g3 ^7 _3 z# z' o
2
5 w0 e0 T2 E" \. e s- H& n: \5 t
3
$ x& o* x8 S/ l
4
3 I/ |, ^% [$ G' i# W
5
' A9 g& X3 ?5 l7 w7 O
6
f3 N) E8 B/ ~% i4 Y, O
7
' 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# o
static 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& d
1
, z T# ^: m) j3 {/ X# ^& M/ i$ x" z
2
1 d$ [! d, `, ?9 V
3
% j4 v+ O/ k2 ^) h
4
1 \- O) N& Z. T) c6 S/ A
5
/ J8 X4 O6 r `
6
! j. R# q) {+ J% W$ y/ |
7
3 \% @8 Z2 x. R9 z6 C4 {
8
# q* v, ]6 V4 {
9
8 L, K+ g/ V8 T# a
10
* g# t" I- l: t
11
7 @. }9 g4 c* k
12
% [; 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 F
16
/ }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& w
public 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
1
3 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' \$ ~( W
6
8 D B4 X- X A! t8 S/ W0 {
7
5 R& q, ]& \7 k! {$ ?
8
5 Z5 y. m% J% u* \9 n
9
2 E6 z, b% M7 T+ a, c- E
10
) [5 ?/ e3 u }% t; T4 j' U
11
( T% A0 y( t- y, u
12
& 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! Q
2 p- ]6 Z8 N! a+ I
1、在刷题的时候,我们要力求完美。
7 H4 G4 k& x6 Q4 h
7 S3 ]3 s9 K4 c* V, H9 v% u; g
2、我想不到这些方法啊,怎么办?那么你就可以去看别人的做法,之后,遇到类似的题,你就会更有思路,更知道往哪个方向想。
+ D. a4 I* Y. {1 n
, `) n- p4 o' }2 c2 w1 y
3、可以从简单暴力入手做一道题,在考虑空间与时间之间的衡量,一点点去优化。
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! _/ t
0 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- E
7 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 d
1、链表(如单向链表、双向链表)。
9 y3 G/ u0 ^1 K2 r& W1 E
, G3 j9 Y! _) @6 e/ ^8 l
2、树(如二叉树、平衡树、红黑树)。
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