(入门向)面向萌新的算法比赛入门指南

在这里插入图片描述

什么是算法

算法是指解决问题或完成特定任务的一系列明确指令或步骤集合。它是一个定义良好、逐步执行的操作序列,用于将输入转换为输出。算法可用于计算、数据处理、自动化控制、问题解决等各个领域。

算法通常由一系列简单的操作组成,这些操作可以是基本的数学运算、逻辑判断、条件分支、循环控制等。通过组合和重复执行这些操作,算法能够解决更加复杂的问题。

在计算机科学中,算法是程序设计的基础。程序员使用算法来描述和实现解决问题的步骤,从而使计算机能够按照指定的方式执行任务。不同的算法可能会根据问题的特性和要求产生不同的结果和效率。

算法的设计和分析是计算机科学的核心内容之一。好的算法应当具有正确性、可读性、高效性和可扩展性。算法的性能评估和优化是提高计算效率和解决问题的关键。

在算法比赛中,参赛者需要运用各种不同的算法来解决问题。一些常见的算法及其在比赛中的应用:

  1. 贪心算法(Greedy algorithm):贪心算法是一种每次选择当前最优解的策略。它通常适用于那些具有最优子结构性质的问题,每一步的选择都不会影响后续步骤的结果。贪心算法在某些场景下可以高效地找到近似最优解,如最小生成树、任务调度等。

  2. 动态规划(Dynamic programming):动态规划将复杂问题分解为相互重叠的子问题,并通过存储子问题的解来避免重复计算。它通常适用于具有重叠子问题和最优子结构性质的问题。动态规划在解决最短路径、背包问题、字符串匹配等方面有广泛应用。

  3. 回溯法(Backtracking):回溯法是一种通过递归地尝试所有可能的解空间来求解问题的方法。它在搜索问题、组合问题、迷宫问题等方面表现出色。回溯法通过深度优先搜索来穷举解空间,同时利用剪枝技术减少不必要的搜索。

  4. 分治法(Divide and Conquer):分治法将问题划分为独立的子问题,然后递归地解决每个子问题,并合并子问题的解来得到最终的解。它在排序算法(如快速排序、归并排序)、查找问题(如二分查找)以及一些优化问题中广泛使用。

  5. 图算法:图算法用于解决与图相关的问题,如最短路径(Dijkstra算法、Floyd-Warshall算法)、最小生成树(Prim算法、Kruskal算法)、拓扑排序、图染色等。

此外,还有许多其他的经典算法和数据结构,如排序算法(冒泡排序、插入排序、堆排序)、搜索算法(深度优先搜索、广度优先搜索)、字符串匹配算法(KMP算法、Boyer-Moore算法)等,在算法比赛中都可能被使用。参赛者需要根据问题的特点选择合适的算法,并进行优化以达到更好的效果。

不同的比赛可能会有不同的限制和要求,参赛者也应该熟悉比赛规则和评测系统,针对性地选择和实现算法。

计算机系学生为什么要刷算法——— by 柳婼

很多⼈(尤其是⼀些初⼊⻔开发的计算机学⽣)可能会觉得算法在开发过程中的⽤处不⼤,甚⾄很多没有认真学过算法的⽼程序员都会这样认为,然⽽并不是这样~

⾸先,刷算法可以培养清晰的逻辑思维能⼒,改变⼈的思维⽅式,使⼈对待复杂的代码问题更容易看透本质~在未来做⼯程项⽬的过程中也会慢慢发现,当年认真学过的算法也并⾮当初想得那样毫⽆⽤处,反⽽能提⾼解决问题的效率,使⾃⼰能⽤更简单的⽅法、更精简的代码解决实际问题~

其次,看过很多IT公司招聘信息的⼈就会发现,他们都会优先选择有⼀定算法基础的⼈,尤其是⼀些⼤公司在⾯试过程中,对于应届⽣,更多考察的是数据结构与算法、计算机⽹络、操作系统等这些计算机基础课程,⽽并⾮都是在考察实际应聘岗位的相关技术、⼯具、知识等~

再者,提⾼算法能⼒不是⼀蹴⽽就的,需要静下⼼来⽤⼤⽚的时间去看书、学习、刷题、找bug或者参加竞赛,也许市⾯上有很多开发类的培训班,如前端、JavaWeb、安卓之类,但鲜有算法类的培训,因为学习算法是实实在在、需要⾃⼰付出努⼒的过程,⾃⼰对题⽬的思考、学习、钻研,别⼈⽆法替代,也不可能通过培训速成。所以这也是为什么很多公司会在应聘的笔试⾯试环节考察算法能⼒的原因,也许语⾔的特性、⼯具的使⽤、开发的技巧可以短期培训,但数据结构和算法的能⼒⾜以证明应聘者在计算机知识⽅⾯的硬实⼒~⽽且很多⼤公司在考察算法时都是⽩板编程,这⽐写代码更能考验应试者的⽔平~

最后,如果从短期⻆度来说,可能刷算法能带来的⼀些看得⻅的收益会给⾃⼰更多的动⼒,⽐如:PAT⼄级刷完可以让⾃⼰在C语⾔⼆级或者C语⾔期末考试中获得⼀个⾼分;PAT甲级能让⾃⼰在考浙⼤时候直接抵复试中机试的成绩;PAT甲级考⾼分能让⾃⼰收到⼀些⼤公司的⾯试邀请;LeetCode能够让⾃⼰在⾯试很多⼤公司尤其是国外⼀些公司(Google、微软、Facebook、Amazon等)时正确解答出笔试和⾯试的算法问题等…⽆论是⻓期还是短期来看,刷算法对于有追求的计算机学⽣都是必经之路~

在这里插入图片描述

什么是OJ,以及对AC、WA、TLE、CE、RE、MLE、PE等状态术语的解释

OJ(Online Judge)是指在线判题系统,将代码提交给OJ后,OJ会在线检测程序源代码的正确性,并返回结果~

