链表
Java中的链表形式
public class ListNode {
int val; // 模拟链表中具体的值
ListNode next; // 模拟链表中的下一个结点
ListNode(int x) { val = x; } // 构造方法,用于修改结点的值
}
注意2022/6/3
注意链表的起始节点保存,要拼接链表的话,对前一个节点的保存;
删除链表中的节点
需求
请编写一个函数,用于 删除单链表中某个特定节点 。在设计函数时需要注意,你无法访问链表的头节点 head ,只能直接访问 要被删除的节点 。
题目数据保证需要删除的节点 不是末尾节点 。
示例
输入:head = [4,5,1,9], node = 5 输出:[4,1,9]
输入:head = [4,5,1,9], node = 1 输出:[4,5,9]
方法1:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
}
时间复杂度:O(1)
空间复杂度:O(1)
备注
2022/6/2 |
---|
想不到这种方式,确实会想为什么没有传入head 确实有在想遍历找到node再去删除 |
删除链表的倒数第N个节点
需求
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例
输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
输入:head = [1], n = 1 输出:[]
输入:head = [1,2], n = 1 输出:[1]
ListNode
public class ListNode {
int val; // 结点的值
ListNode next; // 结点的中存储的下一个元素的指针
ListNode() {} // 无参构造方法
ListNode(int val) { this.val = val; } // 给结点设置值
ListNode(int val, ListNode next) { this.val = val; this.next = next; } // 给结点设置值和指针
}
方法1:非递归求解
public ListNode removeNthFromEnd(ListNode head, int n) {
// 定义要删除节点的前一个
ListNode pre = head;
// 找到要删除节点前所有节点的个数
int len = getLength(head) - n ;
// 获取删除节点前一个节点
for(int i = 0 ; i < len-1 ; i ++) {
pre = pre.next;
}
pre.next = pre.next.next;
return head;
}
// 定义方法计算链表的长度
public int getLength(ListNode head){
int count = 1; // 统计链表的长度
while(head.next != null){
head = head.next;
}
return count;
}
获取删除节点的前一个节点for循环的条件判断语句len-1
方法2:递归求解
public ListNode removeNthFromEnd(ListNode head, int n) {
// 得到 递归结束时的长度
int pos = getLength(head,n);
// 如果pos等于n,表示删除的节点是head
if(pos == n)
return head.next;
return head;
}
// 递归
public int getLength(ListNode node, int n){
// 递归结束条件
if(node == null)
return 0;
int pos = getLength(node.next,n) + 1;
if(pos == n+1)
node.next = node.next.next;
return node;
}
图示:
方法3:双指针
public ListNode removeNthFromEnd(ListNode head, int n) {
// 定义两个指针
ListNode fast = head;
ListNode slow = head;
// fast先向后移动n个位置
for(int i = 0 ; i < n ; i ++)
fast = fast.next;
// 临界值判断,如果fast是null 说明要删除的是head节点
if(fast == null)
return head.next;
// 两个指针都向后移动
while(fast.next != null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return head;
}
图示:
反转链表
需求
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例
输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
输入:head = [1,2] 输出:[2,1]
输入:head = [] 输出:[]
方法1:使用栈的形式
链表的反转,常考题目;
使用栈,因为栈是先进后出的;
实现原理是把链表节点一个个入栈,当全部入栈完之后再一个个出栈,出栈的时候再把出栈的节点串成一个新的链表;
public ListNode reverseList(ListNode head) {
// 定义堆栈
Stack<ListNode> stack = new Stack<ListNode>();
// 入栈
while(head != null){
stack.push(head);
head = head.next;
}
// 临界值判断
if(stack.empty()
return null;
// 出栈
ListNode node = stack.pop(); // 链表的最后一个节点
ListNode res_list = node; // 链表的第一个节点
while( !stack.empty()){
// 新取出来的节点
ListNode tempNode = stack.pop();
// 赋值给链表的最后一个节点的下一个
node.next = tempNode;
// 重新找链表的最后一个节点
node = node.next;
}
// 给node节点的下一个节点赋值为null
node.next = null;
return res_list;
}
图示:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
// 使用栈的形式
Stack<ListNode> stack = new Stack<ListNode>();
while(head != null){
stack.push(head);
head = head.next;
}
// 链表的第一个节点
head = null;
// 栈出栈的元素
ListNode node = new ListNode();
// 出栈元素所要插入位置的表头节点
ListNode temp = new ListNode();
while(!stack.empty()){
node = stack.pop();
if(head == null){
head = node;
temp = node;
}else{
temp.next = node;
temp = node;
node.next = null;
}
}
return head;
}
}
方法2:双链表求解
思路:
每次从旧链表上摘下一个节点,那么就把节点放在新链表的头部;
注意保留旧链表头节点的下一个节点地址;
public ListNode reverseList(ListNode head) {
// 定义新的链表
ListNode head_new = null;
// 定义存储旧链表下一个位置的节点
ListNode head1 = null;
// 遍历旧链表
while(head != null){
// 保存旧链表下一次要访问的首节点
head1 = head.next;
// 修改head的下一个节点位置
head.next = head_new;
// 修改新节点的首节点
head_new = head;
// 给head节点重新赋值
head = head1;
}
// 链表为空判断
if(head_new == null)
return null;
return head_new;
}
图示:
方法3:递归 = 从后往前处理(头递归)
public ListNode reverseList(ListNode head) {
// 递归终止条件
if(head == null || head.next == null)
return head;
// 前者 表示最初的head链表就为空;后者表示 已经找到链表的最后一个元素
// 逻辑处理过程
// 保存当前节点的下一个节点
ListNode next = head.next;
// 递归调用
ListNode reverse = reverseList(next);
// 逻辑处理
next.next = head;
head.next = null;
return reverse;
}
图示:
分析
该递归形式是 向下传递,基本没有逻辑,当往回反弹的时候才开始处理,从链表的尾端往前开始处理;
方法4:递归 = 从前往后处理(尾递归+一次性出栈)
分析:
其中递归的输入是head和newhead,head表示原始链表的首节点,newhead表示新链表的首节点;
尾递归 虽然会不停的压栈,但由于最后返回的是递归函数的值,所以在返回的时候都会一次性出栈,不会一个个出栈这么慢;
public ListNode reverseList(ListNode head) {
return reverseListIte(head,null);
}
public ListNode reverseListIte(ListNode head,ListNode oldhead){
// 递归结束条件
if(head == null)
return oldhead;
// 处理逻辑
ListNode next = head.next; //保存head节点的下一个节点
head.next = oldhead;
// 递归调用
return reverseListIte(next,head);
}
图示:
方法5:递归 = 从前先后处理(尾递归+一个个出栈)
public ListNode reverseList(ListNode head) {
return reverseListIte(head,null);
}
public ListNode reverseListIte(ListNode head,ListNode oldhead){
// 递归结束条件
if(head == null)
return oldhead;
// 逻辑处理
ListNode next = head.next;
head.next = oldhead;
// 递归调用
ListNode node = reverseListIte(next,head);
return node;
}
方式6:双指针 = 双链表迭代
public ListNode reverseList(ListNode head) {
// 临界条件判断
if(head == null)
return head;
// 保存head的下一个节点
ListNode next = head.next;
head.next = null;
// 存储中间元素
ListNode temp = null;
// 循环遍历链表元素
while(next != null){
temp = next.next;
next.next = head;
head = next;
next = temp;
}
return head;
}
图示:
offer06:从尾到头打印链表
需求
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例
输入:head = [1,3,2] 输出:[2,3,1]
方法1:双层for循环
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
// 双层for循环
// 确定链表的长度
int len = 0;
ListNode node = head;
while(node != null){
len ++;
node = node.next;
}
// 定义数组
node = head;
int[] arr = new int[len];
for(int i = len-1; i >= 0;i--){
arr[i] = node.val;
node = node.next;
}
return arr;
}
}
自己写的。2022/7/9 |
---|
时间复杂度:O(N) 两个循环 遍历次数都是N 空间复杂度:O(N) 存储输出元素的数组 |
方法2:递归法(+ArrayList)
分析
先递推至链表的尾部;
回溯时,依次将节点值加入列表,既可实现链表值的倒序输出。
递推终止条件:head == null
递推过程:head = head.next
回溯阶段:节点的head.val加入到列表中。
集合ArrayList存储节点内容(倒叙),再遍历存储到数组中。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
ArrayList<Integer> list = new ArrayList<Integer>();
public int[] reversePrint(ListNode head) {
// 递归法
recur(head);
int[] ret = new int[list.size()];
for(int i = 0 ; i < list.size(); i++){
ret[i] = list.get(i);
}
return ret;
}
public void recur(ListNode node){
if(node == null){
return;
}
recur(node.next);
list.add(node.val);
}
}
自己分析2022/7/9 |
---|
时间复杂度 :O(N) 遍历链表 递归N次 |
空间复杂度:O(N) 系统递归需要使用O(N)的栈空间 |
方法3:辅助栈法
分析
链表只能从前至后的访问每个节点,题目要求倒叙输出,这种先入后出的需求可以借助栈来实现。
栈内存储的是ListNode的val
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
// 将元素存储到栈中
int len = 0;
Stack<Integer> sta = new Stack<Integer>();
while(head != null){
sta.push(head.val);
head = head.next;
len++;
}
// 建立数组 存放倒叙元素
int[] arr = new int[len];
for(int i = 0 ; i < len; i++)
arr[i] = sta.pop();
return arr;
}
}
时间复杂度:入栈和出栈共使用O(N)时间;
空间复杂度:辅助栈stack和数组arr共使用O(N)的额外空间。
栈内存储的元素是ListNode
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
// 辅助栈方法
Stack<ListNode> sta = new Stack<ListNode>();
ListNode temp = head;
while(temp != null){
sta.push(temp);
temp = temp.next;
}
// 定义数组用于存储val内容
int[] arr = new int[sta.size()];
int i = 0 ;
while(!sta.empty()){
arr[i++] = sta.pop().val;
}
return arr;
}
}
方法4:辅助集合
使用ArrayList辅助集合,循环使用一个变量
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
// 使用辅助集合存储ListNode节点
List<ListNode> li = new ArrayList<ListNode>();
ListNode temp = head;
while(temp != null){
li.add(temp);
temp = temp.next;
}
// 创建数组
int[] arr = new int[li.size()];
for(int i = li.size()-1 ; i >= 0 ;i--){
arr[li.size()-i-1] = li.get(i).val;
}
return arr;
}
}
使用ArrayList,循环使用两个变量
public int[] reversePrint(ListNode head) {
List<ListNode> list = new ArrayList<ListNode>();
ListNode temp = head;
while(temp != null){
list.add(temp);
temp = temp.next;
}
int[] array = new int[list.size()];
int j = 0;
for(int i = list.size() -1;i >= 0; i--){
array[j++] = list.get(i).val;
}
return array;
}
方法5:辅助LinkedList
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
// 辅助列表 LinkedList
LinkedList<ListNode> li = new LinkedList<ListNode>();
ListNode temp = head;
while(temp != null){
li.add(temp);
temp = temp.next;
}
// 定义集合长度
int len = li.size();
// 定义数组
int[] arr = new int[len];
// 对于列表li进行了处理 那么li.szie会改变 而不是之前的内容。
for(int i = 0 ; i < len;i++){
arr[i] = li.removeLast().val;
}
return arr;
}
}
方法6:辅助集合+Collections
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
// 辅助集合 Collections.reverse方法
List<ListNode> li = new ArrayList<ListNode>();
while(head != null){
li.add(head);
head = head.next;
}
// 实现集合翻转
Collections.reverse(li);
// 创建数组
int[] arr = new int[li.size()];
for(int i = 0 ; i < arr.length ; i++){
arr[i] = li.get(i).val;
}
return arr;
}
}
offer35 复杂链表的复制
需求
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
示例
random指向前一个元素、指向null、指向第一个元素、指向null之前的元素;
random指向自己;
random指向前一个元素、指向后一个元素、指向null;
给定链表为空,返回null。
方法1:map集合(Node,Node)
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head) {
// 两次使用map
Map<Node,Node> map = new HashMap<Node,Node>();
if(head == null)
return head;
// 保留原始链表的头head
Node cur = head;
// 将链表元素添加到map集合中
while(cur != null){
map.put(cur,new Node(cur.val));
cur = cur.next;
}
// 修改map集合中的value部分 也就是说map中键和值存储内容一样。
cur = head;
while(cur != null){
// cur的value值的next属性 设置为 cur的next属性key 对应的value
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
return map.get(head);
}
}
方法2:递归
未理解
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
// 创建map集合存储节点
Map<Node,Node> map = new HashMap<Node,Node>();
public Node copyRandomList(Node head) {
// 递归形式(尾递归)
// 递归结束条件
// 没有节点的情况
if(head == null) return head;
// 节点已经全部放入map集合中
if(map.containsKey(head)) return map.get(head);
// 逻辑处理
Node node = new Node(head.val);
map.put(head,node);
// 递归调用
node.next = copyRandomList(head.next);
node.random = copyRandomList(head.random);
return node;
}
}
图示