算法初探
元无心
2023年尽量不摆烂
展开
-
二分查找的经典样例
对于查找任务来说,通常就是寻找目标值或者目标区间出现的位置。而看到排序数组+查找,第一反应就是二分查找。原创 2022-08-02 20:38:27 · 568 阅读 · 0 评论 -
[LeetCode 104] 递归、迭代与“控制反转”
程序员是否愿意将自己对代码的一部分控制权交出去,这可能是比题目本身值得思考的问题。原创 2022-07-03 22:38:36 · 206 阅读 · 0 评论 -
[LeetCode 76] 滑动窗口中的“信号量”
在保证滑动窗口的大框架下,使用类似于信号量的思想,让 t 在初始化的时候“占用”信号量,后面随着滑窗的扩展逐渐调整信号量的使用。原创 2022-06-27 22:47:35 · 225 阅读 · 0 评论 -
[LeetCode 1094] 差分数组的本质是动态规划
差分数组其实就是动态规划的一个变体,因为对区间的修改最终还是会落实到每一个元素上。而且,这种修改一般是完全相同的,所以差分数组实际上是对状态修改进行了合并,从而优化了动态规划中 DP 数组的修改开销。...原创 2022-06-24 16:06:07 · 294 阅读 · 0 评论 -
使用 heapq 对链表结点对象进行排序
相当于是手动给 heapq 指定一个 compartor。原创 2022-06-21 15:58:57 · 441 阅读 · 1 评论 -
从反转二进制位中的位运算分治到不同语言的移位操作
反转二进制位其实是一个很简单的问题,比如说反转一个32位无符号整数的二进制位,从 0101 变成 1010(注意这里是无符号的)。这个问题本身的复杂度是很低的,即使是用最直觉的算法,也无非是 O(k)O(k)O(k),也就是数字的位数,一位一位地反转。我随便写了一个:class Solution: def reverseBits(self, n: int) -> int: res = 0 for i in range(32): res原创 2021-05-11 23:31:30 · 200 阅读 · 0 评论 -
动态规划随笔
DP本质上还是一个状态机,而且还是一个单向的状态机。先不管什么重叠子问题和最优化问题,只说他的本质,就是一个对所有状态间可能存在的转移的枚举,然后以n维向量的形式表示出来。这是一个memo。沿着某一个“方向”,记录所有枚举的情况,然后得到答案。这个方向,在LCS里可能是字符串的下标,在股票问题里可能是天数,在楼梯里可能是上楼的过程(往上),在fib里可能是数字增大。当然这也体现出fib不算DP,只是一个memo,因为他没有可选的转移,每次只有一个选项。DP也可以看成是逆向的递归。因此,DP的过程不一定是原创 2021-05-04 16:01:10 · 170 阅读 · 0 评论 -
[LeetCode 19] 删除链表的倒数第N个结点
其实还算比较简单的一个题。链表往往会和双指针绑定在一起。因为链表这个数据结构的特点,并且链表长度未知,不太可能会有小于 O(n)O(n)O(n) 的解法出现。function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null { let slow = head, fast = head, last = null; while (n) { fast = fast!.next; --原创 2020-10-06 17:02:10 · 183 阅读 · 0 评论 -
在JavaScript / TypeScript中使用栈——几种栈的使用方法
这里主要介绍几种栈本身的使用方法,不包括一些作为容器其他用法,比如进行DFS,用来保存中间结点等等;也不包括递归栈,虽然有时候也可以把使用递归方法看做使用了栈,但抠字眼没什么意思。此外,本文不会详细介绍语法,请读者担待。用数组实现栈这个并不属于正文,而且是很简单的内容,但是我觉得还是有必要在这里稍微提一下;看似很简单的东西也会有坑在里面。比如三合一问题,用一个数组实现三个有容量上限的栈:class TripleInOne { private data: number[] = []; priv原创 2020-07-01 22:46:27 · 1294 阅读 · 0 评论 -
让我重新看看汉诺塔问题
汉诺塔可以说是一个非常经典的递归问题了,在很多书上也会把它作为递归的入门题,用来介绍递归的基本概念。故事的背景和问题的具体内容就不在这里介绍了,我觉得我并没有搞明白递归是怎么一回事,比起迭代,递归从头到脚都透露着一种神秘;虽然我想不到,但是递归的逻辑很清晰。这两者并不矛盾。抒情完毕,说正事。如何解决汉诺塔问题(在这里相当于是把A移到C,并且直接在C上修改)?很简单:function hanota(A: number[], B: number[], C: number[]): void { C.pu原创 2020-06-30 22:40:33 · 367 阅读 · 0 评论 -
[LeetCode 41] 用原数组保存信息
首先这题一个很明显的性质:缺少的那个整数一定在[1, n + 1](n为nums的长度)之间。为什么?假设这个数组是从1开始,然后依次递增并且不重复,全部填充时数组正好是[1, n],此时缺少的整数就是n + 1;然后前面替换掉哪个数,哪个数就是缺少的,在[1, n]之间。所以,可以想到一个很自然的解法,用一个数组来记录[1, n]之间哪个数已经出现过了,没出现的那个就是缺少的;如果全都出现了,那就是缺少了n + 1。这个解法我认为比哈希表还要自然:int firstMissingPositive(ve原创 2020-06-27 10:52:59 · 201 阅读 · 0 评论 -
每天一个被拒小技巧——BigInt
LeetCode 67题题解。想必经典的模拟加法大家都会,所以看到这题的时候,我就知道一定会有人用BigInt;果然如此。但是那位并没有解释BigInt的相关内容,只是给出了答案,所以这里主要是介绍一下BigInt的内容。先来看写法:function addBinary(a: string, b: string): string { return (BigInt("0b" + a) + BigInt("0b" + b)).toString(2);}关于这个写法,可能存在三个疑问:为什么.原创 2020-06-23 17:57:45 · 814 阅读 · 0 评论 -
从爬楼梯简单谈谈执行性能
爬楼梯这个问题,其实思路是很明确的:爬到当前楼梯的方法数 = 爬到上一级楼梯(然后再爬一级,只有这一种选择)的方法数 + 爬到上两级楼梯(然后再爬两级,只有这一种选择)的方法数,也就是:f(x)=f(x−1)+f(x−2)f(x)=f(x-1)+f(x-2)f(x)=f(x−1)+f(x−2)这个其实是老生常谈了,它其实就是著名的斐波那契数列,可以循环或者递归求解,也可以用dp来剪枝,然后进一步对dp的数组进行优化(所谓的滚动数组),乃至矩阵快速幂、通项公式,或者更极端的直接打表(如果指定范围的话)原创 2020-06-13 11:13:58 · 264 阅读 · 0 评论 -
计算N叉树的最大深度,搞点阳间的写法
其实题目是很简单的。但这就是函数式编程?????????❤了。var maxDepth = function (root) { function traverse(root, depth) { return Math.max( depth, ...root.children.map((child) => traverse(child, depth + 1)) ); } return root ? traverse(root, 1) : 0;};原创 2020-06-10 17:04:04 · 317 阅读 · 0 评论 -
等式方程的可满足性——TypeScript并查集实现
总的来说,思路还是很清晰的。一看到这个题就想到了图,==构成的是连通关系,只要有连通关系,就可以构造图来解决问题。而解决图的问题,比较常见的思路的就是BFS(或者DFS)和并查集,这个题感觉用BFS和BFS有点麻烦,那就用并查集了:function equationsPossible(equations: string[]): boolean { const parent: number[] = []; for (let i = 0; i < 26; ++i) { parent.p原创 2020-06-08 12:05:51 · 308 阅读 · 0 评论 -
TypeScript 迭代 + 前缀和解决路径总和问题
好像还没人写过ts版本的题解,我就先来抛砖引玉了(笑)。思路其实很简单,就是在前序遍历的过程中,记录当前结点为止的结点值之和,这样到叶结点的时候就可以很容易地判断路径总和是否等于目标值了。因为用的是迭代,不像递归那么直接就可以处理前缀和,所以干脆用一个Map来存储了。function hasPathSum(root: TreeNode | null, sum: number): boolean { if (!root) return false; const stack = [root];原创 2020-06-07 23:06:31 · 587 阅读 · 0 评论 -
pow函数有四样写法,你知道吗
求次幂是个老生常谈的话题了,在任何一门语言里都是最基础的部分。在这里主要以JS为例。首先,最容易想到的是暴力法,也就是连续乘上n个x;但是这个的性能显然很差,我们一般不会考虑这么写。而且,为什么不用内置函数呢?调内置函数没什么难度,随便举个例子吧:var myPow = function(x, n) { return Math.pow(x, n);};确实,这个没什么难度,并且内置函数一般得到了充分的优化,性能也是很有保障的。但是,内置函数一定就快吗?还能进一步优化吗?还有没有别的方法?这原创 2020-05-11 19:20:08 · 2534 阅读 · 0 评论 -
关于快乐数的一个猜想
有一段时间没写博客了,今天看到快乐数,突然想起很久之前的一个猜想,就又翻了出来。快乐数的特性是什么?该数字所有数位的平方和,得到的新数再次求所有数字的平方和,如此重复进行,最终结果一定为1。而我们知道,INT_MAX是21_4748_3647。如果想让次数尽可能多,那直觉应该是让所有数位的平方和最大。在INT_MAX的范围内,数位平方和最大的数字应该是19_9999_9999,而这个数的数位平方...原创 2020-04-30 09:22:47 · 376 阅读 · 0 评论 -
关于C++的构造函数与析构函数的调用顺序
其实这个是一个比较基础的问题,因为今天有人问我这个事情,我就拿出来稍微复习了一下,顺便记录一下。题目是这样的,解释一下这个程序的运行过程:首先需要明确:C++析构函数的调用顺序与构造函数的调用顺序相反。C++对象的生命周期与作用域相同。调用过程:Test类声明时顺便定义了t0,没有参数,使用默认参数,触发0 cons。main里首先定义t1,传入参数1,覆盖默认参数,触发1 ...原创 2020-03-19 11:59:54 · 296 阅读 · 0 评论 -
将数组分成和相等的三个部分,以及maven报错“找不到或无法加载主类”
这两个确实是风马牛不相及的东西,但是在等待SQL脚本运行的时候做一道算法题,似乎也没什么问题(笑)。先说maven的事情。因为是前端,平时不写Java,遇到这个熟悉的报错居然有点不知所措……用的是IDEA,检查project structure之后发现没有问题,然后看看target里生成的class,发现没有对应的class文件,也就是说没build成功,也不知道为啥。maven clean之后...原创 2020-03-11 09:30:11 · 208 阅读 · 0 评论 -
关于二叉搜索树的一些总结
坦率地讲,我一直觉得树这个结构特别复杂,主要是我搞不太清楚递归的过程,所以老是忘(就在刚才,我又忘了怎么把一个有序数组变成BST),主要还是不理解吧……所以就总结一下,方便下次查询。二叉搜索树BST是一个很常见的结构,并且有一些特别好的性质:节点 N 左子树上的所有节点的值都小于等于节点 N 的值节点 N 右子树上的所有节点的值都大于等于节点 N 的值左子树和右子树也都是 BST...原创 2020-01-28 18:29:38 · 466 阅读 · 0 评论 -
下一个更大元素
今天遇到了很有意思的一类题目,也就是标题里说的,“下一个更大元素”。先看看它的简单版本:给定两个没有重复元素的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。找到 nums1 中每个元素在 nums2 中的下一个比其大的值。nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出-1。...原创 2020-01-27 15:23:31 · 263 阅读 · 0 评论 -
string超出内存限制:C++ string的operator+=、operator+、append与push_back
沉寂了很久,从今天开始重操旧业,做点题啥的,一方面是比较功利性的,另一方面……好吧确实是比较功利性的,没什么特殊的原因。今天遇到了一个很有意思的问题:string超出内存限制。题目其实很简单,也没什么太多的坑;虽然说测试用例里的字符串确实很大,但理论上来说是不应该出现这个问题的,并且我在本地测试也没有问题(本地是MinGW的g++)。后来发现问题出在这里,比如我要实现一个字符串反转(以下是示...原创 2020-01-26 19:59:22 · 2347 阅读 · 1 评论 -
计算反转次数时小心精度丢失
LeetCode第541题反转字符串 II 的题解原创 2019-12-21 21:28:19 · 149 阅读 · 0 评论 -
字符串中的第一个唯一字符
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。可以假定该字符串只包含小写字母。其实这个题也没什么可说的,是一个很简单的题目。首先第一反应肯定是用map(不管是C++的unordered_map还是Java的HashMap,或者是JS里的Map,Python里的dict,又或者是别的语言里的类似的数据结构),而且这种规定了范围的题目可以将map退化成数...原创 2019-12-04 10:01:25 · 403 阅读 · 0 评论 -
通过参数传递辅助判断左叶结点
LeetCode第404题左叶子之和的题解原创 2019-12-02 09:32:55 · 142 阅读 · 0 评论 -
回文链表
这个题目值得专门记一笔,其中有一个点(对我而言)实在是很精妙,颇有茅塞顿开之感。我觉得可以算是双指针这一块比较灵性的写法了。其实判断回文数没什么难度,有各种各样的方法,但是把回文数放进链表里就不一样了。我感觉链表的一个特点就是局部性,每次只能知道下一个元素,不像数组一样能掌控全局,相对来说信息量比较少(所以为了更多的信息量,就有了双向链表);数组和链表一个全局一个局部,正好构成了数据结构的最基本...原创 2019-11-22 17:19:56 · 160 阅读 · 0 评论 -
2的幂和3的幂和4的幂
有一段时间没写了,最近实在是太忙,被各种ddl搞得焦头烂额。今天忙里偷闲,写了几道有意思的题目。这几道题其实没有任何难度,如果要判断一个数是不是n的幂,其实只要不断除以n,看看最后是不是1就行了,用几乎同一段代码就能AC这三道简单题;当然了,要改一下参数。概括下来,无非是这样:bool isPowerOfN(int num, int N){ double x = static_cas...原创 2019-11-20 18:52:35 · 360 阅读 · 0 评论 -
环形链表和相交链表
原来双指针还能这么用吗……其实要这么说,很容易就能想起小学的追及问题,在环形跑道上跑步的两个人,跑得快的人到最后反而会跑到跑得慢的人的后面。我感觉我们直觉的思路偏向于线性和二维的矩形。这么说有点玄学的成分,但是如果我现在说平面,有多少人的第一反应是圆形?大部分人想到的应该还是类似于白纸一样的矩形平面吧。这就是很有意思的地方了,并没有人规定二维平面一定要是矩形的啊?这种线性思维的一个直接结果,...原创 2019-11-11 17:09:19 · 218 阅读 · 0 评论 -
二叉树的最大深度和二叉树的层次遍历 II
做了这个之后的感想,大概是两点,虽然说这两点都是常识吧:第一是在和树相关的题目上,递归不是万能的,像层次遍历levelOrder这种的还得用辅助队列迭代;第二是在递归的过程中可以用参数来保存状态。树的最大深度这个问题,我看官方题解(包括算法书上)给出的是那种语义化的解法,也就是这种:int maxDepth(TreeNode *root){ if (root == nullptr)...原创 2019-11-02 09:21:45 · 172 阅读 · 0 评论 -
相同的树和对称二叉树
今天做两道树的题目(虽然说很简单吧)。做的时候的唯一感觉是,我已经菜到连树的遍历都不会了吗……第一道题,判断两个二叉树是否相同。这个很简单,按理来说两个二叉树一起遍历一遍就行了。第一反应还是递归吧,在树上的操作用迭代还是不太习惯。不过我不知道我怎么想的,居然拿了一个string来存储遍历的结果,然后进行字符串比较:class Solution{private: string ps;...原创 2019-10-31 19:23:28 · 142 阅读 · 0 评论 -
删除排序链表中的重复元素和合并两个有序数组
一般而言,对于有序数组可以通过双指针法达到O(n + m)的时间复杂度。这句话来自官方题解。反正我感觉有序的链表和数组都是一样的,用双指针就是了。虽然说删除链表中的元素更像是考察对链表这个数据结构本身的熟悉程度,但不管是对链表的添加和删除,我觉得都可以看成是快慢指针:慢指针指向当前元素,快指针指向需要添加或者删除的元素。当然我并不觉得我这个实现很优雅,明明可以直接改一下链接的。ListN...原创 2019-10-30 11:46:31 · 360 阅读 · 0 评论 -
二进制求和和x的平方根
二进制求和没什么可说的,就是一个很简单的相加,跟加一那道题的做法基本一致(准确来说就是完全一致)。之所以提一下,是因为今天第一次遇到内存泄漏……之前写的基本是有自动GC和内存管理的语言(比如Java),看到内存泄漏居然觉得有点新奇(?)线上跑的时候报错是这样的:AddressSanitizer: heap-buffer-overflow on address 0x608000000202 at...原创 2019-10-27 10:24:35 · 923 阅读 · 0 评论 -
最后一个单词的长度和加一
没错,今天又是做简单题的一天。给定一个仅包含大小写字母和空格 ' ' 的字符串,返回其最后一个单词的长度。如果不存在最后一个单词,请返回 0。看到这个题目,我的第一反应是双指针,快指针指向数组最后一个的时候,减掉停在最后一个' '上的慢指针,就能得到长度了。但是想想觉得用指针还不如直接计数来得实在,而且这样做存在一个问题,如果是类似于" a "这种的字符串,就需要保存之前的状态,所以还需要...原创 2019-10-26 12:08:33 · 1390 阅读 · 0 评论 -
搜索插入位置和区域和检索
搜索插入位置这个题,没啥好说的,就是一个非常简单的二分法。直接遍历虽然说也是个O(n),但二分法是个O(logn),能想到好办法还是尽量用好办法吧。这也是难得的能一遍AC的题目。不过我注意到一件非常有意思的事,我看基本上所有人写二分法都是用left和right进行比较,while(left<=right),但我特别喜欢拿left和mid比较,while(left < mid),也不知...原创 2019-10-23 21:21:14 · 1326 阅读 · 0 评论 -
移除元素
这两天事有点多,都没时间学习,再加上神经网络调参不太顺利,还没上火简直是个奇迹。晚上查完作业实在静不下来,做个简单题吧。做过CPP的作业之后觉得LeetCode真的友好,哪怕不会做,人家的题目描述至少是基本准确的,还有例子和输入输出;CPP那个作业是什么东西。给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。 元素的顺序可以改变。...原创 2019-10-20 23:40:13 · 166 阅读 · 0 评论 -
合并链表和数组去重
其实今天的题目还是挺简单的,但心思不定,做半天才做出来……其实主要目的还是熟悉C++的语法,对象指针和动态分配内存的new运算符的处理还是不太熟悉。合并链表的算法本身其实没什么可说的,只是感觉上对于链表的处理,通常情况下加个虚拟的头结点能好很多。而且链表的一个很重要的特点是通过链接来连接,利用这一点可以减少不必要的空间开销。比如合并链表,就不需要那些不必要的复制操作,直接改变每个结点链接的指向就...原创 2019-10-15 20:27:40 · 215 阅读 · 0 评论 -
最长公共前缀和有效括号
其实最长公共前缀这个题目很容易就能想到水平扫描(两两比较,逐渐缩小范围)或者垂直扫描(同时比较所有字符串的同一位),而且性能很好;但是前缀树我没用过,所以记录一下。C++实现和Java实现还是有点区别的,尤其是在对null的判断上。在Java里可以很轻易地进行类似于object == null这样的判断,但是在C++里就必须用指针。// 结点class TrieNode{private:...原创 2019-10-14 21:36:15 · 212 阅读 · 1 评论 -
整数反转和回文数
在开始之前,我觉得应该默念一遍奥卡姆剃刀原理:“如无必要,勿增实体”。我感觉这一条原则在做算法的时候特别重要:是什么就是什么,不应该增加额外的元素。当然,在需要空间换时间(或者空间换时间)的时候,增加辅助元素是有必要的,并不冲突。为什么这么说呢,且看这两道题目:给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。假设我们的环境只能存储得下 32 位的有符号整数,则其数值...原创 2019-10-13 16:50:21 · 392 阅读 · 0 评论 -
从暴力到优雅——两数之和的JavaScript实现
我一直相信“注释即文档”,所以思路和运行结果都在注释里写了。这道题最直接的思路,肯定就是暴力遍历了。没有什么是循环解决不了的,如果有,就再循环一次:/** * 无优化的暴力遍历,O(n^2) * @param {number[]} nums * @param {number} target * @return {number[]} * ------result------ * me...原创 2019-08-05 21:38:21 · 184 阅读 · 0 评论