目录
牛客网面试必刷top101习题笔记——链表部分
一、反转链表
1.题目描述
2.暴力解法
(1).解题思路
(2).代码实现
3、使用栈
(1).解题思路
(2).代码实现
4、递归
(1).解题思路
(2).代码实现
给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1,长度为n,反转该链表后,返回新链表的表头。
数据范围:0≤n≤1000
要求:空间复杂度O(1) ,时间复杂度O(n) 。
如当输入链表{1,2,3}时,
经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。
以上转换过程如下图所示:
java语言下Node的表示方法:
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
从头结点开始对链表的每一个结点依次进行处理,将当前所处理结点currentNode的指针域指向其前驱结点preNode。同时由于改变currentNode的指针域会导致其丢失后继结点地址,因而需要在处理过程中创建中介结点tempNode对currentNode的后继结点进行记录。
具体流程如下:
1.明确之前提到的各个变量的内容,将头结点作为currentNode,记录相应的preNode和tempNode的位置。
2.将currentNode的next指向preNode,并相应地后移变量所指向的结点,令preNode=currentNode,并令currentNode=tempNode。
3.以此类推,处理至最后一个结点,最终状态如下。
public class Solution {
public ListNode ReverseList(ListNode head) {
ListNode preNode = null; //当前所处理结点的前驱结点
ListNode currentNode = head; //当前所处理结点
while(currentNode!=null) //判定条件:当前节点不为null,避免空链表
{
//改变当前节点指针域指向对象
ListNode tempNode = currentNode.next;
currentNode.next = preNode;
//继续处理下一个结点
preNode = currentNode;
currentNode = tempNode;
};
//该循环结束判定条件下,结束时preNode即为倒转之后链表的头结点
return preNode;
}
}
运行时间23ms,占用内存10276KB。
利用栈后入先出的特性,将整个链表的结点从头压入栈中,再依次取出,拼接为新的链表。
import java.util.Stack;
public class Solution {
public ListNode ReverseList(ListNode head) {
Stack stack = new Stack<>();
//将链表中的结点依次压入栈中
while(head!=null)
{
stack.push(head);
head = head.next; //后移指针
}
if(stack.isEmpty()) //判定空链表
return null;
//确定新链表的头结点
ListNode currentNode = stack.pop();
ListNode headNode = currentNode;
//栈中结点依次出栈,并且将结点依次连接
while(!stack.isEmpty())
{
currentNode.next = stack.pop();
currentNode = currentNode.next;
}
currentNode.next = null; //将尾结点指针域指向null,避免构成环
return headNode;
}
}
运行时间31ms,占用内存10144KB。
使用递归函数的方法在每一层调用中对相应结点的指针域进行处理,将当前结点head的后继结点postNode的指针域指向head,设定递归终止条件为当前处理结点为尾结点,即可把尾结点作为新链表的头结点在递归调用中不断向上传递。
设该链表长度为n,从第n次递归调用开始反推流程。
具体流程如下:
1.第n次调用时,当前节点为尾结点,触发终止条件,将当前处理结点作为新链表头结点直接向上一层传递。
2.第n-1次调用时,由于递归向上传递,当前处理结点head前移。将postNode的指针域指向当前处理结点head,同时将head的指针域置空,避免形成环。
3.第n-2次调用时,重复第n-1次调用的工作,改变postNode的指针域指向当前结点head,并置空head指针域。继续向上传递新链表头结点newHead,以此类推。
4.重复上述步骤直至递归至第一次函数调用,改变postNode的指针域使其指向当前节点head,置空当前结点head的指针域,所得新链表如下所示。
public class Solution {
public ListNode ReverseList(ListNode head) {
//确定链表为空或者当前处理结点为尾结点
if(head == null || head.next == null)
return head;
ListNode postNode = head.next; //处理当前结点的后继结点
ListNode newHead = ReverseList(postNode); //递归返回尾结点,即新链表的头结点
postNode.next = head; //将后继结点指针域指向当前结点
head.next = null; //当前结点指针域置空,避免成环
return newHead;
}
}
运行时间23ms,占用内存10284KB。
将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n),空间复杂度 O(1)。
例如:
给出的链表为 1→2→3→4→5→NULL, m=2,n=4,
返回 1→4→3→2→5→NULL.
数据范围: 链表长度 0 要求:时间复杂度 O(n) ,空间复杂度 O(n) 进阶:时间复杂度 O(n),空间复杂度 O(1) 整体思路为将m位置到n位置之间的结点作为子链表一齐取出,将其顺序反转后拼接回整个链表中。 以题例为例,整体步骤如下: 1.切断m位置结点与其前驱结点的联系,同时切断n位置结点与其后继结点的联系,将m位置结点的原前驱结点与n位置结点相连,同时将m位置结点与n位置结点的原后置结点相连。 2.通过遍历法将m位置到n位置的结点反转,最后连接m结点与n结点原后继结点。 运行时间22ms,占用内存10020KB。 将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表 数据范围:0≤n≤2000 , 1≤k≤2000 ,链表中每个元素都满足 0≤val≤1000 例如: 给定的链表是 1→2→3→4→5 对于k=2 , 你应该返回 2→1→4→3→5 对于k=3 , 你应该返回 3→2→1→4→5 以k=3为例,处理一个n=8的链表。 最终所得到的链表结构如下,将每k个结点划为一个区间,则区间最左侧端点与下一区间最右侧端点相连,当剩余结点不足k时则会直接与最右侧端点的原后继结点相连。除此之外均在区间内部进行反转处理即可。 花费时间33ms,占用内存10272KB。2. 遍历解法
(1).解题思路
(2).代码实现
import java.util.*;
public class Solution {
public ListNode reverseBetween (ListNode head, int m, int n) {
//分别找到m位置和n位置的结点,并记录其前驱与后继结点。
ListNode mNode = head;
ListNode premNode = null;
ListNode nNode = null;
ListNode postnNode = null;
//找m位置结点,同时记录m位置结点的前驱结点
for(int i = 0; i < m - 1; i++){
premNode = mNode;
mNode = mNode.next;
}
nNode = mNode;
//找n位置结点,同时令
for(int i = 0; i < n - m; i ++){
nNode = nNode.next;
}
//开始区间内链表的遍历式反转。
//连接m位置结点的原前驱结点与n位置结点。
if(m!=1)
premNode.next = nNode;
ListNode postNode = null;
ListNode preNode = mNode;
ListNode currentNode = mNode.next;
//连接m位置结点与n位置的原后继结点。
mNode.next = nNode.next;
//开始反转。
for(int i = 0 ; i < n - m ; i++){
ListNode tempNode = currentNode;
currentNode = currentNode.next;
tempNode.next = preNode;
preNode = tempNode;
}
//特殊情况,m为1时区间左端结点无前驱结点。
if(m==1)
head = nNode;
return head;
}
}
第三题、链表中的结点每k个一组翻转
1.题目描述
描述
如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样
你不能更改节点中的值,只能更改节点本身。
要求空间复杂度 O(1),时间复杂度 O(n)2.暴力解法
(1).解题思路
(2).代码实现
import java.util.*;
public class Solution {
public ListNode reverseKGroup (ListNode head, int k) {
ListNode leftNode = head;
ListNode rightNode = null;
ListNode currentNode = leftNode;
//前一区间的左端点结点需要与后一区间的右端点结点相连
ListNode preLeftNode = null;
//先测n
ListNode countNode = head;
int n = 0;
while(countNode!=null){
countNode = countNode.next;
n++;
}
int epoches = n / k ;
for (int i = 0; i < epoches;i++){
//区间内部操作
// currentNode = leftNode;
ListNode preNode = null;
for(int j = 0; j < k;j++){
ListNode tempNode = currentNode.next;
currentNode.next = preNode;
preNode = currentNode;
currentNode = tempNode;
}
//更新端点结点信息
rightNode = preNode;
if(preLeftNode != null)
preLeftNode.next = rightNode;
preLeftNode = leftNode;
leftNode = currentNode;
if(i == 0)
head = rightNode;
}
if (leftNode != null && preLeftNode!=null)
preLeftNode.next = leftNode;
return head;
}
}