程序员面试常见问题


程序员面试常见问题


离职选择

一般选择离职的时机,可以分为两个方面:公司、个人。

公司方面:

  1. 发现公司前景不好
  2. 公司氛围差

个人方面:

  1. 压制个人成长
  2. 耽误个人成长
  3. 影响个人能力发挥

试用期选择离职选择

试用期进入公司工作一段时间后,发现公司有问题,不符合自己的预期的时候,果断选择离职,否则只会后悔留下来。

必须考虑:

  1. 迟到罚款,说明企业对员工严苛,不理解员工,以至于会强制员工做其他非职责内的事。
  2. 与老员工交流,如果公司在大多数员工口中口碑不好,说明公司对待员工存在问题,以后你也一样。
  3. 公司存在多年,但员工却没有老员工,说明企业留不住人,肯定对员工有见不得人的勾当。

可供参考:

  1. 墙面标语提倡为公司无偿奉献,如“公司是我家”、“更高、更快、更强”等
  2. 大小会议接连不断,日报、周报、月报、年度总结,一次不落, 说明公司形式主义泛滥,精力不在发展上,只在想着怎么对内管理员工。公司的发展肯定缓慢,没有前途可言。

博文一

一、公司开会太拖沓、形式主义众多

方明进入一家公司,在试用期就经历了大大小小的会议,经常一开会就是一下午。

转正后,方明还要面临写不完的日报、周报、月报,汇报总结的时间几乎跟工作时间持平。

学会在试用期间观察公司的会议情况。如果大会小会不断,那么公司必定形式主义,容易有面子工程,耽误员工的时间和精力。

越是把会议捧上高处的公司,越容易形成权威崇拜、空喊口号的企业文化。毕竟,高效的工作氛围从来不把精力浪费在琐碎事务上。

二、墙上标语露真相,警惕职场剥削

小陈进入公司后,在公司文化墙上看到“公司是我家,团结努力靠大家”之类的话语后,她观察一段时间后果断辞职。

辞职时,该公司还大打感情牌来挽留小陈。辞职后,小陈在业内听说了该公司加班内卷、习惯情感PUA的企业文化。

文化墙是最能反映企业文化的标志物。多留心标语,留心照片墙内容,你会发现很多企业文化的细节。

当看到文化墙出现提倡员工奉献类的口号时,就得留意了,公司是否经常大打感情牌来弥补待遇上的不足!

当看到“更高、更快、更强”之类的奋斗口号时,要留心公司是否推崇无偿加班模式。 在文化墙照片里经常看到团建活动时,不妨可以多嘴问一句,公司是否会把团建时间安排在休息日?

如果这个公司都没有出现文化墙,要么是推崇简洁高效的工作环境, 要么是处于初创阶段还未形成稳定的企业文化。这时是否该留下,就仁者见仁智者见智了。

三、老员工是风向标,衡量发展空间

李玲进入一家公司后,和老员工无意聊天后才发现,公司承诺的福利待遇,很多老员工都没有享受到。

李玲一想到自己的未来发展很有可能是这样,不禁不寒而栗。在经过多方观察后,李玲意识到,当初面试官只是给自己“画大饼”,于是果断离职了。

想要衡量一个公司是否有足够的发展空间,你只需要多观察该公司如何对待老员工就知道了。

公司能够给予老员工的最佳待遇,其实就是新员工的未来上限。

如果说少数老员工的待遇差,我们可以归结为个人能力的问题。但是当大多数老员工都无法得到好待遇时,就应该仔细思考了,是否公司的员工待遇有问题。

如果一个公司都没有多少老员工,更能反映出该公司管理混乱、没有发展前景、留不住人才的情况。碰到这种情况,职场人更应该“三十六计走为上计”。

四、奖惩制度有猫腻,及时止损最明智

小赵在试用期间,听到领导做出了开会迟到人员罚款或者乐捐的决定后,他判断该公司内部管理制度严苛,后来种种迹象果然验证了他的判断。

严格来说,对迟到员工罚款或者乐捐属于是看似合理实则违法的职场行为。而能够履行这个规定的公司,细究其他地方,也必定钻了法律的漏洞。

奖惩制度相当于是公司内部的“法律法规”,对员工管理过严或者过松,都会在奖惩制度上出现不合时宜的规矩。

所以查看员工手册、观察公司如何应对员工的考勤和绩效、多跟老员工交流摸清情况、考察该公司在业内人士的口碑等,是你在试用期间的重点关注项目。

一般过程

HR Phone Screen

Phone Screen,一般是提交简历后,程序员面试开始的第一步,由公司HR负责。目的是了解一下候选人的背景,为下一步tech interview做准备。

常见问题如下:

1- 你为什么对本公司的这个职位感兴趣?

这一问HR主要想考察你有没有做好“功课”,事先研究了解过公司。

2- 你有没有用过本公司常用的编程语言?

如果你对这种特定语言没有太多的经验,那就说实话,否则就算过了电话关,也过不了之后的coding关。 但你也要告诉HR你自己会的语言,并表示有能力学习新的语言。

3- 你有没有做过什么项目?

简明扼要。直接简洁地描述你参与过的项目和在里面扮演的角色。

4- 你有没有领导别人的经验?

这一题的答案不仅仅局限于技术上的领导力。如果你在学校里有过志愿者活动的领导经验,都可以说。 尽可能地将你以前的领导经验和你要申请的职位联系起来。

5- 为什么你要离开现在的公司?

你可以坦率地说出你的想法,但答案不要仅仅围绕在钱和利益上。你可以从职场文化、创意实践, 解决现实问题的满意度等几个角度来回答这题。

注意,跟HR的面试,并不需要你深入地探讨一些技术上的问题和经验。要做到简明扼要,不要让HR睡着了。

Onsite Interview

当你顺利通过了HR的phone screen、或是之后的technical phone interview后,你就会进入程序员面试的下一个环节。 也就是一个4-5轮的Onsite Interview。面试官会从项目经历、行为、文化、技术、coding等几个角度来全方位了解你是不是他们想要的程序员。

Experiential Questions

6- 在给其他团队成员reveiw code时,你觉得最重要的是什么?

这题的答案没有明确的对与错,目的是为了检验你的知识,以及你在面试中是否可以表达好code review的过程。回答角度可以围绕:

  • Functionality
  • Readability
  • Maintainability
  • Security
  • Simplicity
  • Regulatory requirements
  • Resource optimization

7- 描述一下你写代码的全部过程。

面试官想知道你在写代码时,是否有一个清晰的流程,并确保你的工作方式是有组织的,而不是杂乱无章的。

8- 你做complex algorithms的首选语言是什么?

你可以说实话,但至少要给出两个答案,以表明自己“多才多艺”和“不钻牛角尖”。你可以说 “XYZ是我的第一选择, 但ABC也是一个很好的选择。” 然后告诉他们为什么。

9- 如何设计一个可以扩大规模的APP?

这个面试题测试的是你的知识和思维过程。

10- 你做过的最满意、最值得骄傲的项目是什么?

这是你表现自己的时刻,告诉面试官你的coding实力,并描述一个你最引以为傲的项目。一定说出理由, 为什么你觉得这个项目让你骄傲 (比如它满足了某种需求等等)。

11- 描述一个你做过的失败的项目。

你可以清楚地说明为什么这个项目最终失败了。你还可以说你之后花时间剖析了这个项目,并且总结了问题, 从失败中学到了经验。并在下一次项目中,没有再犯。

Cultural / Behavioral Questions

12- 你目前所在的公司,有什么吸引你的地方吗?

在面试的时候,千万不要说,“没有,我讨厌现在的公司”。可以选择说一说目前公司和所申请公司共同的优点。 如果这是你的第一份工作,你可以谈谈在学习或实习期间喜欢什么。

13- 描述你理想的公司文化。

在进行onsite面试之前,做好你的research,提前了解这个公司。确保说出来的理想文化,和这个公司的程序员文化相似。

14- 你的同事是怎么描述你的?

你可以通过这个问题来向面试官展示你的社交意识,你可以说通过与同事的交流协作,你了解到了自己在别人眼中的样子。 同时,你可以用这个问题来表明你是有自我认知的。你知道自己的长处和短处,以及你能给团队带来什么。诚实回答,不要过度自嘲。

Technical Questions

这可以说是程序员面试中最重要的一个环节之一。这一类的面试题,会根据不同候选人的不同知识背景进行考核。

常见问题举例:

15- mutex 和 semaphore 有什么区别?

16- 什么是多线程编程?

17- Local Variable和Global Variable有什么区别?

18- 哈希表如何工作?

