Skip to content

Inside are some brushing questions and exercises from Code Whispers.

License

Notifications You must be signed in to change notification settings

DemondeLa/Training-Camp

Repository files navigation

Training-Camp

Inside are some brushing questions and exercises from Code Whispers.

建议

有的录友会因为各种各样的事情没跟上进度,时间紧张的录友 可以只把题目AC,博客简单记录思路也可以,甚至是 只把题解看一遍 了解一下思路也可以,但一定要跟上进度,这样才有节奏,如果 大家每日任务落下了 就会一直落下了,很难再追上,然后 自己进度就越来越慢,跟不上节奏,成为群里的旁观者。

哪怕是今天没时间了,就把今天安排题目的题解读一遍,了解一下整体思想,看看大家讨论内容 ,这样也是跟上了,周日自己在抓紧时间补代码练习

鼓励大家 写博客 记录,就是为了 把自己的理解沉淀下来,要不然 学了太多 都是 边学边忘,等 两个月后 大家回顾自己总结 博客,也会感觉收获满满。没有时间的话,可以简单写自己的理解 也可以的,总要有点 感悟 。

遇到奇怪bug无法解决的时候卡哥建议:==代码全删连备份都不要, 重新写==。这种情况下多数是很低级的手误 比如符号的全角半角 少了一个字母那种的 自己去看很难发现 即便花时间排查出来了也没有什么提高

多考虑一下自己代码的鲁棒性, 进入循环用到之前之后都打一下日志啥的,是很好的一个发现错误的方法!

如何Debug???

关于代码 debug 的方式,主要有以下四个层次,由易到难,大家根据自己的水平采用。但是希望通过这次训练营大家可以提升自己达到最高的层次,这是一个程序员的基本素养:

  1. 直接对照代码随想录文章中的示例代码,一行行对比看看自己哪里与之不同。看起来是最笨的办法,但是如果你是初学者,通过这个过程可以很快发现自己与好的代码之间的差距,对能力提升也非常快。
  2. 学会使用 ChatGPT 或者 new bing 等新 AI 工具,在与其对话中找出代码 bug 并解决自己的其它疑惑。新时代的 AI 工具我们要学会使用,把代码直接复制给它就可以让它帮你找出 bug 或者解释代码。
  3. 打印日志到控制台输出并通过观察它们来找出问题所在,本节下面有此种 debug 技巧的详细描述。
  4. 使用 IDE 的断点调试功能,学会在代码中打断点然后单步执行,所有变量的中间值一目了然,bug 自然无所遁形,这也是实际工作中常用debug的方式。网上教程也非常多,大家自己搜一搜就有。

下面是上述第三种 debug 方法的详细说明:

如果你打了以下日志还是没找出问题,则可以利用idea或者其他编辑器自带的断点和debug工具调试,当然可以细化debug的粒度,记住核心是先定位问题的行数,出现的轮次,在那个轮次当中进行检查

  • 一个排查问题的思路: 报错一大串, 也分不清是哪里出错了,这种情况下,你把你的函数一个一个的删(当然之前可以先备份一下),删到哪个函数,没报错了,再去填新的函数,不要整段代码一起看。
  • 对于debug 还是大家在自己不理解的地方, 预期不一样的地方之前打一下输出语句就可以了, 因为可能方法没完全理解等问题

关于看题解

很多同学总是因为自己没有办法一次性把题目A出来而感到沮丧,并且会为了自己需要去看答案而感到自己很菜,我想说的是:一刷不要耻于去看答案或题解,毕竟学习谁也不是生来就会,除非你是天才,保证自己二刷三刷可以不看就有思路或者直接写出来,就是自己最大的进步,刷题也是一种学习的过程,需要循序渐进。如果一道题你二十分钟没思路,或者说你写出来了但是debug了几十分钟,不要犹豫,直接打开卡哥的代码随想录,直接看思路,或者直接对照代码一行行检查,往往是最高效的排错过程,一般俩说出现bug的地方也只是一些微小的细节,注意得多了自然以后经验丰富了bug率自然也会降低的。

其他

图论

https://github.com/SharingSource/LogicStack-LeetCode/wiki/%E5%9B%BE%E8%AE%BA-BFS

https://space.bilibili.com/206214/channel/series

画图