国内著名的OJ系统有POJ(北京⼤学OJ)、杭电OJ(参加过ACM的⼈都知道)等,PAT的官⽅题库、蓝桥杯题库和LeetCode也是OJ系统,都可以在线提交代码并得到返回结果~

PAT考试过程中使⽤的就是和平时刷题题库⼀样的OJ判题系统,⽽蓝桥杯在考试的时候只能提交答案,不能实时看到提交的答案是否正确,但蓝桥杯平时刷题练习的题库是OJ~

相⽐⽽⾔,PAT和蓝桥需要的都是完整的有输⼊输出的代码段,⽽LeetCode并不需要输⼊输出,⽽是将需要填写的代码包在⼀个Solution类⾥,甚⾄会给好Solution类中的函数,涉及到树的题⽬还会给出树的定义,我们只需要在这个函数中填充代码并按照题⽬的要求返回相应类型的值即可~

刷算法OJ过程中提交答案后会返回的⼀些状态术语,

AC (Accepted = 答案正确),WA (WrongAnswer = 答案错误), TLE (Time Limit Exceeded = 运⾏超时 / 时间超限), CE (Compile Error =编译错误), RE (Runtime Error = 运⾏时出错), MLE (Memory Limit Exceeded = 内存超限), PE (Presentation Error = 格式错误)

如果刷过leetcode的⼈对这些应该⽐熟悉~所以我们经常会说 AC 了几道题、 TLE 了或者 WA 了之类的~⽽PAT的OJ⾥返回的是中⽂反馈信息,例如答案正确、格式错误、内存超限、运⾏超时、段错误等~

在这里插入图片描述
在这里插入图片描述

相关链接:

算法竞赛从了解入坑到快速放弃指南
https://www.zhihu.com/tardis/zm/art/29598587?source_id=1005

算法竞赛并不是适合所有的人,需要坚持,坚持,再坚持,半途而废的人很多很多;入门不需要太深的数学能力,毕竟我们是工科生,并非理科生;入门的话,数理化没什么大碍;但是,想要一直深入走下去,走向ICPC——数学能力十分重要、数学专业的学生优势更大;走着走着,你会发现你的对手可能就是双一流大学的、信息学竞赛保送的、花式保送的、从小学开始学起计算机语言的(直接起点比你早五六年)等等。

在这里插入图片描述

零基础的大一新生如何学好算法

如果你是一个零基础的大一新生,想要学好算法,以下是一些建议:

  1. 建立计算机基础知识:首先,确保你对计算机科学基础有一定的了解。学习计算机组成原理、数据结构和算法分析等基础概念。可以通过阅读相关教材、观看在线教育平台的视频课程或参加大学的计算机基础课程来学习。

  2. 学习编程语言:选择一门常用的编程语言作为学习工具,如Python或Java。这些语言相对易于学习,拥有丰富的资源和社区支持。通过学习编程语言,你将能够理解和实现算法。

  3. 掌握基本的数据结构:数据结构是算法的基础。学习各种常见的数据结构,如数组、链表、栈、队列、树和图等。理解它们的特点、操作和应用场景。可以使用教材、在线教程和练习题来巩固学习。

  4. 学习基本的算法概念:了解各种基本算法的概念和原理,如排序、查找、递归、动态规划和贪心算法等。通过阅读教材、观看在线视频和参考实例代码来学习算法。

  5. 刷题练习:通过刷题来提高算法能力。开始从简单的题目开始,逐渐增加难度。可以使用在线编程平台(如LeetCode、HackerRank等)提供的算法题库来进行练习。

  6. 寻找合适的学习资源:在学习过程中,寻找一些优质的学习资源,如经典的算法教材、在线教程、博客和视频教程等。这些资源将为你提供更多的学习内容和实践经验。

  7. 多与他人交流与合作:参加学习小组、参与在线算法社区或与同学进行讨论与交流。与他人分享知识和经验,共同解决问题,能够加深理解并拓宽思路。

  8. 注重实践和项目经验:将所学的算法知识应用于实际项目中。尝试解决一些实际问题,或者参与一些开源项目。通过实际操作,加强对算法的理解和应用能力。

记住,学习算法需要时间和坚持。不要急于求成,要保持积极的学习态度,并通过不断地实践和挑战来提高自己的算法水平。
在这里插入图片描述

C语言入门强烈推荐浙江大学翁恺老师的课程

关于ICPC

ICPC的全程为International Collegiate Programming Contest,是世界上历史最悠久、规模最大、最负盛名的编程竞赛。因为在1977-2017期间该赛事由ACM(计算机协会)主持,所以也有人叫它ACM-ICPC。ICPC比赛主要面
向大一到研一的同学。

在中国还有一项赛事为CCPC(中国大学生程序设计竞赛),主要面向国内高校,赛制与难度与ICPC持平,习惯上我们把ICPC与CCPC统称为XCPC。

如果要参加XCPC相关比赛,通常来讲需要先选拔进入校队,然后三人组队。每年每个人只能参加两次ICPC区域赛,根据比赛结果会角逐出进入EC final甚至World final的队伍。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

XCPC相关比赛是一场关于智力与汗水的比拼,有着极高的难度上限,参赛者能享受到算法竞赛实时竞技无与伦比的乐趣。如果你也爱好解决难题,挑战自己的极限,请一定不要错过。

推荐给萌新的训练方法

刚刚入坑

学习算法竞赛其实不用太依赖于纸质资料,参考网上的资源已足够,XCPC涉及的算法可参见OI-wiki如果刚刚接触算法竞赛,请不要过分执着于学习过于复杂的算法,而是要着眼于提升分析问题,解决实际题目的基本能力。这里推荐大家参加Codeforces的div1-div4系列比赛(Codeforces游玩攻略)

但是Codeforces(以下简称为cf)的比赛多在晚上10点35分举行,如果时间安排不过来的,可以下来自己做题,也可使用virtual contest定时训练。