19- 给出一个真实生活中哈希表的例子,并描述一个哈希表为何是一个糟糕的数据结构选择。

20- 假设你有一个单线程的C标准应用程序,它不断崩溃,但从来不在同一个地方崩溃。你觉得可能导致它崩溃的原因是什么?

21- queue和stack之间有什么区别?

22- 什么是regression test?

Coding Questions

这个阶段是所有程序员面试中最难的一关。你不仅需要在高压的环节中展示你的知识成果, 而且你还要在不熟悉的环境(白板上的手写代码)和时间限制下工作。每个候选人会遇到的具体问题有所不同, 但以下是一些常考的经典题目:

23- Linked lists(删除重复,反转链表,确定它是否有环)

24- 时间和空间复杂度分析

25- Tree:基本构造,遍历和操作算法。知道如何实现平衡二叉树。

26- Stack(用两个栈实现一个队列)

27- 数组和字符串(反转字符串,permutations)

面试时,确保你不断向面试官解释你的思考过程(即使你被困住了)。 尝试着与面试官协作,并可以在遇到困难时勇敢地提问。 因为这可以表明你愿意在团队中寻求帮助、以便把工作良好地进行下去。

如何面试程序员

参见 http://www.ruanyifeng.com/blog/2010/12/how_to_interview_a_programmer.html

一、提问之前的准备

首先,最重要的是,你自己一开始就应该想清楚:

  1. 需要新员工完成什么样的任务?
  2. 怎样的人能完成这样的任务?
  3. 哪些途径和方法可以发现这样的人?

只有明确这些根本性的问题,才能正确高效地完成面试。

二、提问的原则

假定你对上一节的三个问题,已经有了清晰的想法,那么接下来就可以设计如何提问了。

有一些提问的原则,是你应该遵循的:

  • 每一个面试问题都有明确的目的。你不仅自己了解,还能向其他面试官解释清楚。
  • 多提一些开放性(Open-ended)的问题,而不是那种用Yes/No就可以回答的问题。这样做使你有机会与面试者展开讨论,并且提出后续的问题,尽可能多地了解对方。
  • 不要问宗教、家庭、健康、个人隐私等方面的问题。
  • 不要问太复杂的问题。因为面试者没有太多思考时间,所以无法周全地回答,你也就无从判断他的能力了。

三、考察专业能力

为了确认面试者是胜任的,你可以问一些与职位相关的专业方面的问题。(不过通常来说,一次面试不足以看出一个人的专业能力。)

比如,你的招聘职位是系统管理员,你可以问”如何快速地在50台机器上部署Linux?”(提示:正确答案不是刻录50张安装光盘。)

另外,你还应该向面试者了解他的过去,因为过去是未来的最好预测依据。不过,提问的重点不要仅仅是他过去的成果,更要关注在当时的环境中,他是如何决策和实施的。

四、考察综合素质

因为人是会发展的,所以某种程度上,面试者的综合素质要比他的专业能力更重要。

所以,具体的技术问题(如何调用API、什么是设计模式、编程语言的语法等等)可以少问一些,更应该关注面试者的事业心、对工作的热情、进取心、自律能力、毅力等方面。

下面是一些典型问题:

Why did you get into development?
你为什么开发软件?

How many technical books did you read in the past year?
去年你读了几本技术书籍?

What was your favorite technical book in the past year? What did you learn from it?
去年你最喜欢的技术书籍是哪本?你从中学到了什么?

What websites do you read regularly, related to development?
平时你经常访问哪些编程类网站?

Do you maintain any open-source projects?
你有自己的开源项目吗?

Do you code in your spare-time?
业余时间你编程吗?

Do you love programming, or do you do it for the money?
对于你来说,编程是一种爱好,还是一种谋生手段?

Have you accomplished anything important in your career yet? Do you want to?
你的职业生涯之中有什么重要的成就?它是你主导的吗?

What would make you feel that you have done something important?
什么事情会让你很有成就感?

五、考察理性思维

某些情况下,你可能需要了解面试者的分析判断能力,看他能否全面地思考问题、客观地评价自己。

那么,你可以依次提出这样三个问题:

What's your favorite programming language? Why?
你最喜欢的编程语言是哪种?为什么?

If you could add one feature to your favorite language, what would it be? Why?
如果允许你为这种语言加一种功能,你会加什么功能?为什么?

If you could remove one feature from it, what would it be? Why?
如果允许你取消一种功能,会是什么功能?为什么?

这里的重点是,让面试者从正反两方面评价一件自己熟悉的东西,看看他的思维是否片面。 答案无所谓对错,只要面试者有一个明确的立场,能够从正反两方面说出令人信服的理由,就可以了。 比如,某个软件的口碑不好,但是面试者说他很喜欢,而且说得出一大堆理由,清楚地解释了这种软件的优点和缺点在哪里,这样就很好。

你还可以把这些问题,套用在其他东西上面,比如操作系统、文字编辑器等等。

如何向公司提问

参见 http://www.ruanyifeng.com/blog/2012/08/questions_you_need_to_ask_in_an_interview.html

有一些注意点,你需要知道:

  1. 面试之前,一定要做准备,多了解公司的情况。
  2. 你提出的问题,应该围绕”这份工作是否合适我”这个中心点,其他与应聘关系不大的问题,不宜多问。
  3. 提问的时候,要自然放松,不要害羞,就把它当作普通的聊天。你要表现出对公司的真诚兴趣。
  4. 提问要直接了当,不要绕圈子。提出问题之后,你要保持安静,让面试官多说话。
  5. 面试官回答的时候,你可以做笔记,或者事先询问能不能做。笔记必须简短,你的大部分时间,要用来全神贯注倾听面试官的回答,并与其有眼神的交流。
  6. 面试结束后一周内,最好打一个电话或发一封邮件,了解公司对你的反馈意见。即使面试失败,你不妨也问一下原因,这会有助于你以后的面试。

下面是一些你可以问的典型问题。

问题一:你们为什么要招聘这个职位?

Q1: Why are you currently recruiting for this position?

这个问题会使得面试官开始谈论当前的项目,或者谈论前一位离职人员。无论哪种情况,都会让你了解,一些与你最密切相关的公司情况。

问题二:你们的新员工多吗?

Q2: Do you have many new staffs?

这个问题起一个过渡作用,使得谈话导向公司内部的情况。但是,它本身也能说明一些问题。如果公司成立已经超过四年,又没有新项目,但是新员工却很多,这往往说明公司文化不是很健康。

问题三:你们公司(团队)目前面临的最大挑战是什么?

Q3: What are the biggest challenges your team are facing right now?

如果面试官开始谈论一些具体的技术问题,这很好;如果他的回答是项目时间紧迫,或者需要更多的资金,那你就要小心一点了,公司管理上面可能有问题。

问题四:什么新技术(编程语言)是你们未来希望采用的?

Q4: What technologies/languages would you like to see your team adapt to that aren’t currently being utilised?

如果你申请的是技术职位,面试官恰巧又是技术负责人,那么这个问题将会非常合适。你会对公司的技术路线有所了解和准备,一旦入职,就能更好地适应公司的需要。

问题五:在业务方面,有没有什么地方是你们不满意的,未来想要改进的?

Q5: Few companies, if any, are 100% satisfied with the way their business is operating. If you could simply flick a switch to fix it, what one thing would you change?

很少有公司,会百分之百满意自身的现状,即使那些状况很良好的公司也是如此。这个问题可以让你对公司管理层的关注重点和担忧之处,有所了解。

问题六:我申请的这个职位,对公司的业务有何影响?

Q6: If you struggle to fill the position I have applied for, what impact would that have on the business?

这个问题会让你了解自己在公司的角色,以及你的岗位对公司是否重要。

选择公司参考

1、公司开出的工资比市场价低很多,这通常意味着公司现在发展的并不好, 另一方面也说明公司不愿意为未来的发展做更多的投入,这意味着公司未来的发展也可能不一定好。 你的加入并不稳定,面临着要随时被迫离开的可能。

擅长什么

询问你擅长什么,该怎么回答?

  1. 突出自己在某个或某些编程语言方面的专业技能,如:我擅长于PHP后端开发、MySQL数据结构设计
  2. 强调自己在某个技术领域有深入的了解和经验,如:对MySQL查询优化有深入研究
  3. 强调自己的团队合作能力和解决问题的能力,如:碰到随机不重复字符串时用雪花算法解决(兑换码),文件服务项目的建设
  4. 突出自己的学习能力和对新技术的热爱,如:为框架瘦身,避免非必要部分的加载;看到专业队列工具RabbitMQ、Kafka后,开始在项目中研究使用
  5. 强调自己的代码编写能力和对代码质量的追求,如:在项目中使用PSR标准进行编码,提高项目的可维护性

