本章博客用于持续记录 LeetCode 网站做题思路,不定期更新。内容主要包括栈、链表和队列。另外也包括一些经典如汉诺塔问题,还有字符串、求和之类的题目。
题目:给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
思路一:哈希表
建立一个哈希表,依次遍历整个链表,并将其加入哈希表中。若遍历过程中某元素的相同元素个数大于1,即出现环。
若遍历过程中出现空节点,则无环。
思路二:快慢指针
链表有环的一个特性:快指针最终会追上慢指针。
若最终快指针指向空节点,则无环。
平凡解:头节点指针为空或头节点的next指针为空
题目:给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
说明:不允许修改给定的链表。
思路一:哈希表
遍历链表,判断该节点是否在哈希表内:在则返回该节点,否则继续遍历。若最后访问至空指针,则返回 nullptr 。
思路二:快慢指针(fast, low)
注意:快慢指针都从头部开始移动
假设 fast 绕环n圈后和 low 相遇
任何时刻: d ( f a s t ) = 2 ∗ d ( l o w ) d(fast) = 2 * d(low) d(fast)=2∗d(low)
得出关系式: a = c + ( n − 1 ) ∗ ( b + c ) a = c + (n-1) * (b + c) a=c+(n−1)∗(b+c)
即从相遇点 到 入环点 的距离等于 头部 到 入环点
因此找到入环节点只需在头部设置结果指针res。slow 向后移动 直到和cur 相遇,返回该相遇节点。
题目:给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
注意:你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
思路:虚拟头节点
使用虚拟头节点统一所有操作。用临时节点 t m p tmp tmp 指向虚拟头节点,交换节点为临时节点后两个节点。
题目:输入一个链表,输出该链表中倒数第k个节点。
思路一:
思路二:
使用双指针,让快指针先走k步,然后快慢指针同时遍历,当快指针遍历完成时,慢指针指向的位置就是倒数第k个节点。
题目:给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
思路:添加虚拟头节点,方便将表头和其他节点元素的删除操作统一起来。此后只需要寻找链表的倒数第 k + 1 k+1 k+1个节点,然后即可删除第 k k k 个节点。寻找方式可参考上一题。
题目:找到两个单链表相交的起始节点
思路:
使用两个指针node1,node2分别从两个链表头开始遍历链表,若任何一个指针指向空,则从另外一个链表头开始遍历,直到指针相同。
这个思路妙在两个指针最终必然会相同(走过的距离均等于两个链表长度和)。
题目:定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
思路一:递归(分治思想)
递归基:该节点为空或者下一个节点为空(不需要转置)返回该节点
递归操作:
思路二:双指针
初始化:指针 c u r cur cur,指针 p r e pre pre 指向头节点
循环操作:当 p r e pre pre 不为空
题目:反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度
思路:
题目:判断一个链表是否为回文链表。
思路一:
类似括号匹配,使用栈的思想,将链表前半段转化为栈。若当前节点的值或者下一个节点的值和栈顶元素相同,则开始匹配。若成功配对则出栈。最后栈空则是回文链表。
注意在配对过程未结束而栈已空则直接返回false。
思路二:
建立一个新的反转的链表,然后逐节点进行匹配。时间和空间复杂均为O(n)
思路三:
若企图将空间复杂度降至O(1),则只需要将链表后半段进行反转。
确定中点的方法可以使用快慢指针。
题目:给出两个非空的链表用来表示两个非负的整数。其中,它们各自的位数是按照逆序的方式存储的,并且它们的每个节点只能存储 一位 数字。将这两个数相加起来,返回一个新的链表来表示它们的和。
思路:
从头到尾扫描记录该位下的值和进位。注意要分类讨论:两个链表相加,只剩一个链表。注意判断最高位是否有进位。
注:官方题解中,可认为长度短的链表的后面有若干个0,即不用分类。
题目:实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
思路:
确定插入栈和弹出栈。
插入栈:插入操作直接push入栈顶
弹出栈:弹出操作需要分类讨论:
时间复杂度分析:删除操作,每个元素只会至多被插入和弹出 stack2 一次,因此均摊下来每个元素被删除的时间复杂度仍为 O(1)。
题目:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
思路一:
遍历法,每次寻找min值都遍历整个栈,这个栈用数组实现。
时间复杂度为O(n)。
思路二:
使用辅助栈,在每次入栈前检查是否小于等于最小的元素,若是则入栈。
以上操作可以得到一个逆序栈,每次查询栈内最小值只需要返回逆序栈顶即可。
每次出栈需要判断是否弹出的是最小值,以此判断是否需要弹出逆序栈顶元素。
题目:对栈进行排序使最小元素位于栈顶。最多只能使用一个其他的临时栈存放数据,但不得将元素复制到别的数据结构(如数组)中。
思路:
类似插入排序,每当入栈元素大于栈顶元素时,则弹出当前栈顶元素到辅助栈中。当入栈元素按顺序入栈后,再将辅助栈中的元素弹回栈中。时间复杂度为O(n)。
题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。
思路:
用栈进行模拟pop操作。输入序列依次入栈,并根据输出序列判断栈顶元素是否弹出,最后判断栈是否为空。
题目:使用队列实现栈的下列操作:
push(x) – 元素 x 入栈
pop() – 移除栈顶元素
top() – 获取栈顶元素
empty() – 返回栈是否为空
思路一:
使用双队列:q1,q2
push(x):直接在q1入队
pop():若q1非空,则只留下最后一个,其他元素按顺序入队q2。若q2非空,同样只留下最后一个,其余元素按顺序入队q1。注意记录下该值,弹出后再返回结果值。
top():直接返回队列的尾元素
empty():若q1和q2都空则返回true
上述思路优势在于不用两个队列之间反复倒,算法效率较高,但是并未完全按照栈的特性进行元素的排列,即应满足队列前端的元素为栈顶元素。
思路二:
按照上述分析,只用队列q1按照栈的次序保存元素。因此只需在每次入“栈”时,元素入队q2。再将q1中的所有元素依次放入q2中。最后将q2中的所有元素重新放回q1中。(最后一步可以省略,即不严格区分辅助栈,使用判断语句辅助实现即可)。
注:leetcode官网解法最后一步是swap两个栈,但题目中并未允许使用这种操作。
该算法的缺点是每次入栈操作次数为3n+2,相比于思路一仅出栈操作次数2n-1,效率较低。但是符合栈元素排列的规范,思路比较容易接受。
思路三:
使用一个队列。每次第n+1个元素“入栈”时,将队列前n个元素按顺序重新出队再入队。
入栈操作次数为2n+1
题目:写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
思路:
使用队列解决,当n > 1时,记录队首元素并出队,再和队首元素相加并入队。且该记录值为结果值。
题目:请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1
思路:
类似栈的最小值这道题目。
题目:设计实现双端队列。
思路:使用数组实现。额外使用一个空间来避免判空和判满条件重合。
规定头指针指向头元素,尾指针指向尾元素的后一个位置(因此额外使用空间)。
头插入:头指针先移动,再放置元素。
尾插入:尾指针先放置元素,再移动
判空:头尾指针位置相同
判满:尾指针的下一个位置是头指针
注意:
题目:在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。移动圆盘时受到以下限制:
(1) 每次只能移动一个盘子;
(2) 盘子只能从柱子顶端滑出移到下一根柱子;
(3) 盘子只能叠在比它大的盘子上
思路:
分治递归法。
题目:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
思路:双指针法
题目:给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:
不能使用代码库中的排序函数来解决这道题。
思路一:暴力求解
遍历数组分别得到三种颜色的元素数量,再按照顺序放回数组即可。
思路二:双指针(红指针p1在头,蓝指针p2在尾)
遍历数组,当遍历位置大于p2时停止。
注意找到2时要确保交换过来的元素不能为2。
题目:输入一个字符串,打印出该字符串中字符的所有排列。你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
思路:回溯法(交换分割)
递归基:当递推深度等于字符串大小时,将该字符串记录,停止递推并返回。
从当前深度开始遍历所有可选的字符:
题目:输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
思路:使用双指针(左指针p1,右指针p2)
连续区间整数和数学公式: s u m = ( p 1 + p 2 ) ∗ ( p 2 − p 1 + 1 ) 2 sum = \frac{(p1 + p2) * (p2 - p1 + 1)}{2} sum=2(p1+p2)∗(p2−p1+1)
从最小区间开始搜索,即p1 = 1, p2 = 2。并且比较 sum 和 target
时间复杂度O(target)