LeetCode做题思路记录(链表、栈、队列等)

前言

本章博客用于持续记录 LeetCode 网站做题思路,不定期更新。内容主要包括栈、链表和队列。另外也包括一些经典如汉诺塔问题,还有字符串、求和之类的题目。

链表

环形链表

题目:给定一个链表,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。

思路一:哈希表

建立一个哈希表,依次遍历整个链表,并将其加入哈希表中。若遍历过程中某元素的相同元素个数大于1,即出现环。
若遍历过程中出现空节点,则无环。

思路二:快慢指针

链表有环的一个特性:快指针最终会追上慢指针。
若最终快指针指向空节点,则无环。

平凡解:头节点指针为空或头节点的next指针为空

环形链表 II

题目:给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

说明:不允许修改给定的链表。

思路一:哈希表

遍历链表,判断该节点是否在哈希表内:在则返回该节点,否则继续遍历。若最后访问至空指针,则返回 nullptr

思路二:快慢指针(fast, low)

注意:快慢指针都从头部开始移动

假设 fast 绕环n圈后和 low 相遇

  • d ( f a s t ) = a + n ( b + c ) + b = a + ( n + 1 ) b + n c d(fast) = a+n(b+c)+b = a+(n+1)b+nc d(fast)=a+n(b+c)+b=a+(n+1)b+nc
  • d ( l o w ) = a + b d(low) = a + b d(low)=a+b

任何时刻: d ( f a s t ) = 2 ∗ d ( l o w ) d(fast) = 2 * d(low) d(fast)=2d(low)
得出关系式: a = c + ( n − 1 ) ∗ ( b + c ) a = c + (n-1) * (b + c) a=c+(n1)(b+c)

即从相遇点入环点 的距离等于 头部入环点

因此找到入环节点只需在头部设置结果指针res。slow 向后移动 直到和cur 相遇,返回该相遇节点。

两两交换链表中的节点

题目:给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

注意:你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

思路:虚拟头节点

使用虚拟头节点统一所有操作。用临时节点 t m p tmp tmp 指向虚拟头节点,交换节点为临时节点后两个节点。

  • 当待交换节点不足两个,停止操作。
  • 交换操作:
  • 设置前后指针 f n t , b a c k fnt, back fnt,back 指向待交换节点
    • t m p . n e x t = b a c k tmp.next = back tmp.next=back
    • f n t . n e x t = b a c k . n e x t fnt.next = back.next fnt.next=back.next
    • b a c k . n e x t = f n t back.next = fnt back.next=fnt
  • t m p tmp tmp 指针后移两个节点,循环上述交换操作

链表倒数第k节点

题目:输入一个链表,输出该链表中倒数第k个节点。
思路一

  • 递归函数外设置计数器,递归函数内设置指针用于保存应该返回的节点
  • 向下递归到尾节点
  • 每次回退使计数器加一并返回当前节点
  • 当计数器等于k时,用指针记录当前节点并返回
  • 往后每次回退都接收结果指针,直到最后返回该结果

思路二
使用双指针,让快指针先走k步,然后快慢指针同时遍历,当快指针遍历完成时,慢指针指向的位置就是倒数第k个节点。

删除链表倒数第k个节点

题目:给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

思路添加虚拟头节点,方便将表头和其他节点元素的删除操作统一起来。此后只需要寻找链表的倒数第 k + 1 k+1 k+1个节点,然后即可删除第 k k k 个节点。寻找方式可参考上一题。

链表相交

题目:找到两个单链表相交的起始节点

思路
使用两个指针node1,node2分别从两个链表头开始遍历链表,若任何一个指针指向空,则从另外一个链表头开始遍历,直到指针相同。
这个思路妙在两个指针最终必然会相同(走过的距离均等于两个链表长度和)。

反转链表

题目:定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

思路一:递归(分治思想)

递归基:该节点为空或者下一个节点为空(不需要转置)返回该节点

递归操作:

  • 将返回值设置为反转链表的头节点( t a i l = R e v e r s e ( h e a d − > n e x t ) tail = Reverse(head->next) tail=Reverse(head>next)),即整个链表的尾节点
  • 反转操作: h e a d − > n e x t − > n e x t = h e a d head->next->next = head head>next>next=head h e a d − > n e x t = n u l l p t r head->next = nullptr head>next=nullptr。注意将头节点的下一个节点指针置空,防止循环。
  • 返回 “头节点”