英语能力怎么样

当问到你英语读写能力如何时,你可以回答: 读写没有问题,口语会说一些。

你想找一家怎样的公司

尽量往这家公司的企业文化和核心价值观上靠近。

服务器规格

开发中,服务器规格是多少?

有:8核32G、16核64G、24核96G,等

软件工程的核心概念

软件工程的核心概念包括抽象、建模、分解和复用。这些概念的具体内容如下:

  • 抽象:将复杂的事物简化为简单的事物,以便于处理。在软件工程中,抽象是一种重要的工具,用于对软件系统进行高层次描述, 以便在保持关键信息的条件下,忽略不必要的细节。
  • 建模:对现实世界进行某种形式的描述或模拟,以解决特定问题。在软件工程中,建模是对软件系统的行为和结构进行描述的方式, 常用的建模技术包括面向对象建模、数据流图和事件驱动系统设计等。
  • 分解:将复杂问题分解为更小、更易于管理的部分,以便分别理解和解决。在软件工程中, 分解用于将大型软件系统划分为更小、更易于开发的模块,这种技术有助于降低问题的复杂性,提高开发效率。
  • 复用:使用已有的软件组件或模块来开发新的软件系统,而不是重新编写代码。复用可以显著提高开发效率,减少错误, 并改善软件的可维护性。在软件工程中,复用通常涉及设计模式、组件化和面向对象编程等概念。

具体分析

如何从一个需求落实到一个系统设计,如何衡量两个不同设计的好坏, 如何在各种限制下(人员、时间、资源等)选择其中更合适的设计,以及提升该设计的可拓展性?

从需求落实到系统设计,需要经过以下步骤:

  1. 明确需求:理解并清晰表述需求,包括功能需求、非功能需求和约束。
  2. 需求分析:对需求进行深入理解,分析其可行性和合理性,评估其优先级和重要性。
  3. 系统设计:根据需求分析的结果,进行系统设计,包括总体架构、模块划分、接口设计、数据结构等。
  4. 设计评审:邀请同行或专家对设计进行评审,提出改进建议,进一步完善设计。
  5. 原型开发:根据设计,开发一个原型系统,用于验证设计的可行性和合理性。
  6. 原型测试:对原型系统进行全面测试,包括功能测试、性能测试、安全测试等,确保系统满足需求。

衡量两个不同设计的好坏,可以从以下几个方面考虑:

  1. 功能性:评估两个设计的功能是否满足需求,以及功能的完善程度和实用性。
  2. 效率:比较两个设计的运行速度、处理能力等效率指标,评估其性能表现。
  3. 可靠性:评估两个设计的容错处理、故障恢复等能力,以及系统的稳定性和可用性。
  4. 可维护性:评估两个设计的可修改性、可扩展性等,以便在未来进行维护和升级。
  5. 成本:比较两个设计的开发成本、运行成本等,以确定哪个设计更具经济性。

在各种限制下(人员、时间、资源等)选择更合适的设计,需要综合考虑以下因素:

  1. 人员技能和经验:考虑团队成员的技能和经验,选择他们能够胜任的设计。
  2. 时间限制:考虑项目的开发周期和时间安排,选择能够在规定时间内完成的设计。
  3. 资源限制:考虑项目的预算和硬件资源,选择符合预算和资源限制的设计。
  4. 技术选型:考虑所使用的技术是否符合项目需求,以及技术的成熟度和可维护性。
  5. 风险评估:考虑可能出现的风险和问题,选择风险较低的设计。

提升设计可拓展性的方法包括:

  1. 设计开放式结构:使系统能够方便地添加或删除功能,以满足未来的需求变化。
  2. 使用面向对象编程:通过使用类、对象和继承等概念,使代码更加模块化和可重用。
  3. 设计松散耦合:使系统的各个部分之间尽可能减少依赖关系,以便于单独进行维护和升级。
  4. 使用抽象和接口:通过抽象和接口的定义,使系统的各个部分之间能够相互独立地进行开发和扩展。
  5. 数据模型设计:合理设计数据库结构,保证数据的完整性和一致性,以支持未来的数据分析和决策。

除了上述提到的点外,还需要注意以下几点:

  1. 可移植性:确保设计能够在不同的平台或环境中运行,以便在未来进行迁移或升级。
  2. 安全性:考虑系统的安全性需求,包括数据加密、访问控制、漏洞修复等,以确保系统的安全性和稳定性。
  3. 可维护性:确保设计易于维护和修改,包括代码的可读性、模块化、注释等,以便在未来进行维护和升级。
  4. 测试和验证:进行充分的测试和验证,确保设计的正确性和可靠性,包括单元测试、集成测试、系统测试等。
  5. 技术趋势和未来发展:考虑未来技术和趋势的发展,选择具有较好前瞻性和可扩展性的设计。
  6. 用户反馈和需求:持续收集和分析用户反馈和需求,以便在未来进行改进和升级。

总之,从需求到设计是一个复杂的过程,需要考虑多方面的因素,包括功能性、效率、可靠性、可维护性、成本等, 以及各种限制条件和未来的发展趋势。通过不断的迭代和改进,可以不断提升系统的质量和性能。

软件工程中还有其他一些核心概念,包括:

  1. 敏捷开发:一种以人为核心、迭代、循序渐进的软件开发方法。它强调快速响应变化和持续交付价值, 而不是遵循详细的计划和预测。敏捷开发的核心原则包括适应变化、频繁交付、明确目标、团队协作和自我组织等。
  2. 瀑布模型:一种传统的软件开发模型,按照顺序依次经历需求分析、设计、编码、测试和维护阶段。 每个阶段都有明确的任务和输出,前一阶段的输出是后一阶段的输入。瀑布模型要求在每个阶段都进行严格的评审和审查, 以确保软件的质量和稳定性。
  3. 迭代开发:一种软件开发方法,将整个项目划分为一系列的迭代,每个迭代都包含需求分析、设计、编码、测试等阶段。 通过迭代开发,可以将大型项目分解为更小、更易于管理的部分,并且可以尽早发现和解决问题,提高项目的成功率。
  4. 螺旋模型:一种风险驱动的软件开发模型,它将瀑布模型和迭代开发结合在一起。它从初始阶段开始, 然后逐渐增加功能和复杂性,同时不断进行风险评估和管理。螺旋模型适用于高风险或需求不确定的项目, 因为它可以逐步增加功能和降低风险。
  5. 测试驱动开发(TDD):一种软件开发方法,强调通过自动化测试来驱动代码的设计和实现。 它要求在编写测试代码之前先编写实现代码,并且测试代码的数量和质量要与实现代码的数量和质量相当。 TDD可以提高代码的质量和可维护性,同时降低错误和缺陷的数量。

这些核心概念都是软件工程中重要的组成部分,每种方法都有其适用范围和局限性。在实际应用中, 需要根据项目的具体需求和情况选择合适的方法和技术,以达到最佳的软件开发效果。

除了上述提到的核心概念外,还有一些需要补充的点,包括:

  1. 持续集成和持续交付(CI/CD):是一种软件工程实践,旨在提高软件开发的效率和质量。 CI/CD强调频繁地集成、构建、测试和交付软件,同时自动化这些过程。通过CI/CD,可以快速发现和修复问题,提高软件的可维护性和可靠性。
  2. 代码复审:是一种软件开发过程中的质量控制方法,通过同行或专业人士对代码进行审查, 发现并纠正错误、优化代码结构和提高质量。代码复审可以提高代码的可读性、可维护性和可扩展性。
  3. 版本控制:是一种软件工程工具,用于管理和跟踪代码的变更和版本。 版本控制可以帮助团队成员协同工作、追踪变更历史、恢复旧版本和比较不同版本之间的差异。
  4. 设计模式:是一种解决常见设计问题的经验总结,包括对象类设计模式、设计模式原则等。 设计模式可以提高代码的可重用性、可维护性和可扩展性。
  5. 软件开发过程模型:是一种软件开发的框架和方法,提供了一种标准的流程和步骤,用于指导项目管理和软件开发。 常见的软件开发过程模型包括CMMI、敏捷开发过程框架等。

这些点都是软件工程中的重要组成部分,可以进一步提高软件开发的效率和质量。在实际应用中, 需要根据项目的具体需求和情况选择合适的方法和技术,以达到最佳的软件开发效果。