https://excalidraw.com/ 大家平时刷题可以用这个网站画草稿图帮助理解!如果看题解很蒙或者思路不清晰的时候,跟着程序处理流程画一个图,90%的情况下都可以解决问题!

数据结构

https://www.cs.usfca.edu/~galles/visualization/Algorithms.html 数据结构和算法可视化可以看这个网站,还可以互动添加元素等,非常直观让你快速理解!

https://oi-wiki.org/ds

数据结构的百科全书,除了基础的数据结构外还有很多进阶的内容,学有余力的同学可以尝试成为OI佬。

c++

  • c++ 中文参考手册

  • C++函数指针与仿函数:自定义排序规则 关于C++的一些自定义排序规则方法

  • 遇到看不懂的方法的时候(C++),可能是宏定义, 方法本身就是一个 宏定义,所以要找到定义他的地方,这种项目的代码 估计宏定义满天飞,如果没有好的文档,代码很难看

  • 自增自减运算符只能用于变量;不能用于常量和表达式。

  • 在使用map容器存储多个键值对时,容器会自动根据键值对的键大小,按照既定的规则进行排序(默认升序,可以自定义)

  • 使用map存储的键值对,键既不能重复也不能修改

  • multimap和map的区别在于multimap容器可以同时存储多个键相同的键值对

  • c++很多容器都有find(x)函数:在容器中查找x值,如果找到则返回指向该值的迭代器,否则返回和end()方法结果一样的迭代器

  • 优先队列(priority_queue) 是在队列的基础上添加了内部的排序,本质上是一个堆来实现的;其提供的函数和队列的操作基本相同 声明:priority_queue<Type, Container, Funcitonal> 其中,Type指数据类型, Container指容器类型(必须是数组实现的容器,如vector,deque,不能是list。STL中默认是 vector),Functional 指比较的方式,当需要用自定义的类型时才需要传这三个参数,使用基本数据类型时,只需要传入数据类型即可,默认构造是大顶堆 一般构造为:

  • 小顶堆 priority_queue<int, vector, greater> heap;

  • 大顶堆 priority_queue 或者 priority_queue<int, vector, less> heap;

  • greater 和 less 是 std 实现的两个仿函数(即使一个类的使用看上去像一个函数,其实现是类中实现一个operator(),这个类就有了类似函数的行为)

  • 对于 pair 类型,比较时候先比较第一个元素,第一个相等再比较第二个

  • 注意 迭代器不能与NULL做比较

  • 常用的字符串拼接方式有三种:1. 直接用”+“拼接后在赋给对应值,2. “+=",3. string中的append方法,那么这三种方法有什么区别,效率如何? "+=“和append方法效率远远高于”+",因为”+“在每次拼接后会在 内存Q中创建一个新对象,然后将拼接后的字符串赋值给新对象,频繁的创建对象与拷贝消耗了大量时间。而”+="和append每次直接在原字符串进行拼接,直到字符串capacity不够时,才重新分配空间(需要一次对象创建和拷贝)。

  • 关于c的&, 建议大家好好看一下c中&的用法, 加了跟没加主要就是方法内部是否会改变本身, 也就是引用地址

  • 对于频繁插入的场景,C采用list进行插入提高速度(相比于vector),但需要注意C的list迭代器为双向迭代器,不是随机访问迭代器,只能一步一步移动

  • 注意:C++传入函数最好都使用引用,能够大大提高效率

  • c++中不加&会复制一份数组,加了是引用不用新建一个数组

  • c++在打比赛的时候有的时候会卡常数,有时候vector过不了数组能过

