QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 4443|回复: 1
打印 上一主题 下一主题

❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏)

[复制链接]
字体大小: 正常 放大
杨利霞        

5273

主题

82

听众

17万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组2018美赛大象算法课程

    群组2018美赛护航培训课程

    群组2019年 数学中国站长建

    群组2019年数据分析师课程

    群组2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2021-7-8 15:06 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta

    # B3 E! y, F% y$ Z+ Y7 N❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏)
      K) r7 T7 k$ t8 `* S+ U
    6 a' C! V  T4 b, h7 `前言
    ( Y0 v, e' I" a! v: @* Q  所谓活到老,学到老,虽然我感觉自己已经学了很多算法了,但是昨天熬夜整理完以后发现,自己还是个弟弟,实在忍不住了,打算把 算法学习路线 发出来,我把整个算法学习的阶段总结成了五个步骤,分别为: 基础语法学习(重要)、语法配套练习、数据结构、算法入门、算法进阶。本文梳理了这五个大项的思维导图,在下文会有详细介绍。& O3 l* R3 N- e4 ^' M( _
      希望各位能够找到自己的定位,通过自己的努力在算法这条路上越走越远。
    2 x+ b/ B, d# {( t3 H; V) H  刚开始切勿心浮气躁,千万不要给自己立 flag,说一定要把这么多东西都学会。就算你的精力旺盛,日夜操劳,时间也是有限的。所以,首先是明确我们要做什么,然后制定好一个合理的 目标 ,再一点一点将要学习的内容逐步付诸实践才是最重要的。8 }. |$ a1 P/ \4 N2 D( c( |
      每日一篇C语言打卡,目前更新到:光天化日学C语言(20)- 赋值运算符与赋值表达式 | 让代码变得更加简介(建议收藏)。# u) m* l8 O" |' Q

    3 o# |& M3 o1 }! b+ c

    4 ?: j5 j! y8 x5 x9 N+ I  J
    + I- P& s+ b# [* G9 V

    # ^. y8 ^% F! ^9 |) D& ]/ u# u1 j  k: ~) n( L8 t( b
    1 S( E) {. P5 _. z
    ; d6 n% W+ g) o" l9 l& o0 R  J

    1 k' P( a1 X. g  \1 b/ u图片较大,文章中有拆解,需要原图可以留言找我要哈
    - C  {* j6 C& X4 O1、基础语法学习1 F( z6 u. e' J1 X2 ?8 A
    算法是以编程语言为基础的,所以选择一门编程语言来学习是必须的。7 Y+ E: F% P, `# w$ [) U$ L
    因为作者本身是C/C++技术栈的,所以就拿C语言来举例子吧。如果是 Java、Python 技术栈,可以跳过 C语言相关的内容。这一小节,先给出学习路线图,然后我再来讲,每部分应该如何去学。" f6 X0 u1 j- u" |

    & b# O9 J8 ^4 T( ~) k! s3 H% `

    5 \3 y. a9 D; Y- d. R' R
    , D! C; P) n6 @# F" o. N2 c

    9 a# E. a: t) L1 p4 D1)HelloWorld
    ' q+ O7 {: p. p& r% Z5 c无论是 Java、Python、C/C++,想要上手一门语言,第一步一定是 HelloWorld,先不要急着去配环境。如果环境配了几个小时,可能一开始的雄心壮志就被配环境的过程消磨殆尽,更加不要谈日后的丰功伟业了。
    8 U$ v+ R# ~# o2 f$ @1 Y: B  T/ B2)让自己产生兴趣2 q/ t! M% B# H
    所以,我们需要让这件事情从一开始就变得 有趣,这样才能坚持下去。比如找一个相对较为有趣的教程,这里我会推荐这个:《光天化日学C语言》。听名字就比较搞笑,可能作者本身也不是什么正经人,哈哈哈!虽然不能作为一个严谨的教程去学,起码可以对搞笑的内容先产生兴趣。从而对于语言本身有学习下去的动力。
    9 m: G3 J2 i6 U& K! S刚才提到的这个系列,可以先收藏起来。回头再去看,它讲述的是 对白式 的 C语言教学,从最简单的输出 HelloWorld 这个字符串开始讲起,逐渐让读者产生对C语言的兴趣。这个系列的作者是前 WorldFinal 退役选手,一直致力于 将困难的问题讲明白 。我看了他的大部分教程,基本都能一遍看懂。算了,不装了,摊牌了,因为我就是这个作者。
    / v/ ^+ f" f1 Z7 A8 b* J3)目录是精髓
    & X' q0 A7 r" ~, r然后,我们大致看下你选择的教程的前几个章节,那些标题是否有你认知以外的名词出现,比如以这个思维导图为例,前几个章节为:
    ) ~, \) ]. o" t" A1 u7 T' k1、第一个C语言程序
    5 z1 u1 J0 }' {. P. E2、搭建本地环境+ }" }# W+ }4 [8 Z' y3 q9 H
    3、变量' ~1 m: ~& t( I
    4、标准输出
    3 m/ P1 E2 u0 A4 m* @* a; U5、标准输入
    ) F' Y) ^# i7 O  f& s+ F0 D6、进制转换入门
    8 y1 q6 i& g; b; @: q6 t9 v/ K9 \! S7、ASCII字符& ]+ `8 E9 I: e  }
    8、常量
    1 @$ ?8 Z- ^. n/ R3 _
    / W' Z& ]7 S9 P' E6 }) I$ E7 d

    - L0 _, m6 Y; Q7 E如果你觉得这些名词中有 3 / 4 以上是没有什么概念的。那么,可能需要补齐一些数学、计算机方面的基础知识。反之,我们就可以继续下一步了。
    ' o3 S( D6 w) m; r0 [4)习惯思考并爱上它
      y4 c1 T6 E. ~  @% H只要对一件事情养成习惯以后,你就会发现,再难的事情,都只是一点一点积累的过程。重要的是,每天学习的过程一定要吃透,养成主动思考的好习惯。因为,越到后面肯定是越难的,如果前期不养成习惯,后面很可能心有余而力不足。. r9 [8 V7 ~& T3 Y( U: K
    就像刷题,一旦不会做就去找解题报告,最后就养成了看解题报告才会做题的习惯。当然这也是一种习惯,只不过不是一种好习惯罢了。
    $ J0 I3 {) G! _5)实践是检验真理的唯一标准
    9 u2 t1 ?3 Z) j2 Y  [' p% j. I光看教程肯定是不行的,写代码肯定还是要动手的,因为有些语法你看一遍,必定忘记。但是写了几遍,永世难忘。这或许就是写代码的魅力所在吧。# U" D7 n6 F) K5 X! t% E
    所以,记得多写代码实践哟 (^U^)ノ~YO
    6 h8 k/ V' g  T6)坚持其实并没有那么难7 m+ ^: c4 I+ N4 e  k" s* N
    每天把教程上的内容,自己在键盘上敲一遍,坚持一天,两天,三天。你会发现,第四天就变成了习惯。所以坚持就是今天做了这件事情,明天继续做。
    3 p8 v! f8 e9 L  k: A7)适当给予正反馈
    5 H- K; Q" M0 b8 A$ G然而,就算再有趣的教程,看多了都会乏味,这是人性决定的,你我都逃不了。能够让你坚持下去的只有你自己,这时候,适当给予自己一些正反馈就显得尤为重要。比如,可以用一张表格将自己的学习计划记录下来,然后每天都去分析一下自己的数据。
    1 A. t4 O1 X! C- y' @4 C. J当然,你也可以和我一样,创建一个博客,然后每天更新博文,就算没有内容,也坚持日更,久而久之,你会发现,下笔如有神,键盘任我行!更新的内容,可以是自己的学习笔记,心路历程 等等。
    9 n) R. D- w& F: t3 F: h' O看着每天的粉丝量呈指数级增长,这是全网对你的认可,应该没有什么会是比这个更好的正反馈了。
    ( w7 d3 w; Y! j+ P; O8)学习需要有仪式感2 Y6 O% G% i2 ?) U8 K* M9 a* z
    那么,至此,不知道屏幕前的你感想如何,反正正在打字的我已经激情澎湃了。已经全然忘记这一章是要讲C语言基础的了!
    9 o$ o7 s) a& n/ c. s  D( a" S. {介于篇幅,我会把C语言基础的内容,放在这个专栏 《光天化日学C语言》 里面去讲,一天更新一篇,对啊,既然说了要坚持,要养成习惯,我当然也要做到啦~如果你学到了哪一章,可以在评论区评论 “打卡” ,也算是一种全网见证嘛!
    $ A! X5 n4 }- ?' S5 ^我也很希望大家的学习速度能够超越我的更新速度。! U; n. \7 @6 c( w
    2、语法配套练习
    - m9 `9 U7 {6 `学习的过程中,做题当然也是免不了的,还是应征那句话:实践是检验真理的唯一标准。
    / K4 o$ Z# q, p' s! w3 {. e' V而这里的题库,是我花了大量时间,搜罗了网上各大C语言教程里的例题,总结出来的思维导图,可以先大致看一眼:
    . R2 ]. C1 I! w* ?3 g" g9 I, f' N/ M4 D- h/ K' F% [. c
    - S) t* r" h3 g/ f2 R1 g) |
    " m/ Y5 b5 s1 r; f( ^  O7 N

    ; V8 y& ^: Y3 B' z从数学基础、输入输出、数据类型、循环、数组、指针、函数、位运算、结构体、排序 等几个方面,总结出的具有概括性的例题 100 道 《C语言入门100例》,目前还在更新中。% j: {+ c, q) x" [* p; L" P
    这里可以列举几个例子:
    % L0 E3 Z- R1 ^! ^1、例题1:交换变量的值
    7 |# i9 w& G4 k- I6 P一、题目描述  ^- k/ s8 b" c, I  w
      循环输入,每输入两个数 a aa 和 b bb,交换两者的值后输出 a aa 和 b bb。当没有任何输入时,结束程序。2 R: m4 h& [% Y

    : \& S0 X* g* G* k/ a
    - G* \( o. U" C. @' a

    & u3 v/ E7 M7 r8 k
    & x7 M6 F; A! Y0 M% o4 R) f: T, I
    二、解题思路9 b+ P8 ]; \, C( D
    难度:🔴⚪⚪⚪⚪7 g0 s% E: C) J# a: l1 h. k1 `

    ! A( p9 }: U' d0 [, f
    " t# B5 R0 s' y" T9 B+ X
    这个题的核心是考察如何交换两个变量的值,不像 python,我们可以直接写出下面这样的代码就实现了变量的交换。4 \) B( |& g4 X( G# \/ c4 ?& X; b
    a, b = b, a/ O* L, \: m  E7 d) M; `$ \/ r# U
    1: N; o0 |" M/ \4 k3 K
    在C语言里,这个语法是错误的。8 f" x9 p! ]0 w* V) R+ @$ n4 C
    我们可以这么理解,你有两个杯子 a aa 和 b bb,两个杯子里都盛满了水,现在想把两个杯子里的水交换一下,那么第一个想到的方法是什么?: o2 j6 l  w$ T' g) |
    当然是再找来一个临时杯子:9 N: t6 }% Q, `* E
      1)先把 a aa 杯子的水倒进这个临时的杯子里;" O+ c8 o: q  w$ ^
      2)再把 b bb 杯子的水倒进 a aa 杯子里;- Q3 M/ I9 e% ?5 `& M& N6 Q( b; X
      3)最后把临时杯子里的水倒进 b bb 杯子;
    ) x; V, B$ ?# k4 w- g5 n6 J' [; F' f7 ^
    6 B8 u" f# \! q- M. Y
    这种就是临时变量法,那么当然,还有很多很多的方法,接下来就让我们来见识一下吧。. C& p$ q" g% K  C+ e' ]

    0 n% f; _. Q5 A2 W* H, M

    ! c( f# Q9 Q. y) ]( w, O7 z三、代码详解
    % k3 O7 G: T, r5 I- u1、正确解法1:引入临时变量
    . B& R! _% d2 E0 U3 Q; {0 J3 y#include <stdio.h>/ m3 E  _  @( s! R0 J$ `, m+ N
    int main() {
    4 B8 A( S' f4 R. j6 m& r    int a, b, tmp;; X! S0 R; G5 y, Y8 C! Q
            while (scanf("%d %d", &a, &b) != EOF) {
    9 ~% C' o: |9 x. T( H- I            tmp = a;   // (1)
    9 @2 m. H! E/ [. l& ~) V            a = b;     // (2); x. g. a* [7 v8 J! y
                b = tmp;   // (3). B1 |& Q! p/ Z$ B; i, P
                printf("%d %d\n", a, b);
    / K. n' Q* A6 |; J        }
    + U& h. Y# T; o, O$ P8 z6 |6 k        return 0;
    6 [( ~8 m5 s. v}& M0 x: f6 u2 W9 t4 l
    1
    8 d6 U- l% N5 @, B23 j2 V0 @5 z+ c. Y4 z7 Y9 O
    3
    . [& v4 J- }1 ?$ j# p' f$ y2 u4
    1 x2 t* @- U; V2 a( m5
    & k+ x: l1 I  _) k+ ~6
    ' w0 o) W1 A* N6 x* C; R% [9 B76 w4 c/ p% ^% p0 n( |
    81 U* f/ r- e. M' Q7 [$ k
    9$ G- a" T' d7 ^$ C
    10$ W# K, U  ]* w7 J( Y
    11* W' F9 b" F* g3 P# C
    ( 1 ) (1)(1) tmp = a;表示把 a aa 杯子的水倒进这个临时的杯子里;" P% _; r2 ?+ i3 T
    ( 2 ) (2)(2) a = b;表示把 b bb 杯子的水倒进 a aa 杯子里;5 ~9 Z) y3 N8 Y8 `5 ^# O
    ( 3 ) (3)(3) b = tmp;表示把临时杯子里的水倒进 b bb 杯子里;  v  g( H( X. H- q
    这三步,就实现了变量 a aa 和 b bb 的交换。
    4 n8 h; b( N: m1 f5 X5 T! D2、正确解法2:引入算术运算9 N8 d* K% v- X7 R- {4 w
    #include <stdio.h>0 V( y* r& \) Z1 s* y
    int main() {
    : t6 `2 l) h8 L/ ?' k+ _# X) q- ?    int a, b;
    ' C- z4 v# q; a! y6 y. S1 b        while (scanf("%d %d", &a, &b) != EOF) {
    . H; Q5 r  a, @2 F% h            a = a + b;   // (1)
    5 D1 E5 t& m! h5 b            b = a - b;   // (2), r- O+ x9 b  r( g" e8 I9 S
                a = a - b;   // (3)2 m* |$ M! z' w9 `4 O( d+ s/ C
                printf("%d %d\n", a, b);9 h/ ?8 a, o  ~* M6 q8 W: r
            }
    9 j7 x6 q% l% ~8 e        return 0;
    ; i9 k" {9 R# z2 F5 ~}
    ; y' e3 B9 s3 l1 O( g1: f: H8 ~( m2 A8 ^  t* y
    21 v* j) v) C! r; I' G4 D* d; d- w
    3
    . [' r5 P2 |/ ]5 m0 R41 N: f* ^. h+ S0 F4 K) R) b8 o
    5
    % q7 |" E: q4 h6" t! s9 D, P" y4 C
    7
    6 |9 o1 \' E0 U# b' h0 B1 b87 [) i* a" }$ ?
    9+ E2 s4 [5 K6 _& Z
    10
    , U, d5 H8 E* ]11
    : i4 e/ ?6 K) x( 1 ) (1)(1) a = a + b;执行完毕后,现在最新的a的值变成原先的a + b的值;
    7 K/ c8 v8 `1 q8 V5 l. [1 B( 2 ) (2)(2) b = a - b;执行完毕后,相当于b的值变成了a + b - b,即原先a的值;3 [4 v. q& C7 @2 ?
    ( 3 ) (3)(3) a = a - b;执行完毕后,相当于a的值变成了a + b - a,即原先b的值;
    / H' ~5 S3 v9 R8 P从而实现了变量a和b的交换。! N2 R' ~; ]% g5 E! H
    3、正确解法3:引入异或运算( f) M. C0 R% s' j* b8 V1 D
    首先,介绍一下C语言中的^符号,代表的是异或。
    / k0 D- h+ N" f6 ^+ u二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:
    9 R8 W- [$ @; J; v; ~0 U左操作数        右操作数        异或结果
    % @3 U3 g5 y1 l7 C& l( w2 H0        0        0. G: Z7 y) P4 E, r4 W6 J7 g0 Y
    1        1        03 b8 D% y) D/ J7 O: \+ X
    0        1        1
    8 y7 R' Q" v* H! x3 g1        0        16 |* [" V# t( K
    也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。) K% [8 I0 @! ], t" w
    这样就有了三个比较清晰的性质:. o! \5 r9 w% d- v, S  d
    1)两个相同的十进制数异或的结果一定位零。
    5 F, g3 |9 V& p2)任何一个数和 0 的异或结果一定是它本身。
    3 r  v% f3 ^8 O6 q$ I- b$ _, {3)异或运算满足结合律和交换律。
    ; Q$ p. S1 E7 {+ }" U3 E#include <stdio.h>
    % y! E. d0 `" d# v' y) x5 Mint main() {+ t# G# b5 E" k" S
        int a, b;/ J5 A" L5 m4 h" c+ {
            while (scanf("%d %d", &a, &b) != EOF) {
    7 o0 R; a! g8 n! G; a+ c            a = a ^ b;   // (1)+ r. z) _" P. J% H; f/ a. P5 ~
                b = a ^ b;   // (2)3 s7 e% J: i6 _0 Y' S
                a = a ^ b;   // (3)' i& w/ ?  o3 z, T  q: n
                printf("%d %d\n", a, b);# F$ \8 \( i1 {1 d8 x
            }
    2 ^$ V% w' q- d0 c        return 0;: g$ S* T8 L* w5 _& @; T5 M8 [
    }( H3 F6 b& V* y4 V. W  M8 `
    1
    * W9 D9 }  ~& T7 _3 F$ K, H% i2$ M7 N, ?( c* k
    3- k2 T8 n' X% G  y) N" k; q6 H* V' t( Y3 z
    4' H3 V4 F: c+ u# f
    5! ^! A( p' n4 P/ I
    6
    ; H8 r% y* D/ K% x4 {* K& L/ a79 |6 T3 u6 t2 T8 n/ n6 h
    8, K0 y* ~& \$ a& g
    96 O5 v6 R- c0 \2 @2 h7 Z0 W. A
    10' t5 ~; u* }1 X# j3 ]# Y5 C
    114 i" I9 U% h- t: T
    我们直接来看 ( 1 ) (1)(1) 和 ( 2 ) (2)(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。
    ( X7 e; B/ l% e# R2 @1 g而再来看最后一句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。
    % z# J! l1 d2 R% F# y: C从而实现了变量a和b的交换。( V4 _$ m/ W; `/ y
    / r$ p  T' n$ f5 Y1 P3 r
    4 _/ s& \) t8 ^" _
    4、正确解法4:奇淫技巧
    $ \& T7 W# ]% m. Q* C# t/ z- m当然,由于这个题目问的是交换变量后的输出,所以它是没办法知道我程序中是否真的进行了交换,所以可以干一些神奇的事情。比如这么写:8 a. a+ d. }" A
    #include <stdio.h>
    : n: ]. g9 p4 Y' \# M1 ^int main() {
    5 f" `: a- R) L/ X    int a, b;
    / H$ N. f# s2 U, A  ^. }        while (scanf("%d %d", &a, &b) != EOF) {
    1 @" V, U" \1 O( w            printf("%d %d\n", b, a);) s# ?  M% \0 ]' {4 N
            }6 o, D- A! X- I. V  @# ~! j/ O
            return 0;
    ' }0 `! Q  e9 }) K0 h8 O7 E}* y* \" @" o- g" l
    1
    7 R4 g6 N: h6 b1 ~2
    + `# E  s& s' ?33 L% `1 e* P: q' m
    4/ u1 v5 {6 {+ Z  M! u9 ^! g& V
    5
    5 V0 m+ @. n5 m6
    0 Y! T" ~. y! h- @7
    , K3 h% z. d3 N4 j8
    4 O0 x3 t+ ^+ T$ i3 C( m: c你学废了吗 &#129315;?
    7 @. Y* G- a% m" l7 E+ [& w) i- R* j2、例题2:整数溢出
    : c# i. P0 D3 D一、题目描述
    6 |+ U9 L8 i% K" L9 Z  先输入一个 t ( t ≤ 100 ) t (t \le 100)t(t≤100),然后输入 t tt 组数据。每组输入为 4 个正整数 a , b , c , d ( 0 ≤ a , b , c , d ≤ 2 62 ) a,b,c,d(0 \le a,b,c,d \le 2^{62})a,b,c,d(0≤a,b,c,d≤2
    , V# y/ r0 K5 g5 \1 m* \* b; P62
    $ g: G& r# J7 g. d" k ),输出 a + b + c + d a+b+c+da+b+c+d 的值。! B" O! \2 L* [. T) h' r8 }$ V
    ! _9 a* Q1 ~% P- W

      e$ e6 e; T2 o) i9 R2 ~) \8 w二、解题思路
    - [" [. r; }1 ^+ P3 c, b. |3 w9 {8 f  D难度:&#128308;&#128308;⚪⚪⚪4 H- W# {( W! T+ @. A7 |

    & G) r) `- P" n; o  {5 D4 X
    ; O$ M9 b; F. F2 m
    这个问题考察的是对补码的理解。
    " e5 h* n/ R6 w: {" i2 o仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2 3 K# z; J. F1 u
    62, i9 d& _$ M* r' v9 ?
    ],这四个数加起来的和最大值为 2 64 2^{64}2 . L5 X9 n1 m9 b- X  w
    64
    " q0 U9 o6 U+ X2 G 。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12
    + y* B8 |) \; o3 p" f2 z+ j( ^63: v% l) r, Q$ h
    −1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12 3 L3 W( {% U6 n$ }3 c
    64
    ; k3 J& O$ o" r2 H$ j0 [6 { −1。7 R8 ^% A# G; ?" F
    但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2
    4 [0 w* o& D8 y621 Z4 A* a- W/ m! ]
      时,结果才为 2 64 2^{64}2 - ]+ {7 h9 L5 b" f
    64
    ! S. \( B6 m  }; v- D ,所以可以对这一种情况进行特殊判断,具体参考代码详解。
    + t" b. C. H: p% l" ]; n三、代码详解. C  i# S5 y' j  V
    #include <stdio.h>
    . K# J( s/ i4 Atypedef unsigned long long ull;                           // (1)
    + @3 }: @; Q1 q# v' _9 econst ull MAX = (((ull)1)<<62);                           // (2)
    # y8 C( Y% h/ Q/ [5 u7 V9 R4 t4 O! c$ j; ~
    , @5 I1 M0 U; S6 Q; |
    8 m! [4 p& x9 N* M( n
    int main() {
    # C0 \. g  O1 a! L1 o( p( c  z        int t;4 y! Y: P% z& Z5 M& K& W/ D1 e" h
            ull a, b, c, d;$ A3 K7 Z9 Y8 M
            scanf("%d", &t);( b  ?+ f& |0 H* B- `- ~
            while (t--) {
    + g6 e# k4 P$ |3 N                scanf("%llu %llu %llu %llu", &a, &b, &c, &d);     // (3); j% Z+ |" T  `$ T: _6 O( d. l# ~
                    if (a == MAX && b == MAX && c == MAX && d == MAX) // (4)/ e- H. @" i% G. P
                            printf("18446744073709551616\n");             // (5)+ I9 ~$ S* ^( x) S1 c0 q
                    else
    / {6 v5 r/ a+ R4 q, Y% H: E                        printf("%llu\n", a + b + c + d);              // (6)4 m- `' _2 F* p) A! B
            }4 e! U$ X7 t" u
            return 0;
    9 q& y, a1 y0 n7 Y3 K}0 }* L3 w6 d, h8 d7 E+ v9 C
    1
      [' H( [# v' Q% S: }5 s23 `$ R  y  m) R" s4 t
    3, b- }0 }0 P. {- L
    4- m: N3 q0 X  A9 C7 ^* \% o
    5
    8 C; |! X9 {. D% e$ @6
    1 X+ W7 Q- r' o& x4 e) C' q7  I0 o, F! A" @9 Z/ e
    8
    * r" c: B2 d' d5 w) h2 }7 Z! G8 q# `93 p% z6 `" ~# h" r
    10
      @% }* d2 u$ U. p6 Z11
    " n+ B' F% W2 @5 v% c12
    0 |4 R6 B' R) }& _! ]7 C  }139 H& S0 O$ j8 V5 h" }3 L& W$ r
    14
    1 F! @  h; Z2 i+ ]0 }! Y% E8 W$ \152 q, x3 V4 |9 M2 K( ^! S& l
    161 B' N. q, N3 S9 Z) b3 p6 x
    17
    , c3 `9 n0 v( s* S( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名;# W# k! \0 y: i
    ( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2 9 ~5 B8 b" j/ F& A% n% l  ], y; o
    62
    + W" J7 A! k5 v3 {0 P ,这里采用左移运算符直接实现 2 22 是幂运算;
    $ g' Z- u" w- M' }' `数学        C语言+ h0 }8 U/ j3 r: T( _1 h7 n
    2 n 2^n2
    : N: P# v) h; e  S9 un
    " p7 Q1 X; l: `' ^+ L         1<<n3 z" m: \$ m! U  q+ R
    需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1;
    ! |& h5 \9 x  I9 G6 ^9 T7 T; T( 3 ) (3)(3) %llu是无符号64位整型的输入方式;3 |1 M7 F% C8 X, b
    ( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事;
    : ~2 T% x; {, j1 J) I4 A& @( 5 ) (5)(5) 由于 2 64 2^{64}2
    : K( T; f8 {" e/ l# i64
    ' p& M! L5 \, @1 ]4 [9 y" X  是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出;" X2 H* w7 k9 Z  Y" S/ T- R; M$ u; N
    ( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2
    8 I! k8 p( ?' ^7 K644 a) L  P: l' P- ~! W( R
    −1] 范围内,直接相加输出即可。
    0 c3 }/ T- @4 S# h. M& s, t  s由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。
    6 k, _$ k% G" C6 G为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 &#129315;。; u" V( Q. G1 }) {' J
    1 k' p2 j- D2 y0 M, o2 F

    $ }( C7 Y% r5 ~$ s7 i0 p3、数据结构
    2 R/ f' F3 m; r2 S: a《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。
    0 f6 T% H' q$ G% x2 ]  A- l/ F1、什么是数据结构  u' t! w9 \, g# b1 b3 l, w$ E
    你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。
    ) w9 q. l8 u1 `3 P0 k# C如果一定要给出一个官方的解释,那么它就是:% k, K" v/ X) a  g( F$ Z2 \
    计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。' [( V# R$ b- u! ]" }" R

    & C, @" j3 O. N; t
    & n4 p& @# }1 {9 J- X
    是不是还不如说它是堆,是栈,是队列呢?" i) D* E* ]" e1 Y! F9 T& O
    是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。
    / n! s+ n9 o( @2、数据结构和算法的关系
    ' V4 a8 z) T* C* S; P% i0 n5 ^很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。
    8 i+ `2 {5 Z) S7 ?: U! K数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。. _( h! [# ^/ X* v% i+ P. F$ M
    而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出?# {; J2 i+ w' ~5 A* R
    讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。/ P% L$ _! e& R( H: {# `. Q
    3、数据结构概览
    - l. R2 z9 b, Y+ g) [周末花了一个下午整理的思维导图,数据结构:# T! k* W. o& B

    # F2 f" y: o5 b. x6 i3 ]

    ' K" k2 [: }' O$ S常用的一些数据结构,各自有各自的优缺点,总结如下:# q8 J8 B+ ?+ p/ V
    a、数组3 O1 ~: Q9 u* v: ~  ]2 O
    内存结构:内存空间连续
    - c0 {5 P! s4 T# {6 B! E) Z5 _- I实现难度:简单" Q: K- _% P) E$ @, G# G
    下标访问:支持& _7 V( g0 h# ?' G2 W  Z8 C4 c4 {
    分类:静态数组、动态数组
    / `, o( @. E+ T0 m) c% a插入时间复杂度:O ( n ) O(n)O(n)
    + E- W+ X! }4 O5 ?# @查找时间复杂度:O ( n ) O(n)O(n). U4 T& h2 g9 a: @% Z9 @+ F" b3 @
    删除时间复杂度:O ( n ) O(n)O(n). U$ I  [3 b' c* h
    # A+ P0 I' c% E( T% R4 R2 U
    9 ~: l3 G2 m$ R, e
    b、字符串
    # N& O0 R/ G! |- Z' @' o内存结构:内存空间连续,类似字符数组4 P9 R! R3 v% C( s, C) Y
    实现难度:简单,一般系统会提供一些方便的字符串操作函数; n5 w: ~. P' V" R$ s! P
    下标访问:支持) A% c0 V; U) C3 _1 s8 K( E
    插入时间复杂度:O ( n ) O(n)O(n)# K1 S5 c# n; z# H. @
    查找时间复杂度:O ( n ) O(n)O(n)
    8 o" o' J+ @# Y2 ~" \) e删除时间复杂度:O ( n ) O(n)O(n)6 b6 J! p  K3 N/ o
    # n6 h- a$ z, r1 t& l

    9 [) R% g1 E) o7 fc、链表
    ) j  r6 T; G& {* S内存结构:内存空间连续不连续,看具体实现
    2 Y& P$ Z4 Y& Y实现难度:一般: t6 i# j! S" r: I
    下标访问:不支持
    3 w: U3 e2 K" `, {' S分类:单向链表、双向链表、循环链表、DancingLinks$ L& ]( g+ \+ E- e
    插入时间复杂度:O ( 1 ) O(1)O(1); e0 P' D6 v# ^6 a4 V/ d6 A/ [
    查找时间复杂度:O ( n ) O(n)O(n)
    2 k; r1 O5 ?" J6 L7 [; b删除时间复杂度:O ( 1 ) O(1)O(1)
    . l" @, V. }: C" T  d* z9 `( S, V+ r3 G4 g, E* N/ R" _, M
    ( j. |4 ^3 H' L
    d、哈希表2 }. K; a6 S, @4 j9 \. z, j2 h
    内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续
    7 Z, C# b: K/ X; B实现难度:一般
    8 b9 L4 r& e" D下标访问:不支持
    : {- z/ [! f: o+ e( q3 y( E分类:正数哈希、字符串哈希、滚动哈希
    , [/ [( T1 U  i& s# Z! p" Y插入时间复杂度:O ( 1 ) O(1)O(1)
    . j7 N5 D' L! H2 b  O( p查找时间复杂度:O ( 1 ) O(1)O(1)! D, _  j8 r" q
    删除时间复杂度:O ( 1 ) O(1)O(1)
    % Z- b% N' Q0 s4 x7 Q0 m3 T1 s% ^
    $ l" Z/ z- a# d8 x. ~4 ~

    $ S  q5 u( D" K. H3 Z( K5 Xe、队列) k  @2 N. @& [+ |- K7 ~
    内存结构:看用数组实现,还是链表实现
    " Z; F/ h2 X* f' K7 R! C$ J! Y实现难度:一般( ~% V+ l, S3 K1 {* K4 y8 Q* l
    下标访问:不支持
    # R3 {) ?0 P( ?& b' L分类:FIFO、单调队列、双端队列
    3 ^4 T! @, @6 a2 D$ M6 D. ~插入时间复杂度:O ( 1 ) O(1)O(1)
    & w% j! j; t0 z+ Y+ D- L查找时间复杂度:理论上不支持
    + [2 B5 f6 \0 ]2 p7 ^删除时间复杂度:O ( 1 ) O(1)O(1)
    & r6 S5 `$ k, `; q# e: n- h: f7 }) z
    + P2 a4 M# c# a  c8 S
    f、栈0 M' D$ ~" h1 S( q, w
    内存结构:看用数组实现,还是链表实现0 L# r3 K/ @% }& s6 ~3 P( O; f
    实现难度:一般
    # A1 h3 q- ~/ O% B# u# q下标访问:不支持
    # O3 G% w9 H1 a6 g分类:FILO、单调栈) H: Q  g/ H* X3 b5 n7 K+ B
    插入时间复杂度:O ( 1 ) O(1)O(1)
    * P: T& Q1 ^' \) ]查找时间复杂度:理论上不支持
    ; k) e$ [5 x1 V删除时间复杂度:O ( 1 ) O(1)O(1)2 d+ v/ z! S* B5 J
    , T0 _9 U0 z: U/ P. G4 i

    8 `- d( M( {6 J& u$ ?. P! Wg、树
    * @2 R1 O+ k3 i: E  [; r内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续
    0 H0 c2 {- }* b实现难度:较难+ U; h' n# ?9 `2 Y! E1 B
    下标访问:不支持8 ?" W2 j# h+ R* @1 q
    分类:二叉树 和 多叉树
    / A7 c3 c5 \' }插入时间复杂度:看情况而定
    . g: C& f  t$ }  q4 Y查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log 7 x! F0 K' @5 C8 F* S$ ~9 p
    2
    ! ]( V  ^5 o8 u" a, c​       
    : \& L7 l7 B  [- y- B n)0 S+ v0 n- u% ]) w3 ]- O
    删除时间复杂度:看情况而定/ @& u8 T3 n. \- v+ W8 s# y
    ( ?# {6 _, f4 J5 D+ u
    . N; k  }. k0 V
    1、二叉树
    ; h. U  M% f. q. T/ d7 i! R6 V3 C二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。
    + z  U* B4 R" f% z5 _2 c4 F其中,堆也是一种二叉树,也就是我们常说的优先队列。
    9 n' m% I1 z$ |. y! Z: x2、多叉树) w/ w* _9 \' B6 c4 N/ b( ]
    B树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。
    3 P- j4 n. J2 U$ Oh、图+ ]" B$ n4 D6 ?6 F$ o5 C4 [
    内存结构:不一定% a) R! D  c& x# V0 K
    实现难度:难
    % f; L# h8 v1 `0 Q& r) o2 y下标访问:不支持
    , @3 L/ T2 N  F分类:有向图、无向图
    8 \4 i9 y( d# K插入时间复杂度:根据算法而定
    9 o; B* B7 M; ]- X: f查找时间复杂度:根据算法而定
    4 K+ |# r7 j( F! z9 w删除时间复杂度:根据算法而定
    $ G6 f% S! b4 G, @) z& m7 |- M& y& }; C! G( C2 ~8 u& L$ O8 q+ k

    - P/ P  r) q1 h1、图的概念
    ! s+ v  T9 }* f" l6 K在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:
    # {4 q& C+ T, b, z) R" a图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。0 w, N3 `; w; U! ]+ G" j. m: y) S
    对于无权图,边由二元组 ( u , v ) (u,v)(u,v) 表示,其中 u , v ∈ V u, v \in Vu,v∈V。对于带权图,边由三元组 ( u , v , w ) (u,v, w)(u,v,w) 表示,其中 u , v ∈ V u, v \in Vu,v∈V,w ww 为权值,可以是任意类型。6 b: V/ k" J1 k5 S. d9 w- c2 R- z
    图分为有向图和无向图,对于有向图, ( u , v ) (u, v)(u,v) 表示的是 从顶点 u uu 到 顶点 v vv 的边,即 u → v u \to vu→v;对于无向图,( u , v ) (u, v)(u,v) 可以理解成两条边,一条是 从顶点 u uu 到 顶点 v vv 的边,即 u → v u \to vu→v,另一条是从顶点 v vv 到 顶点 u uu 的边,即 v → u v \to uv→u;
    3 `; z7 S/ X/ |2、图的存储" W/ s/ j6 U& w/ D1 k8 F- a
    对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。& h" O: u, b) U# D( ~6 `! a
    + e! T2 P1 M3 }" G3 P0 E
    3 y" K# g, U& `- v  ~% z0 w; m8 R$ X
    1)邻接矩阵7 e+ l6 p2 h! n9 H
    邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。3 a/ C1 R* X1 R/ H; N- D
    它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。* w; `% k" v4 c, b5 R! M+ C# W
    [ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[  N  V. s$ z! o: U. H: }6 v
    01∞9∞0∞8320∞∞∞30
    . D( t2 K4 K8 L# u) S0∞3∞102∞∞∞0398∞0
      M. g/ X9 X6 d0 i2 D, r% m5 C\right]
    , v, e% Z0 x4 D( P' ^* d# K. a% u0 N5 j3 h  H

    * |; ^& w9 ^7 Z) F& ^0 v
    ) z) M+ u" ^# H" x4 r5 e! J* [
    % w' u- Q2 o3 V* ~0 @" @​        4 v/ k1 z3 c* |, P/ }* y. B! J
      
    # O2 p" e) I* B6 K+ j. y9 e) J0* r2 \; M0 p3 j& J5 ~3 T: B
    1( i  `2 n& O, H/ _
    % e2 o; i+ c( p5 @4 s- K, B: u
    97 L, f* S5 Y8 K; _) n
    ​        / \. V5 H( G. \% T
      ! s" H$ r7 g/ f! p8 h
    ) d. q. _7 Z7 s! t6 Y5 z: {7 ?
    08 [! X8 F4 I  j4 z; w! F
    1 I6 F2 ^. L. W0 U# i" n- r/ _: y
    8
    ! V1 |* i; Z5 R* S% j​       
    + h# i. ?3 t5 T  
    * z9 L& N  D7 s7 D* n! y" I3
    2 C. C( L, L4 P2. ^: Z/ s# }$ J" G& k$ C: v( H. v
    0, T1 `1 o! `: K* R3 h

    % ?* d% [9 L7 l* Q7 ?​       
      O1 H* ?  M* G" M! L  % s) m5 _, R. L' X- Q) o" u4 z
    ' G8 W# G7 v) D& R7 t
    ; c& p2 q: H- ~  B. f
    34 J* t/ ?& I0 F$ N8 n
    0) W' _2 Z) s0 r1 g5 m7 f
    ​        4 P5 w' m: m8 w9 c* a
      * U$ B6 o( t/ x

    ! |0 ?5 h1 K1 l' h% W& u7 a. q2 c; y8 J

    , l: Z2 G* O2 O( c6 Q6 W# N; o# E
    # A' L3 r2 U+ n/ ^6 [1 W​        , U5 Z6 A( c7 n( l" V; O6 J

    + g+ t% ?9 Z6 q% A6 L; n2)邻接表1 K) S6 u' d9 y3 d, a
    邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。& \- S! G2 z% Y& N( A8 I: k
    它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。
    . x8 S% N0 _2 P9 \! _& q& V. e如图所示,d a t a datadata 即 ( v , w ) (v, w)(v,w) 二元组,代表和对应顶点 u uu 直接相连的顶点数据,w ww 代表 u → v u \to vu→v 的边权,n e x t nextnext 是一个指针,指向下一个 ( v , w ) (v, w)(v,w) 二元组。
    # }: |, ]- ~* g, L
    - T+ h! i2 Y. G- d& O  X5 H6 K8 e
    * a( ^9 }! {- \  I- x: t+ a
    在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;
    " e# w8 M% T1 x! I  Y( P    vector<Edge> edges[maxn];
    * S6 ?, l% T% Y  M- V/ h. X1( |& H" t7 s3 ]  u! `7 {
    3)前向星0 Y  _9 u) B& |9 g$ ]2 C3 C
    前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。
    8 @; B) R* w0 \( z4 C. [它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。2 A3 g4 y' t7 R+ i
    如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。& g& Y! A9 ^  s1 s  Z* p7 `/ @5 {9 e

    8 O8 h6 i: P" m0 z/ K1 f

    1 g9 R/ v. x% m( x  j! n那么用哪种数据结构才能满足所有图的需求呢?
    2 {" @0 V0 V* z! ?, Z/ X9 |接下来介绍一种新的数据结构 —— 链式前向星。
    . P, i5 `6 F. i4)链式前向星4 j% v  P) H# P$ J' ~( c! B$ j
    链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 i ii 都有一个链表,链表的所有数据是从 i ii 出发的所有边的集合(对比邻接表存的是顶点集合),边的表示为一个四元组 ( u , v , w , n e x t ) (u, v, w, next)(u,v,w,next),其中 ( u , v ) (u, v)(u,v) 代表该条边的有向顶点对 u → v u \to vu→v,w ww 代表边上的权值,n e x t nextnext 指向下一条边。
    ) m7 f9 g( J4 x2 [* j具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。
    % z, Z* y0 y7 h2 L; }边的结构体声明如下:
    : S. n* s* Q9 ]3 S9 M% \! _4 ?struct Edge {% ^4 @, u' Y- v
        int u, v, w, next;& [0 `; L5 P: F
        Edge() {}7 S; |( q5 k$ S( p& `0 d5 \! a1 K; p
        Edge(int _u, int _v, int _w, int _next) :: @/ h6 I; x% Y2 z
            u(_u), v(_v), w(_w), next(_next)
    3 K8 e3 q' l1 Y6 B$ c& E# k    {  A  U0 Q  w; ?0 b
        }
    6 W# R; d4 K' f# b3 R}edge[maxm];  ^/ {) G/ q& K6 I  m- s
    1
    4 ~4 q0 b$ E+ K& ^2( D) ^' q- I- N) V0 g5 N) N) a+ }$ {
    3. H6 J# R8 f; G. w
    4
    + L. U/ T+ S" K+ w* K5
    2 z' G3 M" t, q1 n6
    # t3 D, z' @3 i* ~, |& Y- O- t5 L7
    ) G1 Y& l; I6 o: b- M" u8/ L% v1 g. v' r9 h
    初始化所有的head = -1,当前边总数 edgeCount = 0;
    $ Z" P# ?: b. X7 ^每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下:! S8 G: G; b; q8 z  g
    void addEdge(int u, int v, int w) {) i1 m0 l1 V. ~! P9 A
        edge[edgeCount] = Edge(u, v, w, head);3 c/ o7 W4 U  N, `' M
        head = edgeCount++;# x2 M4 ?! P& u' h
    }
    , ~/ s, @. |' v8 _16 X) L9 P, |$ f" P  {, E0 _
    2
    9 V# E' t9 D1 o: U30 L1 x" @8 N$ m
    4
    * W7 j/ M% l5 `" d: k这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。
    6 ~+ M" A7 _" F9 e5 F  b调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。6 h/ `  W9 U. p6 c
    for (int e = head; ~e; e = edges[e].next) {
    # R5 d/ W- H9 D8 m    int v = edges[e].v;
    ( Z- ~, p) N! @    ValueType w = edges[e].w;% ^3 J" s% D0 P; f/ a1 G
        ...$ k3 U' w  q$ ~7 w  @
    }5 q/ T$ B/ k2 d3 B  |% R8 t
    1
    ; Z+ J! |% q) Q, ~. \. e' B9 \, L2: j) t8 F" `/ J4 s7 Z8 X* F1 V- [4 R
    35 x0 o, K) o& n
    4
    ; G9 `: |, s% H  K; ~5
    " |2 ?9 k2 R0 e% N/ b文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。
    ! v3 g1 |2 y8 N* ~. G8 c7 d4、算法入门
    5 f8 _3 w! Y9 r# v6 c算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。
    $ y; u6 E/ I6 R, g0 l2 o6 ^3 `* k+ b

    , O  O0 @9 T# ^% V7 ^# q入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。
    3 G3 z  _& Z0 O; m对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。
    5 [  W" H! R' E2 \- `( b1、枚举
    ( |- ?+ t) f! G/ X枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。
    & k. B7 y1 @' f" a, `' f对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。
    2 f1 P) n; H9 q5 A, C2、排序# E3 Z) ^" f1 u, k
    既然是入门,千万不要去看快排、希尔排序这种冷门排序。
    & A% a6 x5 b' X( ~. W7 ?9 _冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。
    - B* H% W' q5 w+ P7 MC中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。0 P( N) u+ n$ G6 v, O" w7 T( ]
    3、模拟
    0 |4 y% j5 |0 }( |模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。
    ) J5 C; ?# [5 S4 C3 N6 i- y- v- I不管时间复杂度 和 空间复杂度,放手去做!) d+ k( b: j# N9 L
    但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。
    2 d) o- U" a1 u+ t/ d% h4、二分
    3 U& T8 O9 q1 o, J) L二分一般指二分查找,当然有时候也指代二分枚举。
    3 `+ M) O$ l) J例如,在一个有序数组中查找值,我们一般这个干:
    - I1 x! J' E# @9 y1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;
    - e- B" G9 c, w% {$ H( S: d" l3 @2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种:
    , |! N2 S# _) f; `! T" U  2.a)目标值 等于 数组元素,直接返回 m i d midmid;
    4 v+ C5 }; W* b8 D- [% U  2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;% k3 l4 k4 R8 I1 Y
      2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;
    - n2 b; z) p% _3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。
    7 s, @9 E; W+ w; N# M, W5、双指针7 e# ]6 D$ A6 g$ o( q1 T$ W# c' R
    双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。
    3 L  f1 S2 a! T$ s& O" `
    ' F7 i- e4 Q" W5 N* `" M* A

    8 H- I. ]/ F  H+ Z6、差分法: n: B0 d- I% Y% t' [' j0 l3 X' b
    差分法一般配合前缀和。
    1 l* K0 j6 @3 Z9 t4 y: D对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;
    ( j8 T9 ]) `- f5 s  c5 X: h假设 [ 0 , x ] [0, x][0,x] 内的 g o o d   n u m b e r good \ numbergood number 数量为 g x g_xg
    8 G; F! C+ ^9 o% lx( E0 s3 |) H6 O4 x) ]' T- c1 A2 O/ H
    ​        8 P% V7 z& P: i( U6 \
    ,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g
    + p1 T% i2 U! b" M3 f1 l( rr
    ! @0 e! g0 H. W+ u0 \* ]​        0 s) h9 P- ^# E: j( Q* Y
    −g
    4 E+ H9 X5 j- al−19 t! u  s; U5 C& Q+ ]3 ]' ?
    ​       
    * X2 l4 w3 y; q9 W ;分别用同样的方法求出 g r g_rg
    0 Y7 e9 P: t( l: t' M8 Lr
    % X" Q8 U$ i8 T. x8 P/ a​       
    " b# \8 ~$ o- T( \9 M  和 g l − 1 g_{l-1}g . ]4 B* D$ o0 ]8 L* `" r5 W. m* Z
    l−1
    * r. m# C- V7 W% q! \# t​       
    , I$ ^* [% q: J6 A ,再相减即可;
    $ b  V# E, q; O. [
    6 X9 P7 H% V4 `0 ~* g9 V  ~. X4 b
    # \  a$ [3 _5 s3 O1 {/ }9 p& L
    7、位运算% `9 S: U3 c( k0 a: q
    位运算可以理解成对二进制数字上的每一个位进行操作的运算。9 r1 K* _* x. U+ x3 T
    位运算分为 布尔位运算符 和 移位位运算符。5 h! C) E, _  ^
    布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。6 m7 T* {$ l& ?; {( V
    如图所示:% j1 @1 t% i& J6 N& |" h

    2 A4 P$ \  E* @
    1 [; B- x% H! [
    位运算的特点是语句短,但是可以干大事!
    + G$ S# t1 X8 n- q/ V, B比如,请用一句话来判断一个数是否是2的幂,代码如下:- }9 o& b' d2 f; s
    !(x & (x - 1))
    * [4 d; {5 Z5 d! y; @! {9 |1
    ) A7 Q  I7 E' N# X0 G. q$ ^: Y8、贪心" N% {1 m; L$ u
    贪心,一般就是按照当前最优解,去推算全局最优解。
    $ M( v# f& Q$ p  O% O3 I+ d7 c# b所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。
    ( }! N4 b' z1 v2 f9、迭代& o5 v, d- k8 T$ _0 q
    每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。
    - X& _: b8 c% G+ Q$ K10、分治
    + L2 P% ]( W  A$ n* ^  G分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。
      W' J9 G1 |, S* N' J7 E" n5、算法进阶) |, u5 `+ J6 |& b' {
    算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。
    1 S/ Y" A" t2 @9 w. U" {如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。1 D! y+ f  \  O* a: J
    这个系列主要分为以下几个大块内容:
    " D- t1 p8 Z3 ^+ [4 a  1)图论
    : X7 O$ b9 o& h, O( C- {7 [  2)动态规划- N7 W; f4 ?9 x* n" w: b
      3)计算几何
    * Z7 I% D: Y7 |, ?# P9 h  4)数论6 g4 \! k3 o1 e4 w5 s$ Y4 e/ K/ L
      5)字符串匹配
    $ v8 S) q6 A- K! i& r  6)高级数据结构(课本上学不到的)
    ; J: w5 M: N# m9 t# D; w  7)杂项算法0 ?6 j) u3 F: ?6 _" A

    - `) y+ i! `8 K7 |$ i6 W
    7 g$ \- t5 H3 l# X4 j9 S8 `- E
    先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式:
    / g7 }. s+ F  ]/ b6 j. e! [7 w
    6 b" B5 q! k, `# }, {- ?
    9 d( s: m' \4 k3 S. _# Y

    % {! S- o: z+ r* q
    3 J$ |& ]! m* S* }' J5 v# F' l
    1)图论( k4 _; U! D: n/ M3 h# C" P
    1、搜索概览
    2 R) A  X  T: C图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。- g, o" Q/ K2 J# c. D. S
    # ^! v' U3 q$ G$ Q/ K( f
    / F2 R+ }& Y# Q' v* s
    比较常见的搜索算法是 深度优先搜索(又叫深度优先遍历) 和 广度优先搜索(又叫广度优先遍历 或者 宽度优先遍历)。各种图论的算法基本都是依靠这两者进行展开的。" E0 }( w3 y. d9 ~' T' o
    2、深度优先搜索
    / [0 U( d2 Q4 Z! e. q深度优先搜索一般用来求可行解,利用剪枝进行优化,在树形结构的图上用处较多;而广度优先搜索一般用来求最优解,配合哈希表进行状态空间的标记,从而避免重复状态的计算;
    ( v' P6 L. a+ a) a$ U. {# b# j原则上,天下万物皆可搜,只是时间已惘然。搜索会有大量的重复状态出现,这里的状态和动态规划的状态是同一个概念,所以有时候很难分清到底是用搜索还是动态规划。2 n" v% Z, B) j7 q. k( m6 v, \$ o) L
    但是,大体上还是有迹可循的,如果这个状态不能映射到数组被缓存下来,那么大概率就是需要用搜索来求解的。
      z2 L  T' Y3 O0 g6 Q如图所示,代表的是一个深度优先搜索的例子,红色实箭头表示搜索路径,蓝色虚箭头表示回溯路径。' Y# V; q1 e. Q  ~' O* v
    $ z( R8 o  s( X0 O7 c3 V9 q

    5 q) T7 Q7 f  C( y- |红色块表示往下搜索,蓝色块表示往上回溯,遍历序列为:4 I# t/ Z1 p0 f( m+ ~
            0 -> 1 -> 3 -> 4 -> 5 -> 2 -> 6! M0 q% D0 z+ L) I8 m. R
    17 V8 p5 [% k7 m* M% v- V, z9 ]
    同样,搜索的例子还有:6 N+ h" T- ]: y% f6 v  `
    , M1 s: P. j2 d0 J" @; ^
    4 ]' n5 x1 y% l" Y% l5 L2 l) c
    计算的是利用递归实现的 n nn 的阶乘。3 Q& u: A, e% D
    3、记忆化搜索
    - I$ J0 l8 t% @  K& M* S9 Q对于斐波那契函数的求解,如下所示:
    5 ~. d7 E- Q9 ^f ( n ) = { 1 ( n = 0 ) 1 ( n = 1 ) f ( n − 1 ) + f ( n − 2 ) ( n > 2 ) f(n) =" X# L- I7 M4 C  j( \
    ⎧⎩⎨11f(n−1)+f(n−2)(n=0)(n=1)(n>2)
    / n' t9 F/ j- b9 n( U7 W/ Q' Z8 G{1(n=0)1(n=1)f(n−1)+f(n−2)(n>2)
    : J7 U: U1 D0 B7 H$ d1 S% E4 y5 X# Sf(n)=
    , M$ a- \' O/ G* ]& S: i
    ) b  O; `8 Q! l& o, w# {0 W
    % k% r( L4 V# O% A* q0 r
    2 {4 y6 E9 L( p6 P$ P2 ]( w, n% s
    : X0 M; j" ^- R' M4 H4 V7 E. t6 W2 k
    ​        7 ]5 ]5 ]! T0 z' D4 x/ r
      
    $ R9 r. v$ V; |. P9 D0 k* j1
    - r6 O8 Z+ \9 J3 n1
    / V- |4 ?' k8 [f(n−1)+f(n−2)( l" j7 o/ _  n8 l4 M* O* z. e
    ​        * @% G) j. `) |& ?5 t
      : V/ R1 f9 v" l2 W4 s/ H
    (n=0)
    ( M: a$ U0 @7 N, B! M) h: z(n=1)) v: M% J! j, ]3 a
    (n>2)
    % H+ L1 v; U" I: Z​       
    . v) J' {0 J" V& u' ] ' y  B& P4 F% r9 K
    对于 f ( 5 ) f(5)f(5) 的求解,程序调用如下:
    , [8 v+ G% J8 A+ [7 ~
    4 i9 q' @5 ]& k3 E7 x3 `4 ]
    . [- X1 g8 l3 n+ R
    这个过程用到了很多重复状态的搜索,我们需要将它优化,一般将一些状态缓存起来。+ V  c0 d; `5 n" l; N0 d6 }1 {' D
    我们通过一个动图来感受一下:5 |8 B$ x0 ~& _: F' p

    ) }9 F3 d0 X, l
    / d- W$ }5 O7 M6 u
    当第二次需要计算 f ( 2 ) f(2)f(2) 和 f ( 3 ) f(3)f(3) 时,由于结果已经计算出来并且存储在 h [ 2 ] h[2]h[2] 和 h [ 3 ] h[3]h[3] 中,所以上面这段代码的fib != inf表达式为真,直接返回,不再需要往下递归计算,这样就把原本的 “递归二叉树” 转换成了 “递归链”, 从而将原本指数级的算法变成了多项式级别。
    7 z. \* l' o( L/ I9 k/ ?6 C/ i这就是记忆化搜索,像这种把状态缓存起来的方法,就是动态规划的思想了。0 b/ D! W) ^) ?9 P, ?& @" R
    4、广度优先搜索
    % w2 C8 S: V* M) J% A* q7 j单向广搜就是最简化情况下的广度优先搜索(Breadth First Search),以下简称为广搜。游戏开发过程中用到的比较广泛的 A* 寻路,就是广搜的加强版。' q6 L4 g% D: w# W& c/ F+ |
    我们通过一个动图来对广搜有一个初步的印象。3 N2 y1 L1 Q4 p3 [

    6 Y3 a! ?  ?  W1 W
    " |5 q/ x1 @/ F  r
    - Q8 w/ n+ @8 e; Y5 y( }
    + X0 C, p) X. z9 I
    从图中可以看出,广搜的本质还是暴力枚举。即对于每个当前位置,枚举四个相邻可以行走的方向进行不断尝试,直到找到目的地。有点像洪水爆发,从一个源头开始逐渐蔓延开来,直到所有可达的区域都被洪水灌溉,所以我们也把这种算法称为 FloodFill。
    : Q  _* e! ]/ r' H那么,如何把它描述成程序的语言呢?这里需要用到一种数据结构 —— 队列。
    ' u" k7 ?) P3 T5 n) C这时候,算法和数据结构就完美结合了。
    9 M9 g) x/ W" y& y: I& y2)动态规划9 b2 k! i4 d2 T# I5 _% s& C
    动态规划算法三要素:; J7 U( J9 B0 I/ z) K
      ①所有不同的子问题组成的表;
    0 C+ ]7 y9 `" t- }  ②解决问题的依赖关系可以看成是一个图;4 f7 R" [5 K7 i, j/ l
      ③填充子问题的顺序(即对②的图进行拓扑排序,填充的过程称为状态转移);
    ! W1 r9 s' W5 v) c' f/ E/ x, W8 A4 I9 _# D0 g4 l9 y& ^" l9 v
    3 v8 q0 `4 f( a
    如果子问题的数目为 O ( n t ) O(n^t)O(n   J' ~/ `' W' F7 T+ w* c9 A" F, g" n
    t
    0 W& y' M% V+ c6 a ),每个子问题需要用到 O ( n e ) O(n^e)O(n # ]7 f: d  w) P5 [- ?
    e
    $ L, Y1 Q0 X/ l& h6 t/ i ) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n minmin 或 m a x maxmax ), w ( j , i ) w(j, i)w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。
    + i% `- v) C3 ?- t  k1、1D/1D
    * u) l' E) E+ U; D! ^- ~d [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d = opt( d[j] + w(j, i) | 0 <= i < j )
    + g+ w( C1 S' l  D! Pd=opt(d[j]+w(j,i)∣0<=i<j)
    . K: Z' x6 p7 n5 A( w5 N! t0 K状态转移如图四所示(黄色块代表d [ i ] dd,绿色块代表d [ j ] d[j]d[j]):
    , E3 E( X  S* Z9 K! d5 H3 d! i+ `4 B; `9 S8 {
    1 t; `+ L& `  a& _  q8 H. O' N1 V
    这类状态转移方程一般出现在线性模型中。
    - \! h5 D( S4 ?5 A# x1 H* T4 ^2、2D/0D6 K# r2 R4 c+ b5 j: F
    d [ i ] [ j ] = o p t ( d [ i − 1 ] [ j ] + x i , d [ i ] [ j − 1 ] + y j , d [ i − 1 ] [ j − 1 ] + z i j ) d[j] = opt( d[i-1][j] + x_i, d[j-1] + y_j, d[i-1][j-1] + z_{ij} )
    # w" m. ~! O. b! }) r- zd[j]=opt(d[i−1][j]+x 4 W6 q; y6 ~9 u( n; O
    i
    - @/ w' m6 M" q. h2 r​       
    + V9 F  T' V; a4 m" Y8 l+ g/ c ,d[j−1]+y . J. s1 F& E; `4 w3 Z3 W
    j3 f% ]1 Z# B( R3 f( w" Y
    ​          g  c/ W) N% F5 E9 _7 Z+ q
    ,d[i−1][j−1]+z
    , Q* c: b0 H4 K- l% Kij
    " K  N1 a! N  l; N2 `- {​       
    6 w  L, A& \6 w. f+ S% E7 _$ T2 n* Q )
    & N% \7 \* D, T- C# ^状态转移如图四所示:
    ! U% u( y0 X% u1 x5 M& x5 d9 P0 M' G9 U

    & ?$ e7 ^* `4 h! ]0 j比较经典的问题是最长公共子序列、最小编辑距离。# L! K5 ?. Q+ j9 l
    有关最长公共子序列的问题,可以参考以下文章:夜深人静写算法(二十一)- 最长公共子序列
    " g8 Z  i2 i; Q0 Y% O" p& y0 @有关最小编辑距离的问题,可以参考以下文章:夜深人静写算法(二十二)- 最小编辑距离
    0 p/ q. T! R; S3 u3 S5 p( M9 {3、2D/1D% n! o% T. J6 v# {; ~( G* f
    d [ i ] [ j ] = w ( i , j ) + o p t ( d [ i ] [ k − 1 ] + d [ k ] [ j ] ) d[j] = w(i, j) + opt( d[k-1] + d[k][j] )# r/ p' n4 R9 O' ^; x2 q
    d[j]=w(i,j)+opt(d[k−1]+d[k][j])
    / e; }: l6 z; [9 ~区间模型常用方程,如图所示:
    , e3 f) {6 p0 O6 s" Z
    & |8 _9 z; T0 n8 J& M# @8 a

    - ]1 q$ ], m7 K* g另外一种常用的 2D/1D 的方程为:
      A9 @* s% {3 K, `* G- i  fd [ i ] [ j ] = o p t ( d [ i − 1 ] [ k ] + w ( i , j , k ) ∣ k < j ) d[j] = opt( d[i-1][k] + w(i, j, k) | k < j )4 H' K* K: W- j; ?5 X- r# S. C
    d[j]=opt(d[i−1][k]+w(i,j,k)∣k<j)) m! ^4 K$ p4 N
    区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP; K4 Q2 f" @9 \! w( J8 H
    4、2D/2D  p+ w# K' h( S9 h2 g
    d [ i ] [ j ] = o p t ( d [ i ′ ] [ j ′ ] + w ( i ′ , j ′ , i , j ) ∣ 0 < = i ′ < i , 0 < = j ′ < j ) d[j] = opt( d[i'][j'] + w(i', j', i, j) | 0 <= i' < i, 0 <= j' < j)
    0 \1 q3 p+ a9 A2 y, td[j]=opt(d[i
    : p; K/ p$ R9 @3 @) {2 N- a4 O4 e3 `! \  Z" n! s6 s
    ][j + f' Y+ V: W/ ?. g3 d
    ' g1 c9 A( ~/ z! f
    ]+w(i & b2 g% K. j4 \+ ^) K, q

    ( B! r7 [! N( V- C& p" R  \ ,j
    6 K5 h3 E( B/ L# V, w+ z8 }; a+ L9 o* X3 `+ y5 _& D% c
    ,i,j)∣0<=i . S" i; k$ u0 h- D7 O7 c

    - _6 W- L: U3 q; h5 L' [! n4 J$ V <i,0<=j . T& d+ U/ [! S, n1 h, _5 l

    + O# F0 K% h9 E& }/ E; \# w <j)! @  R7 U% n* n1 v( a
    如图所示:
    & M$ x, X7 y  }+ g+ H- H
    6 y+ `6 P) z+ X
    ' ^1 I! ?: \& r: T: T9 a0 x8 m
    常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。
    # `% Q' B9 M8 R* `+ i0 [. {$ F" W# U对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是O ( n t + e ) O(n^ {t+e})O(n " D# ^% N0 X* g0 Q
    t+e
    3 y" y  f% p2 L4 N# P0 } ),空间复杂度是O ( n t ) O(n^t)O(n % L: P6 c$ j: ^7 C; D2 S; Q& H
    t' X' {9 s  l& ^( Z( j( ]  y2 s
    ) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。- l4 _3 U' t. s5 a. X
    3)计算几何: t6 G/ W. T: K+ N" s/ L
    计算几何的问题是代码量最大的。它是计算机科学的一个分支,以往的解析几何,是用代数的方法,建立坐标系去解决问题,但是很多时候需要付出一些代价,比如精度误差,而计算几何更多的是从几何角度,用向量的方法来尽量减少精度误差,例如:将除法转化为乘法、避免三角函数等近似运算 等等。$ j" d( @- ?' C3 j& j/ A
    如果一个比赛中,有一道计算几何的题,那么至少,它不会是一道水题。( y; p5 G2 C: \3 ^$ }, `- ]
    1、double 代替 float( O' n8 `9 x( Z. k( T
    c++ 中 double 的精度高于 float,对精度要求较高的问题,务必采用 double;* \; K% F, Z% {6 D: K
    2、浮点数判定0 O2 g! T7 J- A9 @
    由于浮点数(小数)中是有无理数的,即无限不循环小数,也就是小数点后的位数是无限的,在计算机存储的时候不可能全部存下来,一定是近似的存储的,所以浮点数一定是存在精度误差的(实际上,就算是有理数,也是存在误差的,这和计算机存储机制有关,这里不再展开,有兴趣可以参见我博客的文章:C++ 浮点数精度判定);
    : s2 Q) _' K# h# `+ U两个浮点数是否相等,可以采用两数相减的绝对值小于某个精度来实现:
    8 M, }+ b5 `# ~( m) b0 \4 xconst double eps = 1e-8;
    ' {, r+ O  e5 X' dbool EQ(double a, double b) {* z( D4 @0 [# ?+ u! Z$ ]: \
        return fabs(a - b) < eps;3 C# z: s  Z( `  r- a
    }
    ! m4 [: ?* O9 P0 Y( r5 v1; ~+ K( }8 `& ~. z2 h3 `6 g* Z
    2
    ( |3 @1 P7 S$ j! o, h39 \- G! x: f5 O: V- N/ p0 V' C% ?  E
    42 q/ \9 Q+ J, B
    并且可以用一个三值函数来确定某个数是零、大于零还是小于零:
    : B) H* J8 K. K$ `5 Z& ?9 cint threeValue(double d) {
      x  o, r$ a5 M& d    if (fabs(d) < eps)
    & f8 ~9 O9 K8 T. Q2 L+ {5 f        return 0;
    * Y) X2 Y  y) q    return d > 0 ? 1 : -1;' I' W0 t7 K( m
    }
    ' J$ S6 `( _% W1 ~8 z0 K4 K1
    ) V: n' i) [2 |+ I26 D1 y# W( @8 A" O' v1 C8 g
    3& r! v: ~4 ^0 |
    4+ q; r" o  o6 q6 v3 w$ J
    5
    4 T! v( J6 ]0 S; J3、负零判定
    , ]: }& D$ k8 ]* @$ l3 U. t- ]因为精度误差的存在,所以在输出的时候一定要注意,避免输出 -0.00:
    # g' |& P2 s$ e! `0 Y    double v = -0.0000000001;9 B  @5 H# ]) Z
        printf("%.2lf\n", v);
    " D5 i* |7 n* t! I. u( R1& L1 `, ^6 K$ c7 w
    2
    & P. l0 j. [! k避免方法是先通过三值函数确定实际值是否为0,如果是0,则需要取完绝对值后再输出:
      {" {- W" O( J0 e$ @" U    double v = -0.0000000001;. m# h$ W% W1 _" H: A6 W8 {+ ]
        if(threeValue(v) == 0) {( [) C) G8 h  o5 N
            v = fabs(v);2 ~1 R+ D; M- ^& m
        }* _8 T6 o  z7 K3 U; Q( Y; d
        printf("%.2lf\n", v);( u/ t2 s0 a; o3 R# d# w) X
    14 X2 G1 O5 h1 s. S+ F6 [
    2
    ! ^4 S# A5 n- o  a( S4 q34 h6 W( z0 |! W9 b* _- P, C3 {$ B
    4
    + m. h% k6 B: E0 @0 u9 `, f; d5
    1 E1 E, @8 `9 k9 H, h1 {4、避免三角函数、对数、开方、除法等, A) `7 W  E) C  C
    c++ 三角函数运算方法采用的是 CORDIC算法,一种利用迭代的方式进行求解的算法,其中还用到了开方运算,所以实际的算力消耗还是很大的,在实际求解问题的过程中,能够避免不用就尽量不用。
    7 G/ `* R0 N9 [4 ]' v! }3 F: t1 t除法运算会带来精度误差,所以能够转换成乘法的也尽量转换为乘法运算。' [* E4 c, M- e3 d7 W
    5、系统性的学习" \" ~  n5 h* F. F) a) r6 O
    基础知识:点、向量、叉乘、点乘、旋转、线段、线段判交、三角形面积;  j8 |6 |/ T9 G" G# k  y5 M7 C! v
    进阶知识:多边形面积、凸多边形判定、点在多边形内判定;0 e/ H5 m8 [- x5 s7 P" F7 T
    相关算法:二维凸包、三维凸包、旋转卡壳、多边形面积交、多边形面积并、多边形面积异或、多边形和圆的面积交、半平面交、最小覆盖圆、最小包围球、模拟退火。: {/ w( K5 x/ a0 v  x& k! ~  a
      A6 a% q" V* G: y' {
    6 _. y6 l( {5 r# o! f5 O* M
    学习计算几何,最好是系统性的,刷题的过程中不断提炼出自己的模板。+ k: M# i; `9 ]
    4)数论
    % g, e. C* ]" n% A2 Z刷题的时候遇到不会的数论题,真的是很揪心,从头学起吧,内容实在是太多了,每个知识点都要证明吃透,不然下次遇到还是不会;不学吧,又不甘心,就是单纯的想把这个题过了,真是进退两难!
    + W% T: h& ]; U' Z2 K数论对一个人的数学思维要求较高,但是一般也是一些固定的模式,所以把模板整理出来很重要。
    3 F- o0 y/ b. n) a7 [当然,数论也有简单问题,一般先做一些入门题提升信心。
    0 q: ~$ W, b. s, W6 p* Q/ f+ ~1、数论入门2 l7 y8 u% ~9 Z& i2 P  `3 X
    主要是一些基本概念,诸如:! h" f' I( q$ g# }
    整除性、素数与合数、素数判定、素数筛选法、因数分解、算术基本定理、因子个数、因子和、最大公约数 (GCD) 和 最小公倍数 (LCM)、辗转相除、同余、模运算、快速幂取模、循环节;& T% S4 }, l9 g6 |
    2、数论四大定理
    - L5 u8 ?4 v  X" H+ z2 l: J* K这四个定理学完,可以KO很多题:
    + S3 |* ~, \( O: }5 A欧拉定理、中国剩余定理、费马小定理、威尔逊定理6 p1 w" V1 Q' T' M+ U- [
    3、数论进阶
    ' V3 ]6 F9 F6 i; p; W- \系统性的学习,基本也就这些内容了:) P6 q$ H( C2 r5 b% y
    扩展欧几里得、逆元、欧拉函数、同余方程组、扩展欧拉定理、RSA、卢卡斯定理、整数分块、狄利克雷卷积、莫比乌斯反演、大数判素、大数因子分解、大步小步离散对数等等。: P! X4 s9 W" Q2 t5 S8 u0 d7 s
    5)字符串匹配6 q; e* F  b! H% j" ?0 r
    字符串匹配学习路线比较明确。. s0 |# Z! J1 S, @7 w
    先学习前缀匹配:字典树。
    8 p" @, f& c- g7 D, Q然后可以简单看一下回文串判定算法:Manacher。
    6 h# Q/ S" e* S! i以及经典的单字符串匹配算法:KMP。9 w6 z% ^) U4 o% ^" o+ c' f" t
    实际上平时最常用的还是 BM 算法,而ACM中基本不考察。! r* D- I# X& H+ m+ z* c
    然后就是较为高阶的 前缀自动机、后缀数组、后缀树、后缀自动机了。
    1 v+ E4 r. Z& \- ~4 t关于 算法学习路线 的内容到这里就结束了。0 V7 n* X7 d/ b1 [, I6 w$ ~: Y  e5 q
    如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。% _1 F" f& e8 y5 w$ \3 b
    参考资料
    % c, {9 i# Q5 i1 [【阶段一】C语言学习资料:《光天化日学C语言》(日更)
    1 R: T! i" @7 m【阶段二】C语言例题:《C语言入门100例》(日更)0 S& q$ R( H' m- A: ?
    【阶段三】算法入门题集:《LeetCode算法全集》(日更)
    . x" e# N2 e$ x: @【阶段四】算法进阶:《夜深人静写算法》(周更)/ u* F# A  R% C  d5 V% H5 U
    ————————————————, u3 ~$ S' {: q* |, N
    版权声明:本文为CSDN博主「英雄哪里出来」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    0 @  P& j5 w, X+ S原文链接:https://blog.csdn.net/WhereIsHeroFrom/article/details/118382228' K6 ?/ S9 V' r# w! R0 U8 J
    ! R6 K1 w( \# V; H1 w) G9 G

    4 P) h! z; C% J4 P+ D: r# z# i" T
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信

    0

    主题

    10

    听众

    299

    积分

    升级  99.5%

  • TA的每日心情
    开心
    2023-10-14 10:28
  • 签到天数: 28 天

    [LV.4]偶尔看看III

    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2026-7-31 15:04 , Processed in 0.450970 second(s), 61 queries .

    回顶部