总结:软件工程的核心概念包括抽象、建模、分解和复用,这些概念有助于我们更好地理解和解决软件开发中的问题。 此外,敏捷开发、瀑布模型、迭代开发、螺旋模型、测试驱动开发、持续集成和持续交付、代码复审、 版本控制以及设计模式等也是软件工程中的重要概念。 这些概念和方法可以帮助我们提高软件开发的效率和质量,降低风险和成本。在选择具体的方法和技术时, 需要根据项目的具体需求和情况进行评估和选择。同时,持续学习和实践也是软件工程发展的重要保证, 只有不断地学习和实践,才能不断提高软件开发的水平和能力。

上面提到的设计模式是一种解决常见设计问题的经验总结,通常包括问题描述、解决方案和效果。 设计模式可以帮助软件开发人员设计更好的软件,提高代码的可重用性、可维护性和可扩展性。

设计模式通常分为三种类型:创建型模式、结构型模式和行为型模式。 创建型模式主要关注对象的创建和初始化,包括单例模式、原型模式、建造者模式等。 结构型模式关注对象之间的结构关系,包括代理模式、装饰器模式、桥接模式等。 行为型模式关注对象之间的行为和交互,包括策略模式、观察者模式、模板方法模式等。

每种设计模式都有其特定的应用场景和效果,选择适当的设计模式可以提高代码的质量和可维护性。 在实际应用中,需要根据项目的具体需求和情况选择合适的设计模式,以达到最佳的软件开发效果。 同时,也需要根据实际需求进行适当的调整和优化,以满足项目的具体需求。

创建型模式:关注对象的创建和初始化。

  • 单例模式:确保一个类只有一个实例,并提供一个全局访问点。
  • 原型模式:通过复制或克隆现有对象来创建新对象。
  • 建造者模式:将一个复杂对象的创建与其表示分离,使得同样的构建过程可以创建不同的表示。

结构型模式:关注对象之间的结构关系。

  • 代理模式:提供一个代理对象来控制对一个对象的访问。
  • 装饰器模式:通过添加新功能来动态地装饰一个对象。
  • 桥接模式:将抽象部分与实现部分分离,使它们可以独立变化。

行为型模式:关注对象之间的行为和交互。

  • 策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换,从而使算法的选择可以独立于使用它的客户端。
  • 观察者模式:定义了对象之间的一对多依赖关系,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
  • 模板方法模式:定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现。

这些设计模式都有其特定的应用场景和效果。在实际应用中,需要根据项目的具体需求和情况选择合适的设计模式, 以达到最佳的软件开发效果。同时,也需要根据实际需求进行适当的调整和优化,以满足项目的具体需求。

白板

如何白板上练习算法题目,写出清晰、简洁、bug free的代码,,并衡量时间和空间复杂度以及可能存在的副作用?

在白板上练习算法题目,以下是一些建议:

  • 清晰地理解问题:在开始编写代码之前,确保你已经清晰地理解了问题。在白板上上写下问题的描述,并针对每个步骤分析其含义和目的。
  • 简化问题:在解决问题之前,尝试将问题简化为更小、更易于管理的部分。将大问题分解为小问题,然后分别解决每个小问题。
  • 伪代码:在白板上写出解决问题的伪代码。这将有助于你理清解决问题的思路,找出潜在的错误,并在编写代码之前理解问题的结构。
  • 编写代码:一旦你有了伪代码,就可以开始编写代码。确保你的代码清晰、简洁,并遵循编程的最佳实践,例如使用有意义的变量名、编写注释等。
  • 测试代码:编写完代码后,一定要进行测试。在白板上写下测试用例,并逐一运行代码来验证每个测试用例是否通过。 这将有助于你发现潜在的错误,并确保你的代码能够正确地解决问题。
  • 时间和空间复杂度:在解决问题时,一定要考虑时间和空间复杂度。在白板上分析你的算法的时间和空间复杂度,并尝试优化它们。
  • 副作用:在编写代码时,一定要考虑可能存在的副作用。例如,某些算法可能会改变输入数据或占用大量内存。 在白板上分析你的算法的潜在副作用,并尝试避免它们。

通过以上步骤,你可以在白板上练习算法题目,并编写清晰、简洁、bug free的代码,同时考虑时间和空间复杂度以及可能存在的副作用。

如何衡量时间和空间复杂度

时间和空间复杂度的衡量通常是通过分析算法中基本操作的数量和所需的存储空间来评估。

时间复杂度是衡量一个算法运行时间的度量,它描述了算法执行的基本操作数量随着输入规模的增长而增长的速率。通常用大O符号表示,描述了算法中基本操作的数量随着输入规模的增长而增长的速率。例如,一个排序算法的时间复杂度可能是O(n log n),表示算法的基本操作数量是输入规模n的log n级别。

空间复杂度是衡量一个算法所需存储空间的度量,它描述了算法所需的存储空间随着输入规模的增长而增长的速率。同样用大O符号表示,描述了算法所需的存储空间随着输入规模的增长而增长的速率。例如,一个排序算法的空间复杂度可能是O(n),表示算法所需的存储空间是输入规模n级别。

在白板上分析算法的时间和空间复杂度时,可以使用以下步骤:

  1. 分析算法的基本操作:分析算法中的基本操作,例如循环、条件判断、函数调用等。了解每个操作所需的时间和空间。
  2. 计算基本操作的数量:根据问题的规模,计算算法中基本操作的数量。例如,在排序算法中,可能需要比较、交换等操作,而这些操作的数量与输入规模有关。
  3. 确定时间复杂度:根据基本操作的数量和问题的规模,确定算法的时间复杂度。例如,如果算法中的基本操作数量是输入规模n的log n级别,那么算法的时间复杂度就是O(n log n)。
  4. 分析所需的存储空间:分析算法所需的存储空间,例如数组、栈、队列等。了解每个存储结构所需的内存空间。
  5. 确定空间复杂度:根据存储结构所需的内存空间和问题的规模,确定算法的空间复杂度。例如,如果算法中需要使用一个大小为n的数组来存储结果,那么算法的空间复杂度就是O(n)。

通过以上步骤,你可以在白板上分析算法的时间和空间复杂度,并对其进行评估和优化。

顺序查找

问题描述:给定一个整数数组,找出数组中最大的元素。

算法描述:

初始化一个变量max为数组的第一个元素。
遍历数组中的每个元素。
    如果当前元素比max大,则将max更新为当前元素。
返回max。

时间复杂度分析:

基本操作:遍历数组中的每个元素,共n次。
基本操作数量:n。
时间复杂度:O(n),因为算法中的基本操作数量与输入规模n成正比。

空间复杂度分析:

存储需求:max变量,存储最大值。
存储空间:常数级别,可以忽略不计。
空间复杂度:O(1),因为算法所需的存储空间是常数级别,不随输入规模n增长。

通过以上分析,我们可以得出该算法的时间复杂度为O(n),空间复杂度为O(1)。

插入排序

插入排序的时间复杂度为O(n^2),空间复杂度为O(1)。

在插入排序中,我们需要进行两层循环,外层循环对除了第一个元素之外的所有元素, 内层循环对当前元素前面已经排好序的元素进行查找和移动。因此,内层循环的平均时间复杂度为O(n), 外层循环的时间复杂度也为O(n),总的平均时间复杂度为O(n^2)。

空间复杂度为O(1),因为插入排序不需要额外的辅助数据结构,只需要一个额外的空间来存储索引。

冒泡排序

冒泡排序的时间复杂度为O(n^2),空间复杂度为O(1)。

在冒泡排序中,我们需要进行两层循环,外层循环对除了最后一个元素之外的所有元素, 内层循环对当前元素后面已经排好序的元素进行比较和移动。因此,内层循环的平均时间复杂度为O(n), 外层循环的时间复杂度也为O(n),总的平均时间复杂度为O(n^2)。

空间复杂度为O(1),因为冒泡排序只需要进行元素的交换,不需要额外的辅助数据结构。

快速排序

快速排序的时间复杂度为O(n log n),空间复杂度为O(log n)。

在快速排序中,我们使用分治策略,将数组分成两个子数组,对每个子数组进行递归排序。 这个过程需要使用一个额外的空间来存储分治后的子数组的指针。因此,空间复杂度为O(log n)。

快速排序的核心操作是分区操作,它将数组分成两个子数组,并使用递归对每个子数组进行排序。 分区操作的时间复杂度为O(n),因为我们需要遍历整个数组。在最好的情况下,快速排序的时间复杂度为O(n log n), 因为每次分区操作都可以将数组分成两个等长的子数组,这样递归的深度就为log n。在最坏的情况下, 时间复杂度为O(n^2),因为分区操作可能无法将数组等分。

总的来说,快速排序的平均时间复杂度为O(n log n),空间复杂度为O(log n)。

希尔排序

希尔排序(Shell Sort)是希尔(Donald Shell)于1959年提出的一种排序算法。它是一种插入排序的改进版本,也称为缩小增量排序。

