一.链表
1.单链表逆置
解题思路:
使用递归函数,一直递归到链表的最后一个结点,该结点就是反转后的头结点,记作 ret
此后,每次函数在返回的过程中,让当前结点的下一个结点的 next 指针指向当前节点。
同时让当前结点的 next 指针指向 NULL ,从而实现从链表尾部开始的局部反转
当递归函数全部出栈后,链表反转完成。
递归实现版:
非递归实现版本解题思路:
定义两个指针: pre 和 cur ;pre 在前 cur 在后。
每次让 pre 的 next 指向 cur ,实现一次局部反转
局部反转完成之后, pre 和 cur 同时往前移动一个位置
循环上述过程,直至 pre 到达链表尾部
在这里插入图片描述
2.k个一组反转链表递归
将给出的链表中的节点每k 个一组翻转,返回翻转后的链表 如果链表中的节点数不是k 的倍数,将最后剩下的节点保持原样 你不能更改节点中的值,只能更改节点本身。 要求空间复杂度 O(1) 例如: 给定的链表是1->2->3->4->5 对于 k=2, 你应该返回 2→1→4→3→5 对于 k=3, 你应该返回 3→2→1→4→5
3.k个一组反转单链表非递归
增加一个新的头节点,先计算链表长度len,那么通过len/k次反转可以达到目的
在每次反转过程中,pre指针指向前一个节点,tmp来保存当前cur节点的下一个节点。
将cur断链,拆除以后用头插法插入pre的后面。
4.链表partition
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之 前。你应当保留两个分区中每个节点的初始相对位置。 输入: head = 1->4->3->2->5->2, x = 3 输出: 1->2->2->4->3->5
第一步:将链表拆分成两部分链表,一部分是等于X的节点,另外一部分是大于等于x的节点。
第二步:拼装链表
5.重排链表
给定一个单链表 L:L0→L1→…→Ln-1→Ln , 将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→… 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
给定链表 1->2->3->4, 重新排列为 1->4->2->3.
给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.
这个题目可以拆分成三个子问题
第一步:找到整个链表的中间节点,用fast指针指向
第二步:将后半部分链表进行链表的反转。
第三步:将两个链表合并
6.奇偶链表
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的 是节点编号的奇偶性,而不是节点的值的奇偶性。 请尝试使用原地算法完成。
你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为 节点总数。
输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
用双指针分别遍历奇数链表和偶数链表。
边界条件用fast和fast->next判断
在判断过程中,断链,重新排列。
7.删除链表倒数N个节点
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
求得链表的长度,那么删除倒数第N个,就是删除倒数第len%n个节点
快慢指针遍历,快指针先向后走N个节点
如果走完了以后快指针为nullptr的话,那么要删除的就是第一个节点 返回head->next
否则快慢指针一起向后走,知道快指针到达最后一个节点
slow指针指向了待删除指针的前驱节点,可以直接删除
8.链表指定区间内反转
将一个链表m位置到n位置之间的区间反转,要求时间复杂度 O(N) 空间复杂度 O(1)
给出的链表为1→2→3→4→5→NULL
返回1→4→3→2→5→NULL
注意: 给出的 满足以下条件: 链表长度1≤m≤n≤链表长度
9.判断回文链表(两种做法)
请判断一个链表是否为回文链表。
输入: 1->2 输出: false
输入: 1->2->2->1 输出: true
找到链表的中间节点。
反转链表后半部分。
逐个对比前半部分和后半部分,判断是否相等,如果全部相等就是回文链表
做法二:利用双端队列
将所有链表节点装入双端队列当中
对比双端队列的前后元素,然后分别pop
10.删除链表中重复元素(只保留出现一次的)
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
示例 1: 输入: 1->2->3->3->4->4->5
输出: 1->2->5
示例 2:
输入: 1->1->1->2->3
输出: 2->3
如果节点为nullptr,或者节点的下一个为nullptr,那么直接返回这个节点
如果节点当前元素和下一个元素不相等,那么递归去判断head->next
到这一步,说明当前元素和下一个元素相等,那么跳过所有相等的元素,再去递归判断跳过节点的
下一个节点
为了考虑特殊情况,比如1 1 2 3 这种情况,第一个节点会被删除,所以引入一个哑节点node。 用cur来遍历整个链表,last表示处理好的链表的最后一个位置。
如果cur节点和下一个节点值不相等,那么cur和last节点一同向后走。
如果cur节点和下一个节点值相等,那么跳过所有的相等元素,last->next为cur->next节点作删除。
11.约瑟夫环问题
据说著名犹太历史学家 Josephus 有过以下故事:在罗马人占领乔塔帕特后,39 个犹太人与 Josephus 及他的朋友躲到一个洞中,39 个犹太人决定宁愿死也不要被敌人抓到,于是决定了一种自杀 方式,41 个人排成一个圆圈,由第 1 个人开始报数,报数到 3 的人就自杀,然后再由下一个人重新报 1,报数到 3 的人再自杀,这样依次下去,直到剩下最后一个人时,那个人可以自由选择自己的命运。这 就是著名的约瑟夫问题。现在请用单向环形链表得出最终存活的人的编号。 n 表示环形链表的长度, m 表示每次报数到 m 就自杀。
解:
如果只求最后一个报数胜利者的话,我们可以用数学归纳法解决该问题,为了讨 论方便,先把问
题稍微改变一下,并不影响原意:
问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人 继续从0开
始报数。求胜利者的编号。
我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新 的约瑟夫
环(以编号为k=m%n的人开始):
k k+1 k+2 … n-2, n-1, 0, 1, 2, … k-2并且从k开始报0。
现在我们把他们的编号做一下转换:
k --> 0
k+1 --> 1
k+2 --> 2
…
…
k-2 --> n-2
k-1 --> n-1
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解: 例如x
是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情 况的解吗?!!变回去
的公式很简单,相信大家都可以推出来:x’=(x+k)%n。
令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]。
递推公式
f[1]=0;
f[i]=(f[i-1]+m)%i; (i>1)
有了这个公式,我们要做的就是从1-n顺序算出f[i]的数值,最后结果是f[n]。 因为实际
生活中编号总是从1开始,我们输出f[n]+1。
12.旋转链表
给定一个链表头节点head。和一个整数K,将链表左旋转K个节点返回操作结果。
输入:1->2->3->4->5 K = 3
输出:4->5->1->2->3
求出链表的长度len,然后计算出来正真需要旋转的k:k%len 如果k是0的话,那么直接返回链表,
遍历途中用一个节点pos保存节点的最后一个元素位置。
然后将需要旋转的k个元素作cut操作,切割下来。
用result保存cut->next位置,这是旋转后需要返回的头节点。
cut->next指向nullptr,断开链。最后一个节点pos->next = head;链接起来
13.二叉树中的链表
给你一棵以 root 为根的二叉树和一个 head 为第一个节点的链表。
如果在二叉树中,存在一条一直向下的路径,且每个点的数值恰好一一对应以 head 为首的链表中每个节 点的值,那么请你返回 True ,否则返回 False 。 一直向下的路径的意思是:从树中某个节点开始,一直连续向下的路径。
深度优先遍历搜索二叉树
dfs用来判断树节点node开始,和链表节点head的匹配结果。
那么dfs判断了当前节点如果不是的话,递归去判断root节点的左右子树。
14.对链表进行插入排序
对一条链表进行排序算法,要求使用算法为插入排序,且时间复杂度符合O(n^2)
算法:
1.判断链表是否为空,为空直接返回
2.新建排序链表头和尾都指向head: sorted_head=head;sorted_tail=head;
3.从链表第二位开始插入排序(curr=head->next)先保存curr-next,next=curr->next
(1)判断curr->val是否小于排序链表头部值sorted->head->val;
如果小于,更新排序链表头部curr->next = sorted->head; sorted->head = curr;
(2)否则判断curr->val是否大于等于排序链表尾部值sorted->tail->val;
如果大于等于,更新排序链表尾部sorted->tail->next = curr; sorted->tail = curr;
(3)否则,落入排序链表中间:
找到排序链表位置p,使得p->val<=curr->valnext->val;将curr插入到p和p->next中间
curr->next = p->next; p->next = curr;
更新curr,curr=next
4.链表问题记得断尾求生
sorted_tail->next = nullptr;
5.返回排序链表
return sorted_head;
15.归并排序链表
对链表进行归并排序,且时间复杂度符合O(nlogn).
首先需要对链表进行cut操作。如果传入的cut值不同,函数cut可以切割不同长度的链表出来。
merge是典型的归并两个有序链表的过程。
在归并排序过程中,cut切割值以指数形式增长,每次乘以二,然后在一次cut当中,通过两两合并
的方式将链表归并。
从 1 开始切割,每次以指数形式切割,切割两两归并
16.复杂链表的复制
请实现 copyRandomList 函数,复制一个复杂链表。
在复杂链表中,每个节点除了有一个 next 指 针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
-10000 <= Node.val <= 10000
Node.random 为空(null)或指向链表中的节点。
节点数目不超过 1000 。
遍历链表,将链表的每一个节点都进行复制,挂在当前节点的下一个位置。
第二步,复制随机random指针,遍历新生成的大链表,后面的node’节点的random指针应该指向
前面node节点的random的next节点。
随机指针复制完毕以后,将两条链重新分开。
比如有这样一条复杂链表。那么过程如图所示。