python

  • 注意range遍历的底层实现是把当前的内容拷贝一份后进行遍历,遍历过程中的下标不会被改变

  • 最好在写算法题时不用numpy库,可能会发生一些奇怪的问题

  • 关于self: self是面向对象部分的内容,配合类一起用的。如果不了解面向对象,可以简单记为:只要是调用这个类的成员变量或者方法就加self,调用库函数什么的不加

  • 比较两个defalutdict是否相等的时间复杂度: 它内部在比较时,首先会比较两个字典是否大小相等,如果不相等会直接返回false,时间是O(1) 如果大小相等,则会遍历其中一个字典,来看另一个字典中是否存在该key-val对,时间复杂度是O(n)

  • python中的heapify这个函数是将list变成一个堆,但是按照堆的性质,同一个父节点的两个子节点之间是没有大小要求的。所以如果直接通过索引遍历这个list,并不能保证一定是按优先级的顺序输出的。如果要保证按优先级的顺序输出,则要使用 heappop() 函数每次将堆顶弹出,来保证每次都是弹出的堆中的最值。

  • 关于Python里,为什么二维数组 append一维列表 path[:] 和 path 结果不一样呢?

  • 这其实是语言的特性,我们写一下测试例子就知道了

  • img

  • 一个是引用,一个是拷贝数据进去,前者会受到原数组改变的影响,后者则不会

  • := 是什么运算符? 海象运算符。https://www.freecodecamp.org/chinese/news/introduction-to-the-walrus-operator-in-python/

  • 判断"节点"(如链表中,树中)是否存在时候,不要直接用"非0"判断,而要用"not" 或者 与"none"比较;例如:不用 if !node,而用 if not node 或者 if node == none

  • if root.left ==None 和if not root.left是一个意思吗? if not root ,除了判定None 还判定了[]和False

刷题技巧

  • 根据题目的数据量范围选择合适的算法,比如数据量是$10^5$,那就只能使用$O(nlogn)$复杂度以下的算法了,使用$O(n^2)$是会超时的;而如果数据量只有100或者1000,则可以果断的采用暴力方法(一般是$O(n^2)$)进行求解
  • 为了特殊情况的专门写判断语句效用有多少?是否需要考虑特殊的情况? 需要考虑特殊情况,比如容器/数组/指针是否为空等,可以在考虑到特殊情况后先额外判断写出来,整体逻辑写完后看是否能和一般情况进行合并
  • while复杂度分析主要看内部总共执行多少次,不是看跟for嵌套就成了On2,滑动窗口就是On
  • 涉及到修改字符串的O(1)算法理论上是只对字符串可以修改的编程语言成立的(如C++),对于其他编程语言通常参数会给成字符数组的形式
  • 大家很多讨论都是讨论bug,发现大家还是很容易bug卡住,推荐看一下上面给出的方法哈,找bug能力是非常非常重要的!!!
  • 对于常数级别复杂度的讨论:不需要纠结固定的什么数量级的复杂度,要从算法的整体复杂度上去考虑。开一千的空间,对于百万的数据量,它就是常数。但对于同样是几千甚至是几百的数据量,认为开这样一个空间是O(1)的复杂度从而认为是一个好的算法明显是不正确的。
  • 大家力扣上建造用例的时候,对于树问题,可以尝试硬编码来进行构造数据哦如下图,还有就是利用力扣当中的playground来cv一下他们的工具,面试当中要求自己有建树操作,可以硬编码or力扣当中有给出String->TreeNode的方法
  • img
  • 几乎所有的代码问题都可以通过打日志调试来解决,哪里不懂打哪里的日志。 img
  • 把力扣的代码放到本地ide调试的话要自己写 主函数 和一些数据结构,力扣给了一个函数 我们填充好就行 它后台会在main函数里进行调用 放到本地就需要自己写main函数来进行调用啦
  • JavaScript版本的二叉树本地debug方式: https://blog.csdn.net/Maximus_ckp/article/details/125083775
  • 回溯算法与dfs的区别:所有的回溯算法都可以说是 深搜,所有的深搜 都可以说是 递归。 那么回溯算法其实就是更具体的一个分类, 也就是父类和子类的一个关系
  • 还是有同学没分清除浅拷贝和深拷贝: 浅拷贝是操作同一个对象, 深拷贝是复制一个一模一样的地址不一样的对象!!!牢记, add要new一个新的
  • 局部变量回溯, 是隐含的回溯, 全局变量的回溯是显示的回溯, 对于什么题目该选择什么回溯方法, 我建议能用全局就用全局, 隐含回溯得创建销毁还麻烦, 当然全局和局部大多数情况是可以互相转化的
  • 有的时候大家会发现debug并不能找到问题, 这其实往往问题会出现在返回值和函数调用的过程, 这种时候就只能拆分的粒度更细一点或者单独test一下函数调用
  • 递归的时候一般只需要考虑子问题, 子问题处理没错一般就能默认处理完主问题
  • 有时候看到字符需要 -'a','A',数字需要 -'0'主要是为了映射在数组上面, 不然都是ascii码映射就不准确了, 也无法定位

About

Inside are some brushing questions and exercises from Code Whispers.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published