希尔排序算法的基本思想是:先将整个待排序的记录序列分割成为若干子序列(由相隔某个“增量”的记录组成的), 分别进行直接插入排序,然后依次缩减增量再进行排序,直到增量为1时,进行最后一次直接插入排序。

希尔排序算法的实现过程如下:

  1. 初始化增量序列:选择一个合适的整数作为初始增量,例如gap=length/2=10/2=5, 将待排序文件分割成若干长度为gap的子序列,然后对每个子序列进行直接插入排序。
  2. 缩小增量:gap每次除以2,直到gap=1为止。

希尔排序算法的时间复杂度取决于增量序列的选择。最坏情况下,当输入数据已经部分排序时, 增量序列的选择可能会使时间复杂度达到O(n^2)。然而,在平均情况下,通过选择合适的增量序列, 希尔排序的时间复杂度可以达到O(n log n)。

希尔排序算法是一种高效的排序算法,尤其是对于大型数据集。在实际应用中,它比冒泡排序、选择排序等算法更快。

堆排序

堆排序的时间复杂度为O(n log n),空间复杂度为O(1)。

在堆排序中,我们首先将数组构建成一个最大堆或最小堆,然后交换最大元素(或最小元素)与最后一个元素的位置, 再将除最后一个元素之外的剩余元素重新调整为最大堆或最小堆。这个过程需要使用一个辅助的数组来构建和调整堆, 但不需要额外的递归调用。因此,空间复杂度为O(1)。

堆排序的核心操作是构建和调整堆,这个过程的时间复杂度为O(n log n),因为我们需要对每个元素进行比较和交换。 在最大堆或最小堆构建完成后,我们可以快速地找到最大元素(或最小元素),并且只需要对剩余的元素进行一次遍历, 因此整个排序过程的时间复杂度为O(n log n)。

总的来说,堆排序的时间复杂度为O(n log n),空间复杂度为O(1)。

归并排序

归并排序的时间复杂度为O(n log n),空间复杂度为O(n)。

在归并排序中,我们首先将数组分成两个子数组,然后对每个子数组进行递归排序。 这个过程需要使用一个额外的空间来存储归并排序的中间结果。因此,空间复杂度为O(n)。

归并排序的核心操作是归并操作,它将两个已经排好序的子数组合并成一个排好序的数组。归并操作的时间复杂度为O(n), 因为我们需要遍历两个子数组中的所有元素。在最好的情况下,归并排序的时间复杂度为O(n log n), 因为每次归并操作都可以将数组分成两个等长的子数组,这样递归的深度就为log n。 在最坏的情况下,时间复杂度为O(n^2),因为归并操作可能无法将数组等分。

总的来说,归并排序的时间复杂度为O(n log n),空间复杂度为O(n)。

飞梭排序

飞梭排序(Fisher-Jenkins Sort)是一种排序算法,它是由罗纳德·费雪和哈斯利·詹金斯于1972年提出的。 这种排序算法基于奇偶排序的思想,也被称为奇偶移动排序或偶移动排序。

飞梭排序算法的基本步骤如下:

  1. 从最后一个元素开始,每隔一个元素取一个值,将取到的元素按照升序插入到一个新的序列中。
  2. 对于剩下的未排序元素,采用相同的方式,每隔一个元素取一个,并插入到新序列中。
  3. 重复这个过程,直到整个序列都排好序。

飞梭排序算法的时间复杂度为O(n^2),与冒泡排序算法相同。这种排序算法在实际应用中并不常用, 因为它比其他更高效的排序算法(如快速排序、归并排序等)更慢。

二分查找

二分查找的时间复杂度为O(n log n),空间复杂度为O(1)。

在二分查找中,我们需要将数组分成两部分,对每部分进行递归查找。 这个过程需要使用一个额外的空间来存储分区的索引。因此,空间复杂度为O(1)。

二分查找的核心操作是查找操作,它将数组分成两个部分,并选择其中一个部分进行递归查找。 这个操作的时间复杂度为O(log n),因为每次查找操作都可以将数组分成两个等长的子数组,这样递归的深度就为log n。 在整个数组中查找每个元素都需要进行一次二分查找,因此时间复杂度为O(n log n)。

总的来说,二分查找的时间复杂度为O(n log n),空间复杂度为O(1)。

暴力匹配算法

暴力匹配算法,也称为朴素匹配算法,是一种简单的字符串匹配算法。它的基本思想是从文本串的第一个字符开始, 逐个字符地与模式串进行比较,如果匹配失败,则将模式串的指针向后移动一位,再与文本串的下一个字符进行比较, 直到找到匹配的子串或者文本串遍历完毕。

暴力匹配算法的时间复杂度为O(mn),其中m和n分别为模式串和文本串的长度。在最坏情况下,需要将模式串移动n-m+1次, 因此算法的效率较低,不适用于处理大规模的文本匹配问题。

KMP算法

KMP算法(Knuth-Morris-Pratt算法)是一种改进的暴力匹配算法,它的时间复杂度为O(m+n),其中m和n分别为模式串和文本串的长度。 KMP算法通过预处理模式串,建立一个“部分匹配表”(也称为“部分匹配表”或“失败函数”),用来减少字符串的比较次数。

KMP算法的核心是当模式串与文本串不匹配时,能够利用已经部分匹配的信息,跳过一些不必要的比较。 具体来说,当模式串中的某个字符与文本串中的某个字符不匹配时,KMP算法根据“部分匹配表”中的信息, 将模式串向右移动若干个位置,使得模式串中的某个已匹配的前缀能够与文本串中的某个子串匹配,从而避免了不必要的比较。

KMP算法的实现需要用到一个辅助函数next,该函数用于生成“部分匹配表”。next函数根据模式串的自身特点计算得到一个next数组, 数组中的每个元素next[i]表示当模式串的第i个字符与文本串中的某个字符不匹配时,模式串应该向右移动的位数。 在实际使用时,还需要根据next数组生成一个next[0]数组, 其中next[0][i]表示当模式串的前缀的第i个字符与文本串中的某个字符不匹配时,模式串应该向右移动的位数。

广度优先搜索

广度优先搜索(BFS)是一种遍历或搜索树(或图)的算法,它从根节点(或任意节点)开始,并探索所有邻居节点, 然后再探索邻居的邻居节点。这种算法属于一种盲目搜寻法,并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。

广度优先搜索的时间复杂度是O(V+E),其中V是顶点数,E是边数。在最坏的情况下,需要检查图中的所有顶点和边。 在平均情况下,如果图是相对稀疏的,其时间复杂度可以更优。

该算法使用队列来保存待探索的节点,每探索一个节点,就将其相邻的未探索过的节点加入队列。通过重复这个过程, 直到队列为空,算法结束。广度优先搜索常用于找到从源节点到另一个节点的最短路径,或者在图中找到一个特定的节点。

深度优先搜索

深度优先搜索(DFS)是一种用于遍历或搜索树(或图)的算法。这种算法会沿着一条路径尽可能深入地搜索, 然后回溯,尝试下一条路径,直到找到目标或搜索完所有可能的路径。

深度优先搜索的时间复杂度取决于图的连通性。对于连通的图,其时间复杂度为O(V+E),其中V是顶点数,E是边数。 对于非连通的图,其时间复杂度为O(V),因为需要访问所有顶点。

该算法使用递归函数来实现。从一个起始顶点开始,递归函数首先访问该顶点的所有相邻顶点。 然后,对于每个相邻顶点,递归函数再访问它们未被访问过的相邻顶点,并以此类推,直到访问到最后一个相邻顶点。 如果已经访问过相邻顶点,则回溯到前一个顶点,并尝试下一条路径。深度优先搜索常用于解决一些需要遍历图的问题, 例如找到从一个顶点到另一个顶点的最短路径,或者确定一个图是否是二分图。

迪克斯特拉算法

迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家艾兹格·迪杰斯特拉于1956年提出的,因此也叫迪杰斯特拉算法。 这是一种用于解决有权有向图中最短路径问题的算法。

迪杰斯特拉算法的主要特点是从起始点开始,采用贪心算法的策略,每次遍历到起始点距离最近且未访问过的顶点的邻接节点, 直到扩展到终点为止。算法的核心是维护一个尚未到达终点的顶点的集合,并按照距离排序,每次选取当前距离最小的顶点进行扩展。

迪杰斯特拉算法的时间复杂度为O((V+E)logV),其中V是顶点数,E是边数。 在空间复杂度方面,该算法需要存储每个顶点的距离和是否被访问的状态,因此空间复杂度为O(V)。

哈希表查找算法