在这个阶段里,建议大家在做题过程中学习未掌握的算法。切记要多多阅读题解给出的标程,并学习标程的代码写法,这样大有裨益。在做题时,推荐大家要想清楚思路后再写,时刻牢记,Think twice,Code once

入门之后

当cf的rating来到1700之后,大家可以逐渐学习一些较复杂的算法,可以结合OI-wiki。其实cf的比赛题目码量和涉及到的算法复杂程度还是逊于常规ICPC区域赛的,所以大家还要多去cf的Gym中做做真题。

这里推荐一下笔者使用Virtual Judge的方法。
假如想要提升cf的rating,可以自己在Virtual Judge上拉一场比赛,主要选择在自己水平线或者之上的题目。例如,你的cf rating为1900,则可以拉3道div2D,3道div2E的题目,组成一场时长三小时的比赛,然后训练结束后再补完所有题目。训练完一场区域赛后,总会有题目没有做出来或者甚至没时间看。对于那些通过人数较少的题目,建议结合自身情况补题,希望大家有敢于与题目死磕的精神,其实一道题做一两天是很正常的当然,如果过于困难了还是要懂得放弃,君子报仇十年不晚。如果有些题目训练时没有看,但是通过的人数较多,是自己有可能独立做出的,可以记录下来,累计到3-4题后,可以用这些题目去Virtual Judge上拉一场二-三小时的比赛。

写在后

ICPC需要长期的付出与训练,并不是一个投资少见效快的“高性价比”比赛。但是这项赛事对于能力的提升是巨大的,特别是那些想要本科毕业就进入大厂就业的同学来说,ICPC的参赛经历是一块宝贵的敲门砖。但是不论你是为了毕业后更好的就业,还是说单纯喜欢学习算法与数学,你一定都能在比赛中收获良多。

在这里插入图片描述

计协大三学长建议:

省赛
如果你是从未接触过算法竞赛的小白,那你首先要考虑的内容是如何面对省赛,毕竟拿到省一等奖才有资格去参加国赛嘛,那你所需要准备的内容以及难度不算太难.

语法 思维(cf 800)

一.算法(序号表示优先级)

1.枚举
掌握枚举思想,就是写暴力,省赛会写暴力加一点点算法就能出线了,这里的枚举主要包括,循环遍历、DFS
Oi 蓝桥杯

Acm icpc 各种周赛

ioi 天梯赛 cccc gplt

在这里插入图片描述

2.排序
一般配合着枚举使用,一般来说会sort就好了,当然作为学习来说手写一下冒泡、选择、归并、快排、堆排 等都是可以的。(掌握sort、 priority_queue(堆))
Stl(c++)

3.双指针、前缀和、二分、差分、递推
常用得基本算法思想用来优化算法复杂度的入门算法(大量刷题、这是基本分)

4.最大公约数、最小公倍数、素数
掌握其中关系、素数筛(欧拉筛、埃氏筛)

5.动态规划(学到这就有机会省一了)

6.图论、字符串算法

最短路径(Dijktra、spfa、Floyd)、最小生成树(prim、Kruskal)、BFS、DFS、KMP、字典树

二.基础数据结构(无先后顺序)

其实在第一部分的内容里就会学到相关的内容了,主要配合着算法使用、作为一种思想

1.栈

单调栈

2.队列(BFS)

单调队列

3.堆(堆排序 priority_queue)

4.链表(邻接表的思想)

5.哈希表

6.并查集(Kruskal)
其实数据结构的实现在各种语言中都有封装好现成的可以使用(C++STL是必须要会用的)

具体训练:
首先是根据要学的内容去刷题,一题一题刷,这里首推洛谷题单,整理的很好,只要跟着写就好了.建议把入门也刷了,可以练一下基本功,否则要是被一些奇怪的输出输入卡了就不好了.其次可以去牛客,有条件的买课只推Acwing跟着练就完了.

在算法竞赛中,一定要记住,思维能力和直觉是核心驱动力,在思维能力达到一定层次以后,算法的学习能力也自然而然上来。

1、快速将语法过关,学习基础算法,如单调栈,动态规划,dfs,Ica, 二分,基础图论,整除,取余,尺取,贪心等,可以上洛谷官方题单板刷,然后去刷hdu入门题,也可以去acwing刷刷基础

2、打开codeforces, 千万不要没听过就害怕!只要尝试去做几道题,就会不断收益良多! ! !在problemset里选1000到1600级别题中找到适合自己难度的题,可以二分查找(check是30分钟内能否独立查出), 最好选择contrustive(构造) 对思维提高最大,然后看看能不能30分钟独立推理出来,一定要独立思考,不要觉得怎么也想不到就觉得受挫,也不要立刻看题解!在怎么也想不到的情况下寻找一条路是锻炼直觉的关键,这种想不到却依旧得去想的经历也是直觉提高的重要铺垫! (除非你是天才),不断去做,如果连续几次在30分钟内想出这个级别题,就去做这个级别题+100难度的题,然后坚持想30分钟,在想的过程中不断寻找可能的方向,想不出来看题解,有不知道的小技巧,小坑点都要记下来,有不懂的算法或特殊应用可以学会,用这种方法不断把上限提高

(重点)3、经常打acwing,牛客,和cf的比赛,不断验证自己的实力,而且打线上比赛很重要的是能提高竞赛能力,同时把比赛中会踩的坑都踩了,在一定的场次之后就可以只作为验证实力提高的标准了

在这里插入图片描述

刷题过程中如何平衡⾃⼰写代码和看他⼈代码的⼩纠结——— by 柳婼

可能很多⼈在刷算法过程中,会觉得⾃⼰写不出来的时候看了别⼈写的代码就不是⾃⼰的了,感觉像是抄了⼀遍别⼈的代码,觉得不是⾃⼰想出来的印象不够深刻,可能因为⼩时候做数学题的时候,发现⽼师讲了⼀遍的数学题⾃⼰没记住,但是⾃⼰独⽴思考然后做出来的却印象很深刻…所以觉得写代码的过程中也应该尽量保持独⽴⾃主完成~其实我觉得这样的想法是不太对的哦~

