目录
1、LeetCode - 反转单链表
题目描述:
- 给你单链表的头节点
head
,请你反转链表,并返回反转后的链表。
题目示例:
- 示例一:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
- 示例二:
输入:head = []
输出:[]
✨解题思路:
- 定义三个引用变量。
- prev:待移动结点的前一个结点的引用,初始值为null
- cur :待移动结点的引用,一开始执行head
- curNext:待移动结点的下一个结点的引用
- 反转链表后,head指向的结点就是最后一个结点,所以cur一开始指向head,是将head的next域赋值为null。prev指向的结点就是头结点。
🌊代码示例:
public ListNode reverList(){
if(this.head==null){
return null;
}
ListNode cur = this.head;
ListNode prev = null;
while (cur!=null){
ListNode curNext = cur.next;
cur.next = prev;
prev =cur;
cur = curNext;
}
return prev;
}
}
- 如果在IDEA 中进行测试,要写一个打印链表的方法,将链表新的头结点(prev所指向的结点)传入。
2、LeetCode - 链表的中间结点
题目描述:
- 给定一个头结点为
head
的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
题目示例:
- 示例一:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
- 示例二:
输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
✨解题思路:
- 定义两个引用指向head。
- fast:遍历链表时,一次走两步。
- slow:遍历链表时,一次走一步。
- fast 走的速是 slow 的两倍,路程也是两倍。当fast 走到结尾了,那么slow就走到了链表的中间位置。
- 先判断链表是否为空链表,如果是空链表就返回null。
- 分链表中结点的个数是奇数还是偶数,如果是奇数当fast.next为null是slow所指向的结点为中间结点。如果是偶数,当fast为null时slow所指向的结点为中间结点。
🌊代码示例:
public ListNode middleNod (){
if(head == null){
return null;
}
ListNode fast = head;
ListNode slow = head;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
3、牛客网 - 链表中倒数第k个结点
题目描述:
- 输入一个链表,输出该链表中倒数第k个结点。
题目示例:
输入:1,{1,2,3,4,5}
返回值:{5}
✨解题思路:
- fast:遍历链表时,先走。
- slow:遍历链表时,后走。
- 先让 fast 走 k-1 步,然后fast 与 slow 一起走,每次走一步,当fast走到最后一个结点时,slow所指向的结点就是要查找的结点。
- 如果k小于等于0、链表为空、k大于链表的长度,这些情况下要返回null。
🌊代码示例 :
public ListNode FindKthToTail(int k){
if(k<=0 || head==null){
return null;
}
ListNode fast = head;
ListNode slow = head;
while(k-1!=0){
fast = fast.next;
if(fast == null){
return null;
}
k--;
}
while(fast.next!=null){
fast = fast.next;
slow = slow.next;
}
return slow;
}
4、牛客网 - 合并两个有序链表
题目描述:
- 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
题目示例:
- 示例一:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
- 示例二:
输入:l1 = [], l2 = []
输出:[]
✨解题思路:
- 创建一个临时结点,将两个链表中的数据进行比较,使临时结点指向较小的结点,遍历两个链表,如果其中一个结点的数据小于另外一个结点的数据,就将上一个结点指向该结点,直到其中一个链表遍历结束。
- 当遍历完某一个链表后,还要进行判断,如果另外一个链表没有遍历完,就要继续遍历下去,直到链表遍历结束。
🌊代码示例 :
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if(list1==null && list2==null){
return null;
}
ListNode newHead= new ListNode(-1);
ListNode tmp = newHead;
while(list1!=null && list2!=null){
if(list1.value <= list2.value){
tmp.next = list1;
list1 = list1.next;
tmp = tmp.next;
}else{
tmp.next = list2;
list2 = list2.next;
tmp = tmp.next;
}
}
if(list1!=null){
tmp.next = list1;
}
if(list2!=null){
tmp.next = list2;
}
return newHead.next;
}
5、牛客网 - 链表分割
题目描述:
- 现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
✨解题思路:
- 定义两个区间,第一个区间存放比x小的结点,第二个区间存放比x大的结点。最终连接两个区间的结点。
- 第一个区间:第一个结点定义为bs(before start)最后一个结点定义为be(before end)
- 第二个区间:第一个结点定义为as(after start)最后一个结点定义为ae(after end)
- bs、be、as、ae 初始值为null。
- 定义引用 cur 遍历链表,将链表中每个数据与x进行比较,小于x的放到第一个区间,大于x的放到第二个区间。当 cur=null 时链表遍历结束。
如果是第一次区分:
- 小于x的结点:bs =null。将cur指向的结点赋给bs与be
- 大于x的结点:as =null。将cur指向的结点赋给as与ae
如果不是第一次区分:
- be就指向cur当前指向的结点,be向后移动,直到将小于x的结点区分完
- ae就指向cur当前指向的结点,ae向后移动,直到将大于x的结点区分完
- 如果链表中的数据都大于x,那么bs = null;直接返回大于x的区间,返回as。
- 如果原链表中最后一个结点的数据小于x,那么ae就会指向链表中间的某一个结点。
- 这样新组成的链表最后一个结点的next域就不为空,所以要将ae的next域置为空:as != null,ae.next = null。
🌊代码示例 :
//以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前
public ListNode partition(int x) {
// write code here
ListNode bs = null;
ListNode be = null;
ListNode as = null;
ListNode ae = null;
ListNode cur = head;
while (cur!=null) {
if(cur.value <x){
//第一次
if(bs==null){
bs = cur;
be = cur;
//不是第一次,采用尾插
}else{
be.next = cur;
be = be.next;
}
}else{
//第一次
if(as == null){
as = cur;
ae = cur;
//不是第一次,采用尾插
}else {
ae.next = cur;
ae = ae.next;
}
}
cur = cur.next;
}
//如果所有的数据都大于x,返回大于x的区间
if(bs==null){
return as;
}
//连接两段区间
be.next = as;
//如果原链表最后一个结点的next域不为空,要将其置为null
if(as!=null){
ae.next = null;
}
return bs;
}
}
6、牛客网 - 删除链表中重复的结点
题目描述:
- 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表 1->2->3->3->4->4->5 处理后为 1->2->5
- 数据范围:链表长度满足 0 ≤ n ≤1000,链表中的值满足 1 ≤ va l≤ 1000
- 进阶:空间复杂度 O(n) ,时间复杂度 O(n)
- 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。
题目示例:
- 示例一:
输入:{1,2,3,3,4,4,5}
返回值:{1,2,5}
- 示例二:
输入:{1,1,1,8}
返回值:{8}
✨解题思路:
- 因为链表是已经排好序的,所以重复的结点肯定是紧挨在一起的。
- 创建一个临时结点,使临时结点(newHead)指向链表。
- 遍历链表,对链表中相邻两个结点是数据进行比较(cur.value 与 cur.next.value进行比较)。因为要比较cur 与 cur.next,当比较到最后一个结点时cur.next 会造成空指针异常,所以在比较两个结点时要对cur.next进行判断,只需要使cur走到倒数第二个结点即可(cur.next != null)。
- 如果相邻的两个结点相同,那么继续遍历链表(可能存在多个相同的结点,所以使用循环来往下遍历链表中相同的结点)。
- 如果相邻的两个结点不相同,使指向临时结点的引用tmp遍历链表。tmp最终所指向的结点就是链表中不重复的最后一个结点,所以要将tmp最终所指向的结点的next域置为null。
- 最终返回临时结点的下一个结点(newHead.next)。
🌊代码示例 :
//删除链表中重复的结点
public ListNode deleteDuplication() {
ListNode cur = head;
//创建一个临时结点,没有实际意义,所以赋值为-1
ListNode newHead = new ListNode(-1);
ListNode tmp = newHead;
while (cur!=null){
//相邻结点之间的数据相同
if(cur.next!=null && cur.value == cur.next.value){
while (cur.next!=null && cur.value == cur.next.value){
cur = cur.next;
}
//将相同结点遍历完后,cur 还要往后走一步
cur = cur.next;
//相邻结点之间的数据不相同
}else {
tmp.next = cur;
tmp = tmp.next;
cur = cur.next;
}
}
//如果最终tmp所指结点的next域不是null,就要赋值为null
tmp.next = null;
return newHead.next;
}
7、牛客网 - 链表的回文结构
题目描述:
- 对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。
- 给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
题目示例:
1->2->2->1
返回:true
✨解题思路:
- 找中间结点,使用快慢指针(fast,slow)
- 逆置中间结点之后的结点,slow指向逆置后的第一个结点。
判断是否是回文:
- 判断head指向的结点数据与slow指向的结点数据是否相同。当head与slow指向同一个结点时,表示链表遍历结束。
- 如果数据不同:则该链表不是回文链表,返回false。
- 如果数据相同:head从前往后走,slow从后往前走。
- 结点个数是奇数:返回true。
- 结点个数是偶数:head的next域与slow指向的结点相同时,返回true。head.next = slow;
🌊代码示例 :
//判断是否是回文链表
public boolean chkPalindrome() {
// write code here
if(head == null) return false;
ListNode fast = head;
ListNode slow = head;
//找中间结点
while (fast!=null && fast.next!=null){
fast =fast.next.next;
slow = slow.next;
}
//链表逆置
ListNode cur = slow.next;
while (cur!=null){
ListNode curNext = cur.next;
cur.next = slow;
slow = cur;
cur = curNext;
}
//判断回文
while (head!=slow){
if(head.val != slow.val){
return false;
}
if(head.next == slow){
return true;
}
head = head.next;
slow = slow.next;
}
return true;
}
8、LeetCode - 相交链表
题目描述:
- 给你两个单链表的头节点
headA
和headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回null
。
题目示例:
- 示例一:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
- 示例二:
输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
- 示例三:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
✨解题思路:
- 两个链表相交的形状是Y型,两个链表中的next域相交。
- 遍历两个链表求出两个链表的长度,计算两个链表的差值步(未到相交结点前两个链表遍历步数的差值),先让最长链表的引用走两个链表的差值步,再让两个链表的引用同时走,直到两个引用指向同一个结点,表示这两个链表相交,返回相交的结点。
- 当两个链表不相交时,遍历结束后两个引用都为null,此时两个引用的值相同,所以在遍历链表时,如果求中某一个引用的值为null,则表示两条链表不相交,返回null。
🌊代码示例 :
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA==null || headB==null){
return null;
}
ListNode pl = headA;
ListNode ps = headB;
int lenA = 0;
int lenB = 0;
while(pl!=null){
lenA++;
pl = pl.next;
}
pl = headA;
while(ps!=null){
lenB++;
ps = ps.next;
}
ps = headB;
//求差值
int len = lenA - lenB;
if(len<0){
pl = headB;
ps = headA;
len = lenB - lenA;
}
//pl指向了最长的链表,ps指向了最短的链表,len为差值步
//让pl先走len步
while(len!=0){
pl = pl.next;
len--;
}
//pl 与 ps 同时走,当pl与pl相遇就结束
while(pl!=ps){
pl = pl.next;
ps = ps.next;
}
return pl;
}
9、LeetCode - 环形链表
题目描述:
- 给你一个链表的头节点 head ,判断链表中是否有环。
- 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
- 如果链表中存在环 ,则返回 true 。 否则,返回 false 。
题目示例:
- 示例一:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
- 示例二:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
- 示例三:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
✨解题思路:
- 使用快慢指针,fast:一次走两步,slow:一次走一步。当两个引用指向了同一结点时,则链表中有环,返回true。
- 因为 fast 比 slow 走的快,如果fast = null 或 fast.next = null 则表示链表没有环,false。
🌊代码示例 :
//判断链表是否有环
public boolean hasCycle(ListNode head) {
if(head == null) return false;
ListNode fast = head;
ListNode slow = head;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
return true;
}
}
return false;
}
10、LeetCode - 环形链表II
题目描述:
- 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
- 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
- 不允许修改 链表。
题目示例:
- 示例一:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
- 示例二:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
- 示例三:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
✨解题思路:
- 先判断链表中是否有环,使用快慢指针走到相遇的结点。
- 如果有环:一个引用从相遇的结点开始,一个引用从头开始,两个引用都是一次走一步,最后相遇的地方就是入环的第一个结点,返回该结点。
- 如果没有环:则返回null。
🌊代码示例:
public ListNode detectCycle(ListNode head) {
if(head == null) return null;
ListNode fast = head;
ListNode slow = head;
while(fast!=null && fast.next!= null){
fast = fast.next.next;
slow =slow.next;
if(fast == slow){
break;
}
}
if(fast ==null || fast.next==null){
return null;
}
fast = head;
while(fast!=slow){
fast = fast.next;
slow = slow.next;
}
return fast;
}