哈希表查找算法是一种非常快速的查找算法,它的时间复杂度可以达到O(1)。 哈希表利用哈希函数将键映射到桶中,并在桶中进行数据的存储和查找。

哈希表查找算法的基本步骤如下:

  1. 构建哈希表:根据要查找的数据,建立一个哈希表,其中每个键对应一个桶。
  2. 计算哈希值:将要查找的数据通过哈希函数计算出哈希值,这个哈希值对应一个桶。
  3. 查找数据:在对应的桶中查找数据,如果有数据存在,则返回数据;否则返回空值或者一个特定的标记。

哈希表查找算法的优点是查找速度快,时间复杂度为O(1);缺点是如果键冲突较多,则会影响查找效率。 为了解决键冲突的问题,可以采用一些方法,例如二次哈希、链地址法等。

数据结构

常见的数据结构有:

  • 数组(Array):这是最简单的一种数据结构,它是一个有序的元素集合,可以通过索引访问。
  • 链表(Linked List):链表是一种非连续的数据结构,每个元素都包含一个链接到下一个元素的引用。
  • 栈(Stack):栈是一种后进先出(LIFO)的数据结构,只有栈顶元素可以被访问和删除。
  • 队列(Queue):队列是一种先进先出(FIFO)的数据结构,只有队列头元素可以被访问和删除。
  • 链式表(LinkedList):链式表是一种特殊的链表,它在内存中分配一块连续的空间来存储元素。
  • 树(Tree):树是一种非线性的数据结构,由节点和边组成。它可以用于表示分层数据。
    1. 二叉树
    2. 堆(特殊类型的树)
    3. AVL树:AVL树是一种自平衡的二叉搜索树,它在插入和删除操作时会对树的平衡进行调整,从而保证最坏情况下的时间复杂度为O(log n)。
    4. 红黑树
    5. B树
    6. B+树
    7. Trie树(前缀树)
  • 图(Graph):图是由节点和边组成的数据结构,可以用于表示复杂的网络关系。
  • 哈希表(Hash Table):哈希表是一种关联数组的数据结构,它可以存储键值对, 根据键(Key)直接访问在内存存储区域中的值(Value)的数据结构,并能在常数时间内完成查找、插入和删除操作。
  • 集合(Set):集合是一种无序的数据结构,用于存储互不相同的元素。
  • 字典(Dictionary):字典是一种关联数组的数据结构,用于存储键值对,键必须是唯一的。
  • 堆(Heap):堆是一种特殊的完全二叉树,用于实现优先队列。
  • 数据框(Data Frame):在数据分析中,数据框是一种特殊的数据结构,用于存储表格数据。
  • 位图(Bitmap):位图是一种二进制数据结构,用于存储大量的布尔值。
  • 矩阵(Matrix):矩阵是一种二维的数据结构,用于存储线性代数中的数据。

其他的数据结构:

  • 跳跃列表(Skip List):跳跃列表是一种概率平衡的线性时间复杂度数据结构,它是一种可以进行快速查找和删除的有序链表。
  • 斐波那契堆(Fibonacci Heap):斐波那契堆是一种特殊的优先队列,用于实现高效的动态图算法。
  • 后缀数组(Suffix Array):后缀数组是一种基于字符串的数据结构,它按照字典序将所有后缀存储在一个数组中,用于解决字符串相关问题。
  • 平衡树(Balanced Tree):平衡树是一种自平衡的二叉搜索树,包括AVL树、红黑树等。
  • 字典(Hash Map):字典或哈希映射是一种关联数组的数据结构,用于存储键值对,并能够在平均情况下实现 O(1) 的查找、插入和删除操作。
  • 字典树(Trie Tree):字典树也被称为前缀树,是一种树形结构,用于存储字符串集合,有效地支持字符串查找操作。
  • B+树:B+树是一种自平衡的树,它是B树的变种,它对于内部节点增加了链接,使得范围查询更加方便,用于数据库和文件系统等领域。
  • B-tree:B-tree是一种自平衡的搜索树,主要用于存储在磁盘上的数据,以便进行高效的随机访问。
  • B*树B*树是B+树的一种改进型,它通过在节点间增加链接来支持更高效的删除操作。
  • K-D 树(k-dimensional tree):K-D 树是一种用于处理多维空间数据的数据结构,常用于空间索引和机器学习等领域。
  • Yen’s 数据结构:Yen’s 数据结构是一种用于解决线段交点问题的数据结构。
  • 主席树(Climbing Tree):主席树是一种二叉搜索树,它通过维护多个路径来加快查找速度。
  • 主席树(主席链、M-tree):主席树是一种多叉树,用于实现高效的布尔查询操作。
  • 主席树(Chairman Tree、Chairman’s Tree、Chairman’s Data Structure):主席树是一种多叉树,用于实现高效的布尔查询操作。
  • 矩阵链(Matrix Chain):矩阵链是一种表示一系列矩阵乘法运算的数据结构,常用于优化矩阵乘法的计算顺序。
  • 哈夫曼树(Huffman Tree):哈夫曼树是一种最优二叉树,它的每个节点都表示一个字符,而树的边则表示字符的二进制编码。
  • 并查集(Disjoint Set):并查集是一种树形数据结构,用于处理一些不交集的集合的合并及查询问题。
  • 线段树(Segment Tree):线段树是一种二叉树,它提供了对线段的快速查询、更新和删除操作。
  • R-树(R-tree):R-树是一种空间索引数据结构,用于高效地存储和查询空间数据。
  • R*树(R*-tree):R*树是一种改进的R-树,它在插入、删除和旋转操作方面具有更好的性能。
  • R-tree及其变种(R-tree, Rtree, R+tree, Rtree等):R-tree及其变种是一种用于处理空间数据的数据结构,常用于空间索引和数据库等领域。
  • Broom Tree:Broom Tree 是一种可用于缓存和优先队列的数据结构。
  • K-d 树(k-dimensional tree):K-d 树是一种用于处理多维空间数据的数据结构,常用于空间索引和机器学习等领域。
  • 莫比乌斯反演(Möbius Inversion):莫比乌斯反演是一种基于拉格朗日乘数法的技术,用于解决某些数学问题。
  • Trie 树(有时也称为前缀树或数字树):Trie 树是一种用于存储字符串的数据结构,适用于字符串查找和前缀查询。
  • Van Emde Boas 树:Van Emde Boas 树是一种近似于哈希表的高效数据结构,适用于小集合的查询问题。
  • 斜率优化二叉搜索树(Splay Tree):Splay Tree 是一种自适应的数据结构,能够根据查询频率进行动态调整,以优化查询效率。
  • 优先队列(Priority Queue):优先队列是一种能够存储元素并保证任何时候最小的元素总是位于队列的头部或按照其他优先级规则排列的数据结构。
  • 滑动窗口树(Sliding Window Tree):滑动窗口树是一种能够高效处理具有时间窗口限制的数据结构。
  • Rabin-Karp 算法:Rabin-Karp 算法是一种字符串搜索算法,用于查找一个短字符串(或模式)在一个长字符串中的所有出现位置。
  • Fibonacci 堆:Fibonacci 堆是一种用于实现动态集合的数据结构,它支持插入、删除和查找操作。
  • 红黑树:红黑树是一种自平衡的二叉搜索树,它在插入和删除操作时能够保持树的平衡,从而保证最坏情况下的时间复杂度为O(log n)。
  • Treap:Treap是一种结合了二叉搜索树和堆(优先队列)特点的数据结构。
  • Splay树:Splay树是一种自适应的数据结构,能够根据查询频率进行动态调整,以优化查询效率。

HTTP各版本的区别是什么

一、HTTP1.0

HTTP1.0默认使用 Connection:close ,浏览器每次请求都需要与服务器建立一个 TCP 连接, 服务器处理完成后立即断开 TCP 连接(无连接),服务器不跟踪每个客户端也不记录过去的请求(无状态)。

二、HTTP1.1

HTTP1.1默认使用 Connection:keep-alive (长连接),避免了连接建立和释放的开销; 通过 Content-Length 字段来判断当前请求的数据是否已经全部接受。不允许同时存在两个并行的响应。

1、为什么需要持久连接?

Http协议的初始版本中,每进行一次Http通信就要断开一次TCP连接。每次请求都会造成TCP连接的建立和断开,增加通信量的开销。

持久连接的特点:

  • 持久连接也称为Http keep-alive,只要任意一端没有明确提出断开连接,则保存TCP连接状态。
  • 减少了TCP连接的重复建立和断开所造成的额外开销,减去了服务器端的压力。
  • 持久连接使得多数请求以管线化方式(pipelining)成为可能。可以同时并行发送多个请求,而不需要一个接一个的等待响应了。 (请求打包一次传输过去,响应打包一次传递回来),管线化的前提是在持久连接下。