算法这个神奇的东东,有它⾃身的⼀些特点,⽐如⼀道PAT题⽬,可能你看了题⽬后觉得⾃⼰有⼀点思路了,毕竟只是给个输⼊要求你给出正确的输出嘛,或多或少还是有些⾃⼰的想法的,就开始⾃⼰写,结果没能AC,修修补补改改也勉强最后AC了,但是代码却冗⻓繁琐,过阵⼦让你再做⼀遍这道题⼜没有思路了…

算法题就是这样,总给你⼀种好像也不是太难的感觉,⽽且这种提交后会看到⾃⼰得分的真题题库总会让⼈产⽣⼀种当作⼀次正式考试测试⼀下⾃⼰的⽔平的想法,导致很多⼈刷算法完完全全就是在把⾃⼰仅有的思维和编程语法知识完全倒出来展现在代码⾥,如果这个⼈是个竞赛⾼⼿倒也没什么关系,但是如果基础不太好,直接⾃⼰写⽽排斥看他⼈代码的想法是对⾃⼰的算法提升是⾮常不利的,可能你冗⻓⽽思路不够清晰的代码确实AC了这道题,但是你可能也错过了向更优秀思路的代码学习的机会。

反⽽那些没什么思路的⼈,可能去看了别⼈优秀的代码,让⾃⼰学会遇到这类算法题的清晰思路,还学了⼀些下次能⽤得到的编程语⾔技巧(⽐如18年12⽉PAT考试结束后,⼀位可爱的⼩学弟来感谢我学了我代码中的s.substr()的⽤法,让他考试的时候直接AC了⼀道题,增强了考试时的信⼼,考试的后半段时间做题状态很好多拿了很多分)所以我建议不管这道题你写出来的代码是AC了还是做错了找不到bug,都应该看⼀看别⼈解这道题的代码是不是和你思路相同~

在我刷题的时候,如果⾃⼰的代码和别⼈思路⽅法完全不同,那我会思考,我所写的⽅法是不是⽐别⼈写的代码优秀?很多时候会发现,并不能找到错误原因的那段代码本身逻辑就为混乱,所以我的建议是直接删掉原来写的,对⾃⼰写的代码⽤更好的⽅法进⾏重构,因为即使这段代码勉强调试写出来了,下⼀次⻅到它还是难以理解,对⾃⼰的考前复习也是⼀种打击,会让⾃⼰看到这段代码就想要跳过不看,⽽且还让⾃⼰错过了⼀次学习他⼈优秀⽅法的机会,要知道刷题的真正意义是学到知识呀~

经典题目分析leetcode第一题

(有人相爱,有人夜里开车看海,有人leetcode第一题都做不出来​​)
在这里插入图片描述

题目

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

提示:

2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案

进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?

分析

方法一:暴力枚举

思路及算法

最容易想到的方法是枚举数组中的每一个数 x,寻找数组中是否存在 target - x。

当我们使用遍历整个数组的方式寻找 target - x 时,需要注意到每一个位于 x 之前的元素都已经和 x 匹配过,因此不需要再进行匹配。而每一个元素不能被使用两次,所以我们只需要在 x 后面的元素中寻找 target - x。

class Solution {
    
    
public:
    vector<int> twoSum(vector<int>& nums, int target) {
    
    
        int n = nums.size();
        for (int i = 0; i < n; ++i) {
    
    
            for (int j = i + 1; j < n; ++j) {
    
    
                if (nums[i] + nums[j] == target) {
    
    
                    return {
    
    i, j};
                }
            }
        }
        return {
    
    };
    }
};

来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

上述代码是一个简单的解决LeetCode上"两数之和"问题的实现。下面对该代码的复杂度进行分析。

设输入的数组长度为n。

  1. 外层循环:i从0到n-1,共进行n次迭代。
  2. 内层循环:j从i+1到n-1,平均情况下进行(n-1-i)/2次迭代(假设n足够大)。

因此,总的迭代次数为:
n + (n-1) + (n-2) + … + 1 ≈ n^2 / 2

由于内层循环中只有常数次的操作,可以忽略不计,所以算法的时间复杂度为O(n^2)。

在空间复杂度方面,除了存储结果的返回数组外,算法并没有使用额外的空间,因此空间复杂度为O(1)(常数级别)。

需要注意的是,由于解决两数之和问题的最优解是哈希表,其时间复杂度为O(n),因此上述代码存在较高的时间复杂度,不是最优解。但在一些规模较小的问题上,该算法的实际性能可能仍然可接受。

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/two-sum/solution/liang-shu-zhi-he-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

方法二:哈希表

在这里插入图片描述

哈希表(Hash Table),也被称为散列表,是一种常用的数据结构。它通过利用哈希函数(Hash Function)来实现快速的数据插入、删除和查找操作。

哈希表由一个数组和哈希函数组成。哈希函数将每个元素映射到数组中的一个位置,该位置称为哈希值或哈希码。数组的索引即为哈希值,可以直接访问到对应位置的元素。

具体的工作原理如下:

  1. 对于要插入或查找的元素,首先经过哈希函数得到它的哈希值。
  2. 将元素存储在计算得到的哈希值对应的数组位置上。
  3. 若存在相同的哈希值,则可能发生冲突(Hash Collision)。冲突可以通过使用开放寻址法和链地址法来解决。
    • 开放寻址法:当发生冲突时,不断地探测下一个空闲位置,直到找到合适的位置插入元素。
    • 链地址法:在哈希表的每个位置上维护一个链表,每个链表存储哈希值相同的元素,冲突时将元素插入链表中。
  4. 插入和查找元素时,再次通过哈希函数计算哈希值,然后在对应位置上进行操作。

哈希表通过利用哈希函数的快速计算和数组的随机访问特性,实现了高效的元素插入、删除和查找。在理想情况下,哈希表的插入、删除和查找操作的平均时间复杂度为O(1)。