思路二:双指针

初始化:指针 c u r cur cur,指针 p r e pre pre 指向头节点

循环操作:当 p r e pre pre 不为空

  • 先保存 p r e pre pre 的前一个节点
  • p r e − > n e x t = c u r pre->next = cur pre>next=cur
  • p r e , c u r pre,cur precur 都沿着链表后移一个节点

反转链表 II

题目:反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。

说明:
1 ≤ m ≤ n ≤ 链表长度

思路

回文链表

题目:判断一个链表是否为回文链表。
思路一
类似括号匹配,使用栈的思想,将链表前半段转化为栈。若当前节点的值或者下一个节点的值和栈顶元素相同,则开始匹配。若成功配对则出栈。最后栈空则是回文链表。
注意在配对过程未结束而栈已空则直接返回false。

思路二
建立一个新的反转的链表,然后逐节点进行匹配。时间和空间复杂均为O(n)

思路三
若企图将空间复杂度降至O(1),则只需要将链表后半段进行反转。
确定中点的方法可以使用快慢指针。

两数相加

题目:给出两个非空的链表用来表示两个非负的整数。其中,它们各自的位数是按照逆序的方式存储的,并且它们的每个节点只能存储 一位 数字。将这两个数相加起来,返回一个新的链表来表示它们的和。

思路
从头到尾扫描记录该位下的进位。注意要分类讨论:两个链表相加,只剩一个链表。注意判断最高位是否有进位。
:官方题解中,可认为长度短的链表的后面有若干个0,即不用分类。

用两个栈实现队列

题目:实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

思路
确定插入栈和弹出栈。
插入栈:插入操作直接push入栈顶
弹出栈:弹出操作需要分类讨论:

  • 弹出栈非空,直接弹出栈顶元素
  • 若插入栈非空,倒入弹出栈中,再弹出栈顶元素
  • 否则返回-1

时间复杂度分析:删除操作,每个元素只会至多被插入和弹出 stack2 一次,因此均摊下来每个元素被删除的时间复杂度仍为 O(1)。

包含min函数的栈、栈的最小值

题目:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 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) 盘子只能叠在比它大的盘子上

思路
分治递归法。

  • 递归基:当只有一个盘子,从A移动到C
  • 借助C将n-1个规模得盘子移动到B
  • 将A中得盘子移动到C
  • 借助A将B中的 n-1个盘子移动到C

其他

四数之和

题目:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意
答案中不可以包含重复的四元组。

思路:双指针法

  • 首先按照升序排序。
  • 双重循环枚举前两个数,下标分别为 i, j。将左指针 lp 设为 j + 1,右指针设为数组的最后一个元素,即 size - 1。每次计算四个数的和:
    • 如果大于目标值,则右指针左移
    • 小于目标值,左指针右移
    • 如果等于,将该序列加入结果数组。且将左指针右移,右指针左移至不同的数
    • 左指针小于右指针则循环

颜色分类

题目:给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

注意:
不能使用代码库中的排序函数来解决这道题。

思路一:暴力求解
遍历数组分别得到三种颜色的元素数量,再按照顺序放回数组即可。

思路二:双指针(红指针p1在头,蓝指针p2在尾)
遍历数组,当遍历位置大于p2时停止。

  • 找到0则和红指针指向的元素交换,p1右移。
  • 找到2则和蓝指针指向的元素交换,p2左移。

注意找到2时要确保交换过来的元素不能为2。

字符串的排列

题目:输入一个字符串,打印出该字符串中字符的所有排列。你可以以任意顺序返回这个字符串数组,但里面不能有重复元素

思路:回溯法(交换分割)

递归基:当递推深度等于字符串大小时,将该字符串记录,停止递推并返回。

从当前深度开始遍历所有可选的字符:

  • 选择一个字符和当前字符交换,即固定当前位置一个字符,分割出其余仍可选择的字符。
  • 递推至下一个深度
  • 撤销选择:将选择的字符再次交换

和为s的连续子序列

题目:输入一个正整数 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)(p2p1+1)

从最小区间开始搜索,即p1 = 1, p2 = 2。并且比较 sum 和 target

  • 等于,将该序列记录,左指针右移
  • 小于,右指针右移
  • 大于,左指针右移

时间复杂度O(target)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值