; p0 e# J# u) M4 z. B! X4 B- O$ L' F衡量一道算法题的好坏无非就是时间复杂度和空间复杂度,所以我们要力求完美,就要把这两个降到最低,令他们相辅相成。 }7 v" i- s! j, {, r% P* R 6 t: `0 a5 H6 \+ A ^我举道例题吧: ) {+ J2 O% m& x& M1 Y( ]1 m- D: N- {) f2 z
问题: 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法? * e! Z7 _. _- U! ^% o 5 t1 c2 s8 j5 g c+ S! Z这道题我在以前的分章分析过,不懂的可以先看下之前写的:递归与动态规划—基础篇1 : |) H6 C0 [9 j: o% ?/ ^) K + ]% T) n- |6 y方法1::暴力递归- a! L2 Y& j. U: u$ O2 `) d3 w K
/ Q A7 V6 ?8 D c3 {' C
这道题不难,或许你会采取下面的做法:* h, d1 K9 s6 P3 R( L+ A4 U9 a: v
1 m) A+ | y$ M1 w+ s6 w2 V% V4 F) i
public int solve(int n){" }/ d* v) ?# r1 R1 R
if(n <= 2){4 Q- E% m! l G. i' ?7 J- R- y
return n; % G8 `7 E0 e5 k+ \ }else{ + y3 H- q. Q2 ]- X/ G( R return solve(n-1) + solve(n-2); 3 K% ], y* v3 J; L3 B0 G1 S }+ I2 I' E3 n5 d# s y
}' n* Z( r, Q1 p* J4 B: A0 C
1 4 ~+ q8 p: w: L2 b8 F( h7 l) z) N$ b0 `1 m2. }. P4 ~& T% x" B8 W1 w
3) H0 F( P2 ~% [' T+ x4 j+ i
47 a7 D3 u3 o8 X7 `
5 8 _; X8 p4 h: b5 h" e! [3 {6 ^. N6 - } p4 @1 l1 o8 M, @7 ' Q0 m! R3 h7 C" T, q- Z/ M这种做法的时间复杂度很高,指数级别了。但是如果你提交之后侥幸通过了,然后你就接着下一道题了,那么你就要好好想想了。& r, l0 V5 r; {1 k' Z; I
# a' S/ K. W3 _3 K$ t$ D1 K
方法二:空间换时间 . g% h/ _6 K; O5 F4 C3 c . j8 b( l, k. z3 |8 Q" K力求完美,我们可以考虑用空间换时间:这道题如何你去仔细想一想,会发现有很多是重复执行了。不行你可以画个图 : l3 j9 Q0 U& U) \4 ~+ K+ [' q ! n! T4 \3 T1 _* K( j8 C5 L所以可以采取下面的方法:& |" l: B( N1 o- W8 ^! z* c( d
0 S4 F1 o8 x8 ]. r; F- }& y9 t
//用一个HashMap来保存已经计算过的状态 / H; W+ Q& v& q3 B" T6 f6 s3 Ystatic Map<Integer,Integer> map = new HashMap(); ?) M2 s+ h4 T" M* a
public static int solve(int n){ % Z' h. t' E5 w! ~$ c* _: f' [ if(n <= 2){* `9 g& D _; W- m
return n;" }! z) n4 E, U& ?4 Q3 `
}else{//是否计算过% g) x% N$ z/ I0 J
if(map.containsKey(n)){5 Y: w7 q6 M$ K0 [$ f0 Y/ s$ M
return map.get(n);( P. @( v6 ?( W+ z( u( ~9 u
}else{. K3 C6 i; v$ e: `* G6 ~
int m = solve(n-1) + solve(n-2);" `8 L( l9 f3 C( Y2 ?; ]; p- c
map.put(n, m);6 K: x7 V- [1 B7 B
return m; 9 {* U, ?& {7 V+ c$ K0 c } 0 b. O P2 k6 g2 { } ; Q' {; _/ e6 v. l$ J}8 y8 p3 ^3 j7 [( r
5 h! N" Q& `, n$ E- u# N; @
1! t% O0 R6 o% Q) K2 S
2/ f% [1 ^+ z7 m0 w
3 ' T- c' P, s4 _" t4 2 q; ^ G" Y4 H5 ! h9 P C8 J4 D: C, C- F0 b6) n7 S' _' }$ W. t
7 } i) r/ V* {5 Q1 d |
8 z, |* W7 B) E' `4 u" E% A, R
9& _0 `8 W H2 g% T) f; N' m
10+ ~" M& F3 X. S0 Z O9 S9 o
112 o) C3 c3 M, u" b7 s; n
12# C! [! c: V; n1 a, @2 q, U: o
13 6 V+ H" ^5 s# r0 A14 - `1 i7 B( H1 H0 c. ^2 e15 0 H9 z# s' c1 s+ U2 N c16, C; i2 N/ }6 t+ W5 v$ _: _
这样,可以大大缩短时间。也就是说,当一道题你做了之后,发现时间复杂度很高,那么可以考虑下,是否有更好的方法,是否可以用空间换时间。 L+ ~$ {( N1 K* H" p5 H
$ v( Y$ J8 A1 K! r! `! y
方法三:斐波那契数列9 j# c0 I3 L# [
1 W; q7 @5 U6 d7 s$ R实际上,我们可以把空间复杂度弄的更小,不需要HashMap来保存状态: ; ^' P! a0 }: B; ^8 D9 a# f 8 B3 y$ Y' K& g+ y/ X- ~public static int solve(int n){ , _' S7 g) S+ K if(n <= 2){/ ^1 d" k) E6 w j9 [4 [
return n;1 p5 c% r' R. C' v
} ! z" E1 h8 @/ S9 V t
int f1 = 0;& y% B/ @. \# F& D3 y% i6 \( a: J; B
int f2 = 1;8 |) m7 m3 R: j# D! |; Q
int sum = 0; L5 J1 C0 ~) ?/ O8 b0 g+ _- r for(int i = 1; i<= n; i++){ ! s4 u1 E8 |; |! i# c5 I sum = f1 + f2; # J8 g( F, {9 D5 ]: | f1 = f2;. F$ x$ c8 |3 c4 M: I
f2 = sum; ( j1 o$ t5 i5 S) B }' Z: ~. M8 ~7 G% I- A
return sum; / h$ D' f8 m& N9 D& b: ]0 p} , G* s, b5 G3 d4 Z/ K" G1 + x/ ~' J# W3 A% Y. e# x2 , B# N- t( V5 y! z0 ]% h3 ! Z2 \' C8 t, K4 & r7 m- U; x% A$ D, D" @$ M5 . |" J: Z: i3 \6 9 j9 M( v( w0 k e _/ B7 , J) P+ S" m9 Z! {80 l0 Z' X1 `+ c6 a( i) o2 [5 L
9 ; ^. a/ Z0 ]' F0 X# H10 & }; E/ g1 z' h/ y- D11* e+ B0 k5 Z; n2 f, A$ W. q
12 + D7 K5 V/ v; H- D, ?9 @) F13+ A( l& @+ H! N }: G' W
14: v, u' o+ w7 K" Y D$ ?
我弄这道题给你们看,并不是在教你们这道题怎么做,而是有以下目的: ( N7 M& u5 y. [! X: r7 C5 J, b }# |
1、在刷题的时候,我们要力求完美。4 a0 ?$ W j* B# G% a7 H
6 H& C9 u. Z( p( _3 l ?. H2、我想不到这些方法啊,怎么办?那么你就可以去看别人的做法,之后,遇到类似的题,你就会更有思路,更知道往哪个方向想。 ' ~7 ~0 I* V6 \ ; K9 ]1 I4 k5 c7 o8 n2 a- u7 }3、可以从简单暴力入手做一道题,在考虑空间与时间之间的衡量,一点点去优化。9 \9 o, R: E, `" Q
$ p. u" X8 o! I5 C挑战自己,跳出舒适区- _7 E) N- e+ s/ L, ^9 S+ V
8 W4 _4 B6 L* x什么叫舒适区?在刷题的时候,可能有一类题是你比较懂的,你每次一看就有思路,然后半个小时就撸好代码,提交代码,然后通过了,然后,哇,又多刷了一道题,心里很舒服。! d. a* @$ N) \9 x0 b, l
3 L( `) P+ F f- e- Q但是,记住,前期你可以多刷这种题练手,提升自己的乐趣,但,我还是建议你慢慢跳出舒适区,去做一些自己不擅长的题,并且找段时间一直刷这种题。例如,我觉得我在递归方面的题还是挺强的, ' y7 l; }& `, |5 Y Y. p: N但是,我对动态规划的题,很菜,每次都要想好久,每次遇到这种题都有点害怕,没什么信心。不过有段时间我觉得只刷动态规划的题,直接在 leetcode 选定专题,连续做了四五十道,刚开始很难受,后来就慢慢知道了套路了,一道题从两三个小时最后缩到半小时,简单的十几分钟就搞定。感觉自己对这类型的题也不惧怕的。 . t" V* P8 P& Y# _ e" H. q3 N8 e4 s' v' C6 ~
当然,对于动态规划的学习,大家也可以看我这篇广受好评的文章:为什么你学不过动态规划?告别动态规划,谈谈我的经验 - }9 q: b- L7 s1 p% I, x) x+ N3 O2 m
所以,建议你,一定要学好跳出自己的舒适区。! u4 f! B6 K3 \! e2 _' p
, [1 K$ i2 t# H3 V F S
一定要学会分类总结 ( x2 ?. n4 q; `! a6 B3 \. t9 h1 |: n$ o
有些人以为 leetcode 的题刷的越多,就一定能越厉害,其实不然,leetcode 虽然有 1000 多道题,但题型就那么几类,我们前期在刷的时候,我是建议按照题型分类刷题的,例如我这整理刷二叉树相关,然后刷链表相关,然后二分法,然后递归等等,每刷一种题型,都要研究他们的套路,如果你愿意去总结,那么 leetcode 的题,其实你刷几百道,有目的、挑选的刷,我觉得就差不多了。+ {! x! ^, X" g7 ^1 }
* C2 v/ Q" W0 }2 `/ n我看过一本书,叫做《程序员代码面试指南:IT 名企算法与数据结构题目最优解》,这本书就非常不错,里面按照栈,队列,链表,二叉树,字符串等一个专题一个专题来刷的,并且每道题都给出了最优解,而且里面的题有一定的难度,感兴趣的,真心不错,如果你把这本书的题全部搞定,并且总结相关套路,那么你的算法一定有很大的提升。 V) P7 q' a5 L% u2 Z : |4 K8 ^% u- O! Z推荐一些刷题网站0 H w1 B+ v. O
; M, U F2 t; A- ]4 B) g. T- U
我一般是在leetcode和牛客网刷题,感觉挺不错,题目难度不是很大。) A+ D, |. T# s6 H( R3 y. u3 I
7 O/ d/ f" s# U
在牛客网那里,我主要刷剑指Offer,不过那里也有个在线刷leetcode,不过里面的题量比较少。牛客网刷题有个非常方便的地方就是有个讨论区,那里会有很多大佬分享他们的解题方法,不用我们去百度找题解。所以你做完后,实在想不出,可以很方便着去看别人是怎么做的。 ( u- A" |% w/ q1 {9 v+ ~( i) o' S6 N( t( I# n
至于leetcode,也是大部分题目官方都有给出答案,也是个不错的刷题网站。你们可以两个挑选一个,或者两个都刷。2 q; e. m9 w8 ^
0 @8 l b7 W- |8 Y+ j8 d% u: |当然,还有其他刷题的网站,不过,其他网站没刷过,不大清除如何。' \1 v+ g7 f7 L
' B# _+ H. D4 t; F0 F/ _% @) P- X2 K
至于leetcode,有中文版和英文版 ; N5 H! A6 D' s% L w7 h1 Y4 U7 b( ^
leetcode有中文版 . Z. e I2 u6 ]: z F7 @6 n" {/ ^7 Y* \( L
英文版 G: G/ D2 t# W9 S1 b& t7 k9 R0 Z9 s0 R0 a6 Z" k' |
根据自己的兴趣选。 . c# R4 i( o8 D( Z2 o) ?$ e8 } o/ e; x! n* ]7 p% r
学习一些解题技巧 ) T6 U, Q& ~ _ 7 F* o; q, r% }( C$ a说实话,有些题在你没看别人的解法前,你好不知道有这么美妙优雅的解法,看了之后,卧槽,居然还可以这样。而我们在刷题的过程中,就要不断累积这些技巧,当你累计多了,你就会形成一种 & Q( G+ ?8 v1 g7 p神经反应,一下子就想到了某种方法。解题技巧很多,例如数组下标法、位图法、双指针等等,我自己也分享过一篇总结一些算法技巧的文章 ; o. z5 @0 w* b/ X% {/ m8 |9 e( ]! I) z6 f* H* w4 o
推荐阅读:一些常用的算法技巧总结 4 U0 e' t- R. Q( \; c* r0 E% [( C9 G9 Y+ ~- Z) T4 n
例如在刷题的时候,我们要学会巧用双指针、数组下标法、位运算等等技巧来解决问题,可能会有意想不到的效果。我给你再找点我之前写文章的一些例子吧:2 o/ Y/ V8 q$ n& m8 S) X
0 ]5 w, ^- G0 n分享一道解法巧妙的算法题 & d( T1 M: Q2 j. o2 t# G! S) a& _! q5 G9 M6 ~+ z0 [2 M, }$ z8 T
【算法技巧】位运算装逼指南 7 H; |6 f6 P7 l3 ?6 o) h1 R( Y C; { * |- v# n$ v, D这是个长期累积的过程,我自己也精彩在我的公众号里分享一些解题的文章,感兴趣的可以关注我的公众号:帅地玩编程。 + [7 f* d0 m6 F% |% o l3 c 1 [1 N1 J+ V, U. C5 K再说数据结构发重要性' v. ], z1 X$ Z" e
6 y# A9 M7 D2 r0 H, h- U" Q前面我主要是说了我平时都是怎么学习算法的。在数据结构方法,我只是列举了你们一定要学习链表和树(二叉堆),但这是最基本的,刷题之前要掌握的,对于数据结构,我列举下一些比较重要的: ( T% V# @5 \9 c: Q9 ?% ], \ X9 P6 S0 F# d1 W& V: u
1、链表(如单向链表、双向链表)。 8 r# f6 w M! Q- c# a5 H # H8 |8 x& G' F* X/ Y' @. b! ?2、树(如二叉树、平衡树、红黑树)。 + K8 Z% `. |) t0 V8 W# c. b9 E c) E
3、图(如最短路径的几种算法)。( @" r/ e' `+ u- \; Y6 b+ T- q