哈希表在很多应用中都得到广泛的使用,例如数据库索引、缓存系统、编译器中的符号表等。

思路及算法

注意到方法一的时间复杂度较高的原因是寻找 target - x 的时间复杂度过高。因此,我们需要一种更优秀的方法,能够快速寻找数组中是否存在目标元素。如果存在,我们需要找出它的索引。

class Solution {
    
    
public:
    vector<int> twoSum(vector<int>& nums, int target) {
    
    
        unordered_map<int, int> hashtable;
        for (int i = 0; i < nums.size(); ++i) {
    
    
            auto it = hashtable.find(target - nums[i]);
            if (it != hashtable.end()) {
    
    
                return {
    
    it->second, i};
            }
            hashtable[nums[i]] = i;
        }
        return {
    
    };
    }
};

上述代码是使用哈希表来解决LeetCode上"两数之和"问题的实现。该算法的时间复杂度为O(n),空间复杂度为O(n)。

具体分析如下:

  1. 创建一个哈希表 hashtable,用于存储数组中的元素及其对应的索引。
  2. 遍历数组 nums,对于每个元素 nums[i],进行以下操作:
    • 在哈希表 hashtable 中寻找是否存在 target - nums[i] 的键值对,即找到了另外一个数与当前数之和为目标值 target。
    • 如果找到了,返回对应的索引和当前元素的索引。
    • 如果没有找到,将当前元素 nums[i] 插入哈希表 hashtable 中,键为元素值,值为元素索引。
  3. 如果遍历结束仍未找到满足条件的元素对,返回空数组。

在这个算法中,通过哈希表的快速查找特性,将寻找 target - x 的时间复杂度降低为O(1)。因此,总的时间复杂度为O(n)。同时,哈希表用来存储元素及其索引,所需的额外空间为O(n)。

相比于方法一,方法二使用哈希表使得算法的时间复杂度大幅降低,是更优的解法。

进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?

当然可以!通过使用两个指针,可以将时间复杂度降低到O(n)。下面是一个优化的算法:

假设有一个排序好的数组,并且我们要找到两个数之和等于目标值target。使用双指针方法可以高效地解决这个问题。

  1. 初始化两个指针left和right,分别指向数组的开头和结尾。
  2. 计算两个指针指向的元素的和sum。
    • 如果sum等于target,那么找到了两个数的和等于目标值,返回它们的索引。
    • 如果sum小于target,说明需要增加两个数的和,因此将left指针右移一位。
    • 如果sum大于target,说明需要减小两个数的和,因此将right指针左移一位。
  3. 重复步骤2直到找到满足条件的两个数,或者left大于等于right时停止搜索。

这个算法的时间复杂度为O(n),因为在最坏情况下,我们需要遍历数组一次。而空间复杂度仍为O(1),因为只需要额外存储两个指针的索引。

需要注意的是,这个方法适用于已经排序好的数组。如果输入的数组无序,我们可以先对其进行排序,然后再使用双指针方法。

def two_sum(nums, target):
    left = 0
    right = len(nums) - 1
    
    while left < right:
        sum = nums[left] + nums[right]
        
        if sum == target:
            return [left, right]
        elif sum < target:
            left += 1
        else:
            right -= 1
    
    # 若找不到满足条件的两个数,则返回空列表
    return []

# 示例输入
nums = [-2, 0, 3, 6, 7, 9, 11]
target = 9

result = two_sum(nums, target)
if result:
    print("找到满足条件的两个数的索引:", result)
else:
    print("找不到满足条件的两个数")

在这里插入图片描述

代码随想录:

https://www.bilibili.com/video/BV1aT41177mK/

思路

很明显暴力的解法是两层for循环查找,时间复杂度是O(n^2)。

本题呢,则要使用map,那么来看一下使用数组和set来做哈希法的局限。

数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下表位置,因为要返回x 和 y的下表。所以set 也不能用。
此时就要选择另一种数据结构:map ,map是一种key value的存储结构,可以用key保存数值,用value在保存数值所在的下表。

C++中map,有三种类型: 如图

std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。

同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。 更多哈希表的理论知识请看关于哈希表,你该了解这些!。

这道题目中并不需要key有序,选择std::unordered_map 效率更高!使用其他语言的录友注意了解一下自己所用语言的map结构的内容实现原理。

接下来需要明确两点:

map用来做什么
map中key和value分别表示什么
map目的用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下表,这样才能找到与当前元素相匹配的(也就是相加等于target)

接下来是map中key和value分别表示什么。

这道题 我们需要 给出一个元素,判断这个元素是否出现过,如果出现过,返回这个元素的下标。

那么判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。

所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下表}。

在遍历数组的时候,只需要向map去查询是否有和目前遍历元素比配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。

过程如下:

!过程一

!过程二

C++代码:

class Solution {
    
    
public:
    vector<int> twoSum(vector<int>& nums, int target) {
    
    
        std::unordered_map <int,int> map;
        for(int i = 0; i < nums.size(); i++) {
    
    
            // 遍历当前元素,并在map中寻找是否有匹配的key
            auto iter = map.find(target - nums[i]); 
            if(iter != map.end()) {
    
    
                return {
    
    iter->second, i};
            }
            // 如果没找到匹配对,就把访问过的元素和下标加入到map中
            map.insert(pair<int, int>(nums[i], i)); 
        }
        return {
    
    };
    }
};

总结

这道题目关键是在于明确map是用来做什么的,map中的key和value用来存什么的。

这个想清楚了,题目代码就比较清晰了。

很多录友把这道题目 通过了,但都没想清楚map是用来做什么的,以至于对代码的理解其实是 一知半解的。

其他语言版本

在这里插入图片描述

Java:

public int[] twoSum(int[] nums, int target) {
    
    
    int[] res = new int[2];
    if(nums == null || nums.length == 0){
    
    
        return res;
    }
    Map<Integer, Integer> map = new HashMap<>();
    for(int i = 0; i < nums.length; i++){
    
    
        int temp = target - nums[i];
        if(map.containsKey(temp)){
    
    
            res[1] = i;
            res[0] = map.get(temp);
        }
        map.put(nums[i], i);
    }
    return res;
}
Python:

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        records = dict()

        # 用枚举更方便,就不需要通过索引再去取当前位置的值
        for idx, val in enumerate(nums):
            if target - val not in records:
                records[val] = idx
            else:
                return [records[target - val], idx] # 如果存在就返回字典记录索引和当前索引
Go:

func twoSum(nums []int, target int) []int {
    
    
    for k1, _ := range nums {
    
    
        for k2 := k1 + 1; k2 < len(nums); k2++ {
    
    
            if target == nums[k1] + nums[k2] {
    
    
                return []int{
    
    k1, k2}
            }
        }
    }
    return []int{
    
    }
}
// 使用map方式解题,降低时间复杂度
func twoSum(nums []int, target int) []int {
    
    
    m := make(map[int]int)
    for index, val := range nums {
    
    
        if preIndex, ok := m[target-val]; ok {
    
    
            return []int{
    
    preIndex, index}
        } else {
    
    
            m[val] = index
        }
    }
    return []int{
    
    }
}
Rust

use std::collections::HashMap;

impl Solution {
    
    
    pub fn two_sum(nums: Vec<i32>, target: i32) -> Vec<i32> {
    
    
        let mut map = HashMap::with_capacity(nums.len());

        for i in 0..nums.len() {
    
    
            if let Some(k) = map.get(&(target - nums[i])) {
    
    
                if *k != i {
    
    
                    return vec![*k as i32,  i as i32];
                }
            }
            map.insert(nums[i], i);
        }
        panic!("not found")
    }
}
Javascript

var twoSum = function (nums, target) {
    
    
  let hash = {
    
    };
  for (let i = 0; i < nums.length; i++) {
    
    
    if (hash[target - nums[i]] !== undefined) {
    
    
      return [i, hash[target - nums[i]]];
    }
    hash[nums[i]] = i;
  }
  return [];
};
php

function twoSum(array $nums, int $target): array
{
    
    
    for ($i = 0; $i < count($nums);$i++) {
    
    
        // 计算剩下的数
        $residue = $target - $nums[$i];
        // 匹配的index,有则返回index, 无则返回false
        $match_index = array_search($residue, $nums);
        if ($match_index !== false && $match_index != $i) {
    
    
            return array($i, $match_index);
        }
    }
    return [];
}
Swift:

func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
    
    
    var res = [Int]()
    var dict = [Int : Int]()
    for i in 0 ..< nums.count {
    
    
        let other = target - nums[i]
        if dict.keys.contains(other) {
    
    
            res.append(i)
            res.append(dict[other]!)
            return res
        }
        dict[nums[i]] = i
    }
    return res
}
Scala:

object Solution {
    
    
  // 导入包
  import scala.collection.mutable
  def twoSum(nums: Array[Int], target: Int): Array[Int] = {
    
    
    // key存储值,value存储下标
    val map = new mutable.HashMap[Int, Int]()
    for (i <- nums.indices) {
    
    
      val tmp = target - nums(i) // 计算差值
      // 如果这个差值存在于map,则说明找到了结果
      if (map.contains(tmp)) {
    
    
        return Array(map.get(tmp).get, i)
      }
      // 如果不包含把当前值与其下标放到map
      map.put(nums(i), i)
    }
    // 如果没有找到直接返回一个空的数组,return关键字可以省略
    new Array[Int](2)
  }
}
C#:

public class Solution {
    
    
    public int[] TwoSum(int[] nums, int target) {
    
    
        Dictionary<int ,int> dic= new Dictionary<int,int>();
        for(int i=0;i<nums.Length;i++){
    
    
            int imp= target-nums[i];
            if(dic.ContainsKey(imp)&&dic[imp]!=i){
    
    
               return new int[]{
    
    i, dic[imp]};
            }
            if(!dic.ContainsKey(nums[i])){
    
    
                dic.Add(nums[i],i);
            }
        }
        return new int[]{
    
    0, 0};
    }
}

图解算法:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

比赛举例

https://zhuanlan.zhihu.com/p/120404245