2、Http1.1缺陷

(1)高延迟,带来页面加载速度的降低。(网络延迟问题只要由于队头阻塞,导致宽带无法被充分利用)

(2)无状态特性,带来巨大的Http头部。

(3)明文传输,不安全。

(4)不支持服务器推送消息。

三、HTTP2.0

SPDY协议:2009年谷歌公开了SPDY协议,主要解决Http1.1效率不高的问题。

TCP -> SSL -> SPDY -> HTTP

SPDY被当做HTTP2.0的基础,其主要特性(兼容老版本HTTP协议,同时可以使用SSL功能)都在HTTP2.0中得到继承。

HTTP2.0:基于SPDY,专注于性能,目标是在用户和网站直接只用一个连接。

1、HTTP2.0新特性

(1)二进制传输

http2.0将请求和响应数据分割为更小的帧,并且它们采用二进制编码(http1.0基于文本格式)。 多个帧之间可以乱序发送,根据帧首部的流表示可以重新组装。

(2)Header压缩

Http2.0开发了专门的“HPACK”算法,大大压缩了Header信息。

(3)多路复用

http2.0中引入了多路复用技术,很好的解决了浏览器限制同一个域名下的请求数量的问题。

多路复用技术可以只通过一个TCP链接就可以传输所有的请求数据。

(4)服务端推送

HTTP2.0在一定程度上改变了传统的“请求-应答”工作模式,服务器不再完全被动地响应请求, 也可以新建“流”主动向客户端发送消息。(例如,浏览器在刚请求html的时候就提前把可能会用到的JS、CSS文件发送给客户端, 减少等待延迟,这被称为“服务端推送Server Push”)

服务器也不能随便将第三方资源推送给客户端,必须经过双方确认。

2、HTTP2.0缺点

(1)TCP以及TCP+TLS建立连接的延迟(握手延迟)

(2)TCP的队头阻塞没有彻底解决(http2.0中,多个请求是跑在一个TCP管道中的, 一旦丢包,TCP就要等待重传(丢失的包等待重新传输确认),从而阻塞该TCP连接中的所有请求)

因为HTTP2.0存在这些缺点,所以出现了HTTP3.0。

四、HTTP3.0

Google在推行SPDY的时候意识到了上述http2.0一系列问题,于是又产生了基于UDP协议的“QUIC”协议, 让HTTP跑在QUIC上而不是TCP上。从而产生了HTTP3.0版本,它解决了“队头阻塞”的问题。

特点:

(1)实现了类似TCP的流量控制,传输可靠性的功能。

(2)实现了快速握手功能(QUIC基于UDP,UDP是面向无连接的,不需要握手和挥手,比TCP快)

(3)集成了TLS加密功能

(4)多路复用,彻底解决TCP中队头阻塞的问题(单个“流”是有序的,可能会因为丢包而阻塞,但是其他流不会受到影响)

五、总结

HTTP1.1的缺点:安全性不足和性能不高;

HTTP2.0完全兼容HTTTP1.0,是“更安全的HTTP,更快的HTTPS”,头部压缩,多路复用等技术充分利用了带宽,降低了延迟。

HTTP3.0的底层支撑协议QUIC基于UDP实现,又含TCP的特点,实现了又快又可靠的协议。

浏览器中输入网址到内容展示发生了什么

在浏览器中输入一个网址,到窗口页面中展示内容,过程中发生了哪些事?

电脑上有一块 网卡,每个网卡出场时都有自己的一个 mac地址,网卡工作在第二层,即链路层。 网卡工作时,首先需要有自己 IP地址 ,网卡会通过 交换机 在局域网内广播一条信息,让 DHCP服务器 给自己分配一个IP地址。 DHCP服务器收到这条信息后,会回复有一个IP地址可以使用,网卡向DHCP服务器确认自己要使用这个IP。 DHCP服务器收到这条信息后,会记录下这个mac对应的IP,然后回复一条确认信息,并把 网关路由器IP地址 、DNS服务器IP地址 附带上。 网卡向 域名定位的服务器 获取数据时要先知道 域名定位的服务器的IP地址。这要使用到DNS服务。 网卡发送信息是要通过 网关路由器 的,但却只有 网关路由器的IP地址,却不知道 网关路由器的mac地址,所以信息不知道发送给谁。 现在网卡还要知道 网关路由器的mac地址,网卡会通过广播询问网关路由器IP地址的mac地址是多少, 网关路由器收到后会把自己的mac地址回复过去,然后网卡会把这个mac地址记录下来,这就是 ARP地址解析协议。 现在网卡可以通过网关路由器向DNS服务器查询 域名的IP地址 了,DNS服务器返回域名的IP地址。 然后网卡把要发送的信息发送到域名的IP地址上去,具体发送给网关路由器后就不管了,后面由广域网来负责传输。

其中牵涉到的协议有:

  • DHCP
  • ARP
  • DNS

具体可以参阅下 https://mp.weixin.qq.com/s/vyHlB9pem4rv4htJS9ca6Q

一个请求到达 nginx 的全部处理过程

描述一下:一个请求到达 nginx 的全部处理过程(nginx 自身会调用哪些逻辑)、然后怎么与 php 通信,中间的流程是什么样的等等?

参阅 https://www.jianshu.com/p/df89b530db89https://blog.csdn.net/xiajun07061225/article/details/9309273

防盗链

如果某个博客通过判断 referer 方式来进行图片防盗链,如何破解?

curl 设置来源地址来欺骗对方服务器验证

秒杀系统

设计一个秒杀系统,如何保证商品不超卖?

1、前端三板斧【扩容】【限流】【静态化】

2、后端两条路【内存】+【排队】

参阅 https://blog.csdn.net/zhoudaxia/article/details/38067003

前端

面对高并发的抢购活动,前端常用的三板斧是【扩容】【静态化】【限流】

A:扩容

加机器,这是最简单的方法,通过增加前端池的整体承载量来抗峰值。

B:静态化

将活动页面上的所有可以静态的元素全部静态化,并尽量减少动态元素。通过CDN来抗峰值。

C:限流

一般都会采用IP级别的限流,即针对某一个IP,限制单位时间内发起请求数量。

或者活动入口的时候增加游戏或者问题环节进行消峰操作。

D:有损服务

最后一招,在接近前端池承载能力的水位上限的时候,随机拒绝部分请求来保护活动整体的可用性。

后端

那么后端的数据库在高并发和超卖下会遇到什么问题呢?主要会有如下3个问题:(主要讨论写的问题,读的问题通过增加cache可以很容易的解决)

I: 首先MySQL自身对于高并发的处理性能就会出现问题,一般来说,MySQL的处理性能会随着并发thread上升而上升, 但是到了一定的并发度之后会出现明显的拐点,之后一路下降,最终甚至会比单thread的性能还要差。

II: 其次,超卖的根结在于减库存操作是一个事务操作,需要先select,然后insert,最后update -1。 最后这个-1操作是不能出现负数的,但是当多用户在有库存的情况下并发操作,出现负数这是无法避免的。

III:最后,当减库存和高并发碰到一起的时候,由于操作的库存数目在同一行,就会出现争抢InnoDB行锁的问题, 导致出现互相等待甚至死锁,从而大大降低MySQL的处理性能,最终导致前端页面出现超时异常。

针对上述问题,如何解决呢? 我们先看眼淘宝的高大上解决方案:

I: 关闭死锁检测,提高并发处理性能。

II:修改源代码,将排队提到进入引擎层前,降低引擎层面的并发度。

III:组提交,降低server和引擎的交互次数,降低IO消耗。

以上内容可以参考丁奇在DTCC2013上分享的《秒杀场景下MySQL的低效》一文。在文中所有优化都使用后,TPS在高并发下, 从原始的150飙升到8.5w,提升近566倍,非常吓人!!!

不过结合我们的实际,改源码这种高大上的解决方案显然有那么一点不切实际。 于是小伙伴们需要讨论出一种适合我们实际情况的解决方案。以下就是我们讨论的解决方案:

首先设定一个前提,为了防止超卖现象,所有减库存操作都需要进行一次减后检查,保证减完不能等于负数。 (由于MySQL事务的特性,这种方法只能降低超卖的数量,但是不可能完全避免超卖)

update number set x=x-1 where (x -1 ) >= 0;

解决方案1:

将存库从MySQL前移到Redis中,所有的写操作放到内存中,由于Redis中不存在锁故不会出现互相等待, 并且由于Redis的写性能和读性能都远高于MySQL,这就解决了高并发下的性能问题。然后通过队列等异步手段,将变化的数据异步写入到DB中。

优点:解决性能问题

缺点:没有解决超卖问题,同时由于异步写入DB,存在某一时刻DB和Redis中数据不一致的风险。

解决方案2:

引入队列,然后将所有写DB操作在单队列中排队,完全串行处理。当达到库存阀值的时候就不在消费队列,并关闭购买功能。这就解决了超卖问题。

优点:解决超卖问题,略微提升性能。

缺点:性能受限于队列处理机处理性能和DB的写入性能中最短的那个,另外多商品同时抢购的时候需要准备多条队列。

解决方案3:

将写操作前移到Memcached中,同时利用Memcached的轻量级的锁机制CAS来实现减库存操作。

优点:读写在内存中,操作性能快,引入轻量级锁之后可以保证同一时刻只有一个写入成功,解决减库存问题。

缺点:没有实测,基于CAS的特性不知道高并发下是否会出现大量更新失败?不过加锁之后肯定对并发性能会有影响。

解决方案4:

将提交操作变成两段式,先申请后确认。然后利用Redis的原子自增操作(相比较MySQL的自增来说没有空洞), 同时利用Redis的事务特性来发号,保证拿到小于等于库存阀值的号的人都可以成功提交订单。然后数据异步更新到DB中。

优点:解决超卖问题,库存读写都在内存中,故同时解决性能问题。

缺点:由于异步写入DB,可能存在数据不一致。另可能存在少买,也就是如果拿到号的人不真正下订单,可能库存减为0,但是订单数并没有达到库存阀值。

碰到CPU飙升怎么解决

当PHP项目运行时遇到CPU飙升的问题,可以采取以下措施来解决:

  • 检查代码:首先,检查PHP项目的代码,特别是涉及循环、递归和数据库查询的部分。 确保代码中没有低效的算法或逻辑错误,这些可能导致CPU使用率增加。
  • 优化数据库查询:检查数据库查询是否有效率较低或者有没有使用索引,尽量优化查询语句,减少全表扫描和复杂的联合查询。 可以考虑使用数据库分析工具来找出性能瓶颈。
  • 使用缓存:如果项目中涉及到频繁访问的数据,可以考虑使用缓存技术来减轻数据库或文件系统的负载。 例如,使用Redis或Memcached等内存缓存系统来存储热点数据。
  • 调整PHP配置:检查PHP的配置文件(php.ini)中的相关设置,如内存限制(memory_limit)、 最大执行时间(max_execution_time)等。根据实际情况进行调整,确保PHP进程有足够的资源来运行。
  • 负载均衡:如果项目需要处理大量并发请求,可以考虑使用负载均衡技术来分配请求到多个服务器或进程上,以减轻单个服务器的压力。
  • 监控与分析:使用工具监控服务器的性能指标,如CPU使用率、内存占用、磁盘IO等。 可以使用像Zabbix、Nagios或者Prometheus等工具来实时监控和警报。同时,通过分析日志和错误报告,找出导致CPU飙升的具体原因。
  • 更新PHP版本:检查当前使用的PHP版本是否存在已知的性能问题或漏洞。 如果是较旧的版本,考虑升级到最新的稳定版本以获得更好的性能和安全性。
  • 考虑使用其他技术栈:如果PHP项目中的性能问题难以解决,可以考虑将部分功能或整个项目迁移到其他技术栈, 如使用Node.js、Python或Go等语言来替代PHP。

在项目中Redis主要用来做什么了

询问你在项目中使用Redis做什么了。

具体可以结合项目回答,如:存储热点数据,减少对数据库的查询(分类列表等);数据共享,如队列及消费;

项目的QPS

你开发的项目的QPS是多少?

有两个概念要区分开,QPS、TPS。

QPS(Queries Per Second,每秒查询率):是一台服务器每秒能够相应的查询次数, 是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准,即每秒的响应请求数,也即是最大吞吐能力。

TPS(Transactions Per Second,每秒事务数):一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。 一个事务通常包括一个或多个查询请求,这些请求共同完成一个业务操作。 在Web应用中,一个页面加载可能就是一个事务,但也可能包含多个请求(如CSS、JS、图片等资源的加载)。 客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数。

Qps基本类似于Tps,但是不同的是,对于一个页面的一次访问,形成一个Tps,但一次页面请求, 可能产生多次对服务器的请求(如中间夹杂的同步/异步请求),服务器对这些请求,就可计入“Qps”之中。

我们看一下百度的TPS,寻找多少个客户端请求时无失败的临界点:

[root@localhost ~]# webbench -c 10 http://www.baidu.com/
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.

Benchmarking: GET http://www.baidu.com/
10 clients, running 30 sec.

Speed=444 pages/min, 2963449 bytes/sec.
Requests: 222 susceed, 0 failed.
[root@localhost ~]#
[root@localhost ~]# webbench -c 120 http://www.baidu.com/
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.

Benchmarking: GET http://www.baidu.com/
120 clients, running 30 sec.

Speed=506 pages/min, 3208671 bytes/sec.
Requests: 236 susceed, 17 failed.
[root@localhost ~]#
[root@localhost ~]#
[root@localhost ~]# webbench -c 100 http://www.baidu.com/
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.

Benchmarking: GET http://www.baidu.com/
100 clients, running 30 sec.

Speed=450 pages/min, 3006399 bytes/sec.
Requests: 222 susceed, 3 failed.
[root@localhost ~]#

临界点基本在100个客户端,此时服务器每分钟能够处理450个页面,每秒钟能够处理7.5个页面,即TPS是7.5。

峰值 QPS 和机器计算公式

原理:每天 80% 的访问集中在 20% 的时间里,这 20% 时间叫做峰值时间

公式:(总 PV 数 * 80%) / ( 每天秒数 * 20% ) = 峰值时间每秒请求数 (QPS)

机器:峰值时间每秒 QPS / 单台机器的 QPS = 需要的机器

问:每天 300w PV 的在单台机器上,这台机器需要多少 QPS?

答:(3000000 * 0.8) / (86400 * 0.2 ) = 139 (QPS)

问:如果一台机器的 QPS 是 58,需要几台机器来支持?

答:139 / 58 = 3

QPS侧重的是日请求,是一天所有页面请求计算的结果,而不是单个页面请求计算的结果,单个页面彼此之间的请求速度会有很大的差异。 这里我们就拿一个页面的请求举例来说一下QPS是多少,还是使用WebBench进行测试。

webbench -c 1000 -t 60 http://example.com/

并发客户端数(clients):1000,测试时间(time):60秒,请求Url:http://example.com/。结果:

Webbench - Simple Web Benchmark 1.5  
Benchmarking: GET http://example.com/  
1000 clients, running 60 sec.  
  
Speed=5844 pages/min, 974 reqs/sec, 121922 bytes/sec  
Requests: 58440 susceed, 0 failed

根据测试结果,可以直接读取reqs/sec字段的值来得到QPS:974 reqs/sec。

在这个实例中,WebBench模拟了1000个并发客户端,在60秒内对http://example.com/进行了测试。 测试结果显示,服务器每秒能够处理974个HTTP请求,即QPS为974。同时,测试还告诉我们服务器每分钟能够处理5844个页面(pages/min), 并且每秒能够传输121922字节的数据(bytes/sec)。计算得出每秒能够处理97.4个页面(pages/sec),每个页面大概有10次请求。






参考资料

程序员面试中最常见的27个问题,拿走不谢! https://zhuanlan.zhihu.com/p/36896984

有了这份程序员面试指南,你离大厂Offer还远吗? https://zhuanlan.zhihu.com/p/165413854

程序员面试 10 大潜规则,千万不要踩坑! https://mp.weixin.qq.com/s?__biz=Mzg2MzE5MjMxNQ==&mid=2247502803&idx=1&sn=f9cf74b6dc0ddd24482f9204108fe023&chksm=ce7ede47f9095751180c9c2145c481c5d45a3465f30fc570154fbcfe1a1c01bc60748468fac3&scene=21#wechat_redirect

面试了一个 39 岁程序员,我有点慌…… https://mp.weixin.qq.com/s?__biz=Mzg2MzE5MjMxNQ==&mid=2247502845&idx=1&sn=510126368bb6d9eb5c279e99a378f13f&chksm=ce7ede69f909577f5a022abd584b46427ddd25bf29569b67539586e231e7b506f043b71bd7c1&scene=21#wechat_redirect

HTTP及其版本(HTTP1.0、HTTP1.1、HTTP2.0、HTTP3.0)详解 https://blog.csdn.net/weixin_53186633/article/details/123624445


返回