蓝桥杯(官网:http://dasai.lanqiao.cn/pages/dasai/index.html)
报名时间:一般为每年9月份-12月份。
举办时间:一般是报名次年的3月份省赛、6月份决赛(2020年因疫情推迟),拿到省赛一等奖才能进入在北京举行的全国总决赛。每年举办一次,2020年为第11届。
报名费:300元。
可选语言:c/c++,java,python。
参赛要求:具有正式全日制学籍并且符合相关科目报名要求的研究生、本科及高职高专学生(以报名时状态为准),以个人为单位进行比赛。
分组说明:研究生组、大学A组、大学B组、大学C组。研究生只能报研究生组。985、211本科生只能报大学A组及以上组别,其它院校本科生可自行选择报大学B组及以上组别,高职高专院校可报大学C组或自行选择任意组别。python方向仅设置大学组。各组的题目只有部分相同,各组分开比赛、分开评奖。
比赛时长:4小时。
比赛地点:线下比赛。省赛在全国各地都有赛点,决赛统一在北京举行。
比赛题型:5道填空题+5道编程题,填空题一般也是需要编程来求解的,满分150分。
比赛赛制:OI赛制,即每道题提交之后都没有任何反馈,填空题不是满分就是0分,编程题最后根据每道题通过的测试点的数量获得相应的分数。每道题不限制提交次数,仅以最后一次提交为准。
往年真题:http://dasai.lanqiao.cn/pages/dasai/news_detail_w.html?id=644
官方练习系统:http://lx.lanqiao.cn/
获奖:比赛分为省赛和决赛,所有获奖选手均可获得由工业和信息化部人才交流中心及大赛组委会联合颁发的获奖证书。
省赛:省赛每个组别设置一、二、三等奖,比例分别为10%、20%、30%。省赛一等奖选手可获得进入在北京举行的全国总决赛的资格。
决赛:决赛一等奖5%,二等奖20%,三等奖25%,优秀奖不超过50%,零分卷不得奖。

PAT(计算机程序设计能力考试,官网:https://www.patest.cn/)
举办时间:每年举办3次,一般为每年3月(2020年因疫情推迟到6月21日)、9月和12月。
报名时间:一般上次考试刚刚结束,下次考试的报名就马上开始了。
报名费:256元。只需要刷十道牛客网的PAT真题,就可以领取50元的PAT报名优惠券。链接: https://www.nowcoder.com/pat
可选语言:c/c++,java,python等多种语言都可以。
参赛要求:无论是否是大学生,均可参加。
分组说明:PAT分为顶级(英文,3题)、甲级(英文,4题)、乙级(中文,5题)。满分都是100分。
比赛时长:各组都是3小时。
比赛题型:各语言考的题目相同,而顶级、甲级、乙级各组考的题目都不同,只考编程题。
比赛地点:线下考试,PAT目前有考点 70多处, 分布在 26 省/直辖市的 51 座城市中, 涉及合作院校 67 所。
比赛赛制:IOI赛制,即每道题提交之后都有反馈,可以看到“通过”、“运行错误”、“答案错误”等等,甚至可以实时看到自己每道题得了多少分,根据每道题通过的测试点的数量可获得相应的分数。每道题不限制提交次数,仅以最后一次提交为准。
往年真题(收费):https://pintia.cn/market
练习系统(免费):https://pintia.cn/problem-sets
获奖:考试后会发成绩单。浙江大学计算机学院与软件学院还以PAT(甲级、顶级)一年内的成绩作为硕士研究生招生上机复试成绩。另外很多企业对于PAT成绩优异者可以免机试、优先录取等,详情见https://www.patest.cn/company

CCF CSP(CCF计算机软件能力认证,官网:http://www.cspro.org/)
举办时间:从2014年开始每年举办3次,一般为每年3月(2020年因疫情推迟)、9月和12月。
报名时间:一般在每次考试前一两个月开始报名。
报名费:非会员400元,会员200元。会员只需花50元就可以开通一年,一年内三次认证都可以享受会员价(最近办会员好像暂停了)。
可选语言:C/C++、Java和Python。
参赛要求:无论是否是大学生,均可参加。
分组说明:无分组,每届所有考生都考同一套题。
考试时长:4小时。
考试题型:5道题,都是编程题。一般难度按照题号递增。
考试地点:线下考试,在全国有80多个认证点。
考试赛制:OI赛制,即每道题提交之后都没有任何反馈,最后根据每道题通过的测试点的数量获得相应的分数。每道题不限制提交次数,仅以最后一次提交为准。
往年真题:http://www.cspro.org/ ,注册并登陆后,“报名考试”-“模拟考试”
获奖:认证结束3个工作日后可登陆官网查看成绩,可下载打印带红色公章电子版成绩单。有的学校将CSP成绩作为毕业要求、保研要求或考研免机试条件等,有的公司对CSP成绩优异者优先录取。另外如果成绩达到一定标准(各地区分数要求不同),可报名参加CCF CCSP分赛区竞赛、CCF CCSP竞赛,报名需要另外收费。

GPLT 团体程序设计天梯赛(官网:https://gplt.patest.cn/regulation)
举办时间:比赛时间一般安排在每年 3~5 月择日举行,2020年是第5届。
报名时间:一般在举办时间十天前截止。
报名费:竞赛注册费为500元/队,会务费为150元/人。
可选语言:C、C++ 和 Java。
参赛要求:需要由每个学校的老师注册并申请队伍后,学生才能报名,由老师带队参赛。每名参赛队员必须是参赛队所属高等学校的在册本科生或专科生,每支参赛队由最多 10 名队员组成,每位参赛队员使用一台计算机独立比赛。
分组说明:竞赛分为 3 个组别:珠峰争鼎(本科组)、华山论剑(本科组)、沧海竞舟(专科组),本科生限参加 “华山论剑”组或“珠峰争鼎”组;专科生可参加任一组。竞赛中 3 个不同组别使用同一套题目,在同一时间,按照统一评分规则进行比赛。
比赛时长:3个小时。
比赛题型:都是编程题。竞赛题目分 3 个梯级:基础级设 8 道题,其中 5 分、10 分、15 分、20 分的题各 2 道,满分为 100 分;进阶级设 4 道题,每道题 25 分,满分为 100 分;登顶级设 3 道题,每道题 30 分,满分为 90 分。
比赛地点:线下比赛,在全国有三四十个赛点。
比赛赛制:IOI赛制,即每道题提交之后都有反馈,可以看到“通过”、“运行错误”、“答案错误”等等,甚至可以实时看到自己每道题得了多少分,根据每道题通过的测试点的数量获得相应的分数。每道题不限制提交次数,仅以最后一次提交为准。
练习系统:拼题A网站 https://pintia.cn(即比赛使用的在线自动判题系统)提供包括往届真题在内的练习题目。是的,就是跟PAT同一个练习系统。
获奖:竞赛的 3 个组别分别设置全国高校奖、全国团队奖、个人特等奖、个人优胜奖、特别奖、成功参赛奖;同时各省设置省内高校奖和团队奖。详见 https://gplt.patest.cn/regulation

传智杯(官网:http://dasai.ityxb.com/)
举办时间:每年举办1次,一般为每年3月或4月。2020年是第二届。
报名时间:一般在前一年11月开始报名。
报名费:免费。
可选语言:C/C++、Java和Python。
参赛要求:大赛面向中国高校所有专业的在校生(含高职、大专、本科及研究生),已毕业的学生不具备参赛报名资格。
分组说明:无分组,每届所有考生都考同一套题。
比赛时长:3小时。
比赛题型:每场⽐赛共计 4 题,每道题根据难易度有不同的得分,答对题⽬数越多即得分越⾼,在得分相同的情况下,答题⽤时最短则排名越⾼。
比赛地点:院校选拔赛为线上比赛,决赛为北京线下比赛(2020年因疫情均改为线上比赛)。
比赛赛制:ACM-ICPC 赛制,即每道题提交之后都有反馈,可以看到“通过”、“运行错误”、“答案错误”等等,每道题必须通过所有的测试点才算通过,通过题数/分数相同的情况下按照答题时间排名。每道题不限制提交次数,但没通过的话会有罚时,仅以最后一次提交为准。

练习系统:没有公布往年真题,没有自己的练习系统,官方建议练习系统为计蒜客https://nanti.jisuanke.com/acm
获奖:正式赛分为院校选拔赛和决赛。
院校选拔赛:根据排名,分设一等奖(5%)、二等奖(10%)、三等奖(20%)和优秀奖(15%)各若干项,都有荣誉证书。初赛 一、二等奖获奖选手将有资格进入决赛。
决赛:设一等奖(2%)、二等奖(3%)、三等奖(5%)各若干项,总获奖人数不超过总报名数的10%,都有荣誉证书。

全国高校计算机能力挑战赛(官网:http://www.ncccu.org.cn/index.html#jg)
报名时间:2019年是9月-11月报名。
报名费:60元。
可选语言:C/C++、Java和Python。
参赛要求:大赛的参赛对象是高校所有专业的在校生(含高职、大专、本科及研究生)。
分组说明:各语言科目分开比赛,题目根据所选语言系统自动生成。
比赛时长:区域赛为90分钟,决赛为120分钟。
命题范围:命题范围参考:基本语言知识、数据结构基础(线性结构、树形结构、图结构)、算法基础(如排序查找等算法,以及算法综合应用)等知识。
比赛题型: 题型为选择题、程序阅读(阅读程序写结果)、程序设计(每题设置若干得分点,按通过的得分点计分)。
比赛地点:线上比赛。
比赛赛制:OI赛制,即每道题提交之后都没有任何反馈,最后根据每道题通过的测试点的数量获得相应的分数。每道题不限制提交次数,仅以最后一次提交为准。
练习系统:没有公布往年真题,没有自己的练习系统。
获奖:程序设计赛分为区域赛和决赛。
区域赛:根据各参赛科目排名,分设一等奖(5%)、二等奖(10%)、三等奖(20%)和优秀奖(15%),都有荣誉证书。区域赛一、二等奖获奖选手将有资格进入决赛。
决赛:设一等奖(2%)、二等奖(3%)、三等奖(5%)各若干项,总获奖人数不超过总报名数的10%,都有荣誉证书。

软考(计算机技术与软件专业技术资格(水平)考试,官网:http://www.ruankao.org.cn/)
举办时间:每年5月份、11月份举办两次。
报名时间:各省报名时间不同。一般上半年为2、3月份报名,下半年为8、9月份报名。
历史:计算机软件资格考试在全国范围内已经实施了二十多年,近十年来,考试规模持续增长,截止目前,累计报考人数约有五百万人。
报名费:各省不同,几十元到一百多元。
参赛要求:无要求。无论是否是大学生,都可以报名。报考任何级别不需要学历、资历条件。
分组说明:计算机软件资格考试设置了27个专业资格,涵盖5个专业领域, 3个级别层次(初级、中级、高级)。计算机专业的大学生一般报考“程序员”(初级)或“软件设计师”(中级),可直接报考中级。
时长:考试包括上午场、下午场,都是2.5小时。
可选语言:下午场中有一道C语言的题目必考,另外选做一道C++或Java的题目。都是代码填空题。
命题范围:软件工程(占比最大)、基本语言知识、数据结构、算法、操作系统、计算机组成原理、数据库、计算机网络、编译原理、设计模式等计算机专业知识,几乎覆盖了计算机专业本科的所有专业课。虽然命题范围广,但是题目都不难。
考试题型: 不考编程题。上午场都是选择题,满分75分。下午场都是填空题,满分75分。
考试地点:线下考试,全国各地都有考点。
往年真题:官方将往年真题整理出书,可自行购买。详见 http://www.ruankao.org.cn/book
获奖:需要上午场和下午场都达到45分以上,才能通过考试。通过之后可以领取到合格证书,详见http://www.ruankao.org.cn/index/hg

学习资源推荐

https://oi-wiki.org
在这里插入图片描述

贴一下我用过的资料:

oj(Online Judge):

洛谷:https://www.luogu.com.cn/
(有完善的的题单,基本涵盖所有热门知识点)
功能:
云剪切板
题库
题单

acwing:https://www.acwing.com/about/
(主打付费课程,质量很高,有条件的话可以报一下)

牛客竞赛:https://ac.nowcoder.com/acm/home/542457559
(会时不时的举办比赛)

CF:https://codeforces.com/
(俄罗斯网站,题目质量最高,举办比赛很频繁,有积分系统,可以检验自己的水平)

c语言网:https://www.dotcpp.com/oj/problemset.php?mark=6
(有蓝桥杯的历年题目)

蓝桥杯官方练习系统:https://lx.lanqiao.cn/
(感觉有点拉跨,数据不公开)

纸质资料:

算法竞赛入门经典(紫书):难度较高,不太推荐

算法竞赛进阶指南(蓝书):难度较高,但是讲解很详细,冲国赛用

啊哈算法:适合新手入门,有很多小人插图,很有趣

深入浅出程序设计竞赛:强推,配合洛谷题单使用

比赛链接

【蓝桥杯】萌新首秀全国高校新生编程排位赛!点击链接https://www.lanqiao.cn/oj-contest/ 选择对应场次准时参赛

在这里插入图片描述
在这里插入图片描述
之前发的蓝桥杯的新生赛名额我已经申请下来了,登录网站注册认证后,进入比赛界面自己报名适合自己时间的就行

猜你喜欢

转载自blog.csdn.net/shaozheng0503/article/details/132881042