链表的基础知识
单向链表
清空元素 – clear()
public void clear() {
size = 0;
first = null;
}
添加元素 - add(int index, E element)
public void add(int index,E element){
if(index==0){
first=new Node<>(element,first);
}else {
Node <E> prev=node(index-1);
prve.next=new Node<>(element,prev.next);
}
size++;
}
删除元素 - remove(int index)
public E remove(int index) {
/*
* 最好:O(1)
* 最坏:O(n)
* 平均:O(n)
*/
rangeCheck(index);
Node<E> node = first;
if (index == 0) {//当待删除节点是第一个节点的时候
first = first.next;
} else {
//1.先找到待删除节点的前一个节点
Node<E> prev = node(index - 1);
//2.将要删除1的节点的值取出来
node = prev.next;
//3.将前一个节点指向待删除节点的下一个节点
prev.next = node.next;//等价于pre.next=prev.next.next
}
size--;
//4.返回待删除节点的值
return node.element;
}
删除元素 – 注意0位置
返回查找元素的下标
public int indexOf(E element) {
Node<E> node = first;
if (element == null) {//如果链表为空
for (int i = 0; i < size; i++) {
if (node.element == null)
return i;
node = node.next;
}
} else {//element!=null
for (int i = 0; i < size; i++) {
if (element.equals(node.element))//将传入的元素和遍历的元素进行比较
return i;
node = node.next;
}
}
return ELEMENT_NOT_FOUND;//return -1;
}
重写toString方法
public String toString() {
StringBuilder string = new StringBuilder();
string.append("size=").append(size).append(", [");
//方法一
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (i != 0) {
string.append(", ");
}
string.append(node.element);
node = node.next;
}
string.append("]");
/* 方法二:
Node<E> node1 = first;
while (node1 != null) {
node1 = node1.next;
}
*/
return string.toString();
}
双向链表(LinkedList)
节点定义
private static class Node<E> {
E element;
Node<E> prev;
Node<E> next;
public Node(Node<E> prev, E element, Node<E> next) {
this.prev = prev;
this.element = element;
this.next = next;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (prev != null) {
sb.append(prev.element);
} else {
sb.append("null");
}
sb.append("_").append(element).append("_");
if (next != null) {
sb.append(next.element);
} else {
sb.append("null");
}
return sb.toString();
}
}
双向链表 – 只有一个元素
获取index位置对应的节点对象
private Node<E> node(int index) {
rangeCheck(index);
if (index < (size >> 1)) {//如果要查找元素的下标小于链表长度的一半,则从前往后找
//将从第一个元素开始查找
Node<E> node = first;
for (int i = 0; i < index; i++) {
//不断往下遍历
node = node.next;
}
return node;
} else {//如果要查找元素的下标大于链表长度的一半,则从后往前找
//将从最后一个元素开始查找
Node<E> node = last;
/*记住最后一个元素下标为size-1*/
for (int i = size - 1; i > index; i--) {
//不断的往前遍历
node = node.prev;
}
return node;
}
}
public void add(int index, E element) {
rangeCheckForAdd(index);
// size == 0
// index == 0
if (index == size) { // 往最后面添加元素
Node<E> oldLast = last;
last = new Node<>(oldLast, element, null);
if (oldLast == null) { // 这是链表添加的第一个元素
first = last;
} else {
oldLast.next = last;
}
} else {
Node<E> next = node(index);
Node<E> prev = next.prev;
Node<E> node = new Node<>(prev, element, next);
next.prev = node;
if (prev == null) { // index == 0
first = node;
} else {
prev.next = node;
}
}
size++;
}
双向链表 – remove(int index)
public E remove(int index) {
rangeCheck(index);
//先找到要删除的元素
Node<E> node = node(index);
Node<E> prev = node.prev;
Node<E> next = node.next;
//当删除第一个节点的时候
if (prev == null) { // index == 0
first = next;
} else {
prev.next = next;
}
//删除最后一个节点
if (next == null) { // index == size - 1
last = prev;
} else {
next.prev = prev;
}
size--;
return node.element;
}
单向循环链表
单向循环链表 – 只有1个节点
节点定义
private static class Node<E> {
E element;
Node<E> next;
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(element).append("_").append(next.element);
return sb.toString();
}
}
单向循环链表 – add(int index, E element)
public void add(int index, E element) {
rangeCheckForAdd(index);
if (index == 0) {//如果在第一个元素的位置插入
Node<E> newFirst = new Node<>(element, first);
// 拿到最后一个节点
//如果链表中就只有一个节点的时候,自己的next指向自己
Node<E> last = (size == 0) ? newFirst : node(size - 1);
last.next = newFirst;
first = newFirst;
} else {
Node<E> prev = node(index - 1);
prev.next = new Node<>(element, prev.next);
}
size++;
}
单向循环链表 – remove(int index)
public E remove(int index) {
rangeCheck(index);
Node<E> node = first;
if (index == 0) {
if (size == 1) {
first = null;
} else {
Node<E> last = node(size - 1);
first = first.next;
last.next = first;
}
} else {
Node<E> prev = node(index - 1);
node = prev.next;
prev.next = node.next;
}
size--;
return node.element;
}
双向循环链表
双向循环链表 – 只有1个节点
双向循环链表 – add(int index, E element)
public void add(int index, E element) {
rangeCheckForAdd(index);
// size == 0
// index == 0
if (index == size) { // 往最后面添加元素
Node<E> oldLast = last;
//这里已经将要插入的节点的prev和next指向确定了
last = new Node<>(oldLast, element, first);
if (oldLast == null) { // 原来链表为空,这是链表添加的第一个元素
first = last;
//自己的next指向自己
first.next = first;
//自己的prev指向自己
first.prev = first;
} else {
//原来链表的最后一个元素指向要插入的新元素
oldLast.next = last;
//原来链表的第一个节点的prev指向新插入的节点
first.prev = last;
}
} else {//往链表中的任意位置插入
//先找到要插入的节点位置上的节点
Node<E> next = node(index);
//记录下当前的next
Node<E> prev = next.prev;
Node<E> node = new Node<>(prev, element, next);
next.prev = node;
prev.next = node;
if (next == first) { // index == 0
first = node;
}
}
size++;
}
传入要删除节点的下标
/**
* 传入要删除节点的下标
* @param index
* @return
*/
@Override
public E remove(int index) {
rangeCheck(index);
return remove(node(index));
}
传入要删除的node值
/**
* 传入要删除的node值
* @param node
* @return
*/
private E remove(Node<E> node) {
if (size == 1) {//当链表中只有一个元素的时候
first = null;
last = null;
} else {
Node<E> prev = node.prev;
Node<E> next = node.next;
prev.next = next;
next.prev = prev;
if (node == first) { // index == 0
first = next;
}
if (node == last) { // index == size - 1
last = prev;
}
}
size--;
return node.element;
}
203.移除链表元素
如果使用C,C++编程语言的话,不要忘了还要从内存中删除这两个移除的节点
如果使用java ,python的话就不用手动管理内存了
这里就涉及如下链表操作的两种方式:
- 直接使用原来的链表来进行删除操作。
- 设置一个虚拟头结点在进行删除操作。
直接使用原来的链表进行删除
移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点。
所以头结点如何移除呢,其实只要将头结点向后移动一位就可以,这样就从链表中移除了一个头结点。
设置一个虚拟头结点在进行删除操作。
可以设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了
这里来给链表添加一个虚拟头结点为新的头结点,此时要移除这个旧头结点元素1。
这样是不是就可以使用和移除链表其他节点的方式统一了呢?
来看一下,如何移除元素1 呢,还是熟悉的方式,然后从内存中删除元素1。
最后呢在题目中,return 头结点的时候,别忘了 return dummyNode->next;
, 这才是新的头结点
package leetcode.链表;
import org.w3c.dom.Node;
/**
* @author lin
* @creat 2022--12--20:09
* https://leetcode.cn/problems/remove-linked-list-elements/
*
*/
public class $_203移除元素 {
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;
}
}
/**
* 不添加虚拟节点方式
* 时间复杂度 O(n)
* 空间复杂度 O(1)
* @param head
* @param val
* @return
*/
//方法一:不设虚拟结点
public ListNode removeElements(ListNode head, int val) {
while (head != null && head.val == val)
head = head.next;
ListNode prev = head;
if (prev != null)
{
while (prev.next != null)
{
if (prev.next.val == val)
prev.next = prev.next.next;
else
prev = prev.next;
}
}
return head;
}
//方法二:使用递归
public ListNode removeElements2(ListNode head, int val) {
if(head==null){//只有一个元素
return head;
}
//当不至有一个元素的时候,就不断的指向下一个元素
head.next=removeElements2(head.next,val);
return head.val==val?head.next:head;
}
/**
* 添加虚节点方式
* 时间复杂度 O(n)
* 空间复杂度 O(1)
* @param head
* @param val
* @return
*/
//3.使用虚拟头节点
public ListNode removeElements3(ListNode head,int val){
if(head==null){
return head;
}
//因为删除可能使用到头节点,所以使用虚拟头节点,设置虚拟头节点为dummy head
ListNode dummyHead=new ListNode(-1,head);
ListNode pre=dummyHead;
ListNode cur=head;
while (cur!=null){
if(cur.val==val){
pre.next=cur.next;
}else {
pre=cur;
}
cur=cur.next;
}
//返回头节点
return dummyHead;
}
}
707.设计链表
删除链表节点:
添加链表节点:
设置一个虚拟头结点在进行删除操作。
获取第n个节点的值
头部插入节点
尾部插入节点
第n个节点前插入节点
删除第n个节点
package leetcode.链表;
/**
* @author lin
* @creat 2022--12--19:39
*/
//单链表
class ListNode {
int val;
ListNode next;
ListNode(){}
ListNode(int val) {
this.val=val;
}
}
class $_707_设计链表 {
//size存储链表元素的个数
int size;
//虚拟头结点
ListNode head;
//初始化链表
public $_707_设计链表() {
size = 0;
head = new ListNode(0);
}
//获取第index个节点的数值,注意index是从0开始的,第0个节点就是头结点
public int get(int index) {
//如果index非法,返回-1
if (index < 0 || index >= size) {
return -1;
}
ListNode currentNode = head;
//包含一个虚拟头节点,所以查找第 index+1 个节点
for (int i = 0; i <= index; i++) {
currentNode = currentNode.next;
}
return currentNode.val;
}
//在链表最前面插入一个节点,等价于在第0个元素前添加
public void addAtHead(int val) {
addAtIndex(0, val);
}
//在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
public void addAtTail(int val) {
addAtIndex(size, val);
}
// 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果 index 大于链表的长度,则返回空
public void addAtIndex(int index, int val) {
if (index > size) {
return;
}
if (index < 0) {
index = 0;
}
size++;
//找到要插入节点的前驱
ListNode pred = head;
for (int i = 0; i < index; i++) {
pred = pred.next;
}
ListNode toAdd = new ListNode(val);
toAdd.next = pred.next;
pred.next = toAdd;
}
//删除第index个节点
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
size--;
if (index == 0) {
head = head.next;
return;
}
ListNode pred = head;
for (int i = 0; i < index ; i++) {
pred = pred.next;
}
pred.next = pred.next.next;
}
}
//双链表
class ListNode{
int val;
ListNode next,prev;
ListNode() {};
ListNode(int val){
this.val = val;
}
}
class MyLinkedList {
//记录链表中元素的数量
int size;
//记录链表的虚拟头结点和尾结点
ListNode head,tail;
public MyLinkedList() {
//初始化操作
this.size = 0;
this.head = new ListNode(0);
this.tail = new ListNode(0);
//这一步非常关键,否则在加入头结点的操作中会出现null.next的错误!!!
head.next=tail;
tail.prev=head;
}
public int get(int index) {
//判断index是否有效
if(index<0 || index>=size){
return -1;
}
ListNode cur = this.head;
//判断是哪一边遍历时间更短
if(index >= size / 2){
//tail开始
cur = tail;
for(int i=0; i< size-index; i++){
cur = cur.prev;
}
}else{
for(int i=0; i<= index; i++){
cur = cur.next;
}
}
return cur.val;
}
public void addAtHead(int val) {
//等价于在第0个元素前添加
addAtIndex(0,val);
}
public void addAtTail(int val) {
//等价于在最后一个元素(null)前添加
addAtIndex(size,val);
}
public void addAtIndex(int index, int val) {
//index大于链表长度
if(index>size){
return;
}
//index小于0
if(index<0){
index = 0;
}
size++;
//找到前驱
ListNode pre = this.head;
for(int i=0; i<index; i++){
pre = pre.next;
}
//新建结点
ListNode newNode = new ListNode(val);
newNode.next = pre.next;
pre.next.prev = newNode;
newNode.prev = pre;
pre.next = newNode;
}
public void deleteAtIndex(int index) {
//判断索引是否有效
if(index<0 || index>=size){
return;
}
//删除操作
size--;
ListNode pre = this.head;
for(int i=0; i<index; i++){
pre = pre.next;
}
pre.next.next.prev = pre;
pre.next = pre.next.next;
}
}
206.反转链表
使用双指针
public ListNode reverseList2(ListNode head){
ListNode pre=null;
ListNode cur=head;
ListNode temp=cur.next;
while (cur!=null){
temp=cur.next;//保存下一个节点,要不然找不到
//将链表方向进行改变
cur.next=pre;
//将cur赋值给pre
pre=cur;
//将temp赋值给cur
cur=temp;
}
//返回新链表的头节点,此时cur指向null
return pre;
}
使用递归
public ListNode reverse(ListNode pre,ListNode cur){
if (cur==null){//终止遍历的条件就是cur==null
return pre;//返回新链表的头节点
}
ListNode temp=null;
temp=cur.next;//记录下一个节点的位置
//改变链表的方向
cur.next=pre;
//更新pre和cur的位置
//pre=cur;
//cur=temp;
return reverse(cur,temp);
}
public ListNode reverseList(ListNode head) {
//这里为什么传入的是null,head
//因为初始化pre是指向头节点的前面,cur指向头节点
return reverse(null,head);
}
24.两两交换链表中的节点
建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。
使用双指针
public ListNode swapPairs(ListNode head) {
//创建一个虚拟头节点
ListNode dummyNode = new ListNode(0);
//将头节点的next指向头节点
dummyNode.next = head;
//将定义一个临时变量来遍历
ListNode prev = dummyNode;
while (prev.next != null && prev.next.next != null) {//如果后面没有2个元素,则反转结束
ListNode temp = head.next.next; // 缓存 next
prev.next = head.next; // 将 prev 的 next 改为 head 的 next
head.next.next = head; // 将 head.next(prev.next) 的next,指向 head
head.next = temp; // 将head 的 next 接上缓存的temp
prev = head; // 步进1位
head = head.next; // 步进1位
}
return dummyNode.next;
}
使用递归
public ListNode swapPairs1(ListNode head) {
if(head == null || head.next == null) return head;
// 获取当前节点的下一个节点
ListNode next = head.next;
// 进行递归
ListNode newNode = swapPairs(next.next);
// 这里进行交换
next.next = head;
head.next = newNode;
return next;
}
19.删除链表的倒数第N个节点
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
- 首先这里我推荐大家使用虚拟头结点,这样方便处理删除实际头结点的逻辑
- 定义fast指针和slow指针,初始值为虚拟头结点
定义fast指针和slow指针,初始值为虚拟头结点
fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作)
fast和slow同时移动,直到fast指向末尾
删除slow指向的下一个节点
让fast先走n+1步
/**
* 方法一:快指针向走n+1步
* @param head
* @param n
* @return
*/
public ListNode removeNthFromEnd(ListNode head, int n) {
//创建虚拟节点
ListNode dummyHead=new ListNode(0);
//将虚拟链表和头节点连接起来
dummyHead.next=head;
//将快指针指向虚拟头节点
ListNode fast=dummyHead;
//将慢指针指向虚拟头节点
ListNode slow=dummyHead;
//因为快指针要比慢指针向走n+1个节点,所以先+1
n++;
//将快指针先走n+1步
for (int i=0;i<n;i++){
fast=fast.next;
}
//前面快指针先走完后,且当快指针不为空的时候,那快指针就要和慢指针一起走
while (fast!=null){
fast=fast.next;
slow=slow.next;
}
//此时slow指向待删除节点的前一个节点
slow.next=slow.next.next;
return dummyHead.next;
}
让fast先走n步
/**
* 方法二:快指针向走n步
* @param head
* @param n
* @return
*/
public ListNode removeNthFromEnd1(ListNode head, int n) {
//创建虚拟节点
ListNode dummyHead=new ListNode(0);
//将虚拟链表和头节点连接起来
dummyHead.next=head;
//将快指针指向虚拟头节点
ListNode fast=dummyHead;
//将慢指针指向虚拟头节点
ListNode slow=dummyHead;
//将快指针先走n步
for (int i=0;i<n;i++){
fast=fast.next;
}
//前面快指针先走完后,且当快指针不为空的时候,那快指针就要和慢指针一起走
//因为快指针就快走n步,所以要判断的是fast.next是否为空,而不是fast==null
while (fast.next!=null){
fast=fast.next;
slow=slow.next;
}
//此时slow指向待删除节点的前一个节点
slow.next=slow.next.next;
return dummyHead.next;
}
160.相交链表
简单来说,就是求两个链表交点节点的指针。交点不是数值相等,而是指针相等。
为了方便举例,假设节点元素数值相等,则节点指针相等。
- 目前curA指向链表A的头结点,curB指向链表B的头结点:
- 我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置
- 此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。
- 否则循环退出返回空指针。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//分别创建2个指针来遍历链表
ListNode curA=headA;
ListNode curB=headB;
//分别创建两个变量来记录2个链表的长度
int lengthA=0;
int lengthB=0;
while (curA!=null){//求链表A的长度
curA=curA.next;
lengthA++;
}
while (curB!=null){//求链表B的长度
curB=curB.next;
lengthB++;
}
curA=headA;
curB=headB;
//让curA为最长链表的头,lengthA为其长度
//如果本来链表A就为最长的链表,则不用进入
if (lengthB>lengthA){
//1.交换两个链表的长度
int tempLength=lengthA;
lengthA=lengthB;
lengthB=tempLength;
//2.交换两个链表
ListNode tempNode=curA;
curA=curB;
curB=tempNode;
}
//求长度差
int len=lengthA-lengthB;
//让curA和curB在同一个起点,则将链表A不断的向下遍历
while (len-->0){
curA=curA.next;
}
//不断的遍历curA和curB,直到遇到相同就返回
while (curA!=null){
if (curA==curB){
return curA;
}
//如果找不到curA==curB,则一直往下遍历
curA=curA.next;
curB=curB.next;
}
return null;
}
142.环形2链表||
主要考察两知识点:
- 判断链表是否环
- 如果有环,如何找到这个环的入口
#判断链表是否有环
可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢
首先第一点:fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。
那么来看一下,为什么fast指针和slow指针一定会相遇呢?
可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。
fast和slow各自再走一步, fast和slow就相遇了
这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。
如果有环,如何找到这个环的入口
此时已经可以判断链表是否有环了,那么接下来要找这个环的入口了。
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。
从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。
让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。
那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。
其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。
public ListNode detectCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while (fast!=null && fast.next!=null){//因为fast一次走两步,所以要判断当前和下一个是否为空
//如果不为空,则fast一次走2步,slow一次走1步
fast=fast.next.next;
slow=slow.next;
//判断是否有环---他们一定在环中相遇
if (slow==fast){
ListNode index1=fast;//ListNode index1=slow;---用于记录fast和slow相遇的位置
/*创建一个新的节点指向头节点,然后让index1和index2往前走,直到相遇点就是环的入口*/
ListNode index2=head;
while (index2!=index1){
//当index1和index2没有相遇时,两者都一直往前走
index1=index1.next;
index2=index2.next;
}
return index1;//return index2;
}
}
return null;
}
补充
在推理过程中,大家可能有一个疑问就是:为什么第一次在环中相遇,slow的 步数 是 x+y 而不是 x + 若干环的长度 + y 呢?
首先slow进环的时候,fast一定是先进环来了。
如果slow进环入口,fast也在环入口,那么把这个环展开成直线,就是如下图的样子:
可以看出如果slow 和 fast同时在环入口开始走,一定会在环入口3相遇,slow走了一圈,fast走了两圈。
重点来了,slow进环的时候,fast一定是在环的任意一个位置
那么fast指针走到环入口3的时候,已经走了k + n 个节点,slow相应的应该走了(k + n) / 2 个节点。
因为k是小于n的(图中可以看出),所以(k + n) / 2 一定小于n。
也就是说slow一定没有走到环入口3,而fast已经到环入口3了。
这说明什么呢?
在slow开始走的那一环已经和fast相遇了。
为什么fast不能跳过去呢? 在刚刚已经说过一次了,fast相对于slow是一次移动一个节点,所以不可能跳过去。
2.两数相加
迭代法
/**
* 使用非递归
* @param l1
* @param l2
* @return
*/
public ListNode addTwoNumbers1(ListNode l1, ListNode l2) {
//记录l1.val+l2.val的值
int total=0;
//记录十位上的值
int next1=0;
//创建一个新的链表存放新值
ListNode res=new ListNode();
//创建一个临时遍历来遍历新链表
ListNode cur=res;
while (l1!=null && l2!=null){
total=l1.val+l2.val+next1;
//res记录下个位的值
cur.next=new ListNode(total%10);
//记录十位的值
next1=total/10;
l1=l1.next;
l2=l2.next;
//cur不断指向下一个节点
cur=cur.next;
}
while (l1!=null){//此时l2==null
total=l1.val+next1;
cur.next=new ListNode(total%10);
next1=total/10;
l1=l1.next;
cur=cur.next;
}
while (l2!=null){//此时l1==null
total=l2.val+next1;
cur.next=new ListNode(total%10);
next1=total/10;
l2=l2.next;
cur=cur.next;
}
if (next1!=0){//此时l1.next和l2.next为0
cur.next=new ListNode(next1);
}
return res.next;
}
递归法
/**
* 使用递归
* @param l1
* @param l2
* @return
*/
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//total记录和
int total=l1.val+l2.val;
//next1记录十位数的值
int next1=total/10;
//res记录下当前要加入新链表中的节点(也就是个位数)
ListNode res=new ListNode(total%10);
if (l1.next!=null || l2.next!=null ||next1!=0){
if (l1.next!=null){
l1=l1.next;
}else {//l1.next==null
l1=new ListNode(0);
}
if (l2.next!=null){
l2=l2.next;
}else {//l2.next==null
l2=new ListNode(0);
}
l1.val=l1.val+next1;
res.next=addTwoNumbers(l1,l2);
}
return res;
}
61.旋转链表
public ListNode rotateRight(ListNode head, int k) {
if (head==null || head.next==null ){//判断当链表为空或者只有一个节点
return head;
}
//创建一个临时节点
ListNode cur=head;
//保存链表长度--初始化为1
int length=1;
while (cur.next!=null){//使用cur不断遍历链表
length++;
cur=cur.next;
}
//此时的k是记录下每一个数应该移动的位数
k=k%length;
//使用两个指针分别指向新链表的尾节点和新链表的头节点
//新链表的头节点--初始化指向头节点
ListNode newHead=head;
//指向新链表的尾节点--初始化指向头节点
ListNode newTail=head;
//创建一个变量来记录当前遍历到的旧链表的下标
int index=0;
while (newHead.next!=null){//如果
if(index<k){//如果此时index小于要指向节点的下标,则要不断的向下遍历
newHead=newHead.next;
index++;
}else {//index>=k的时候
newHead=newHead.next;
newTail=newTail.next;
index++;
}
}
//将原来的头节点赋值新头节点的下一个节点
newHead.next=head;
//将原来的头节点赋值给新的头节点
head=newTail.next;
//将新的尾节点的next置为空
newTail.next=null;
//返回头节点
return head;
}
82.删除排序链表中的重复元素
82. 删除排序链表中的重复元素 II - 力扣(LeetCode)
面试题 02.03 删除中间节点
面试题 02.03. 删除中间节点 - 力扣(LeetCode)
package leetcode.链表;
/**
* @author lin
* @creat 2022--12--10:49
*/
public class $_面试题_02_03_删除中间节点 {
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
public void deleteNode(ListNode node) {
node.val=node.next.val;
//不断遍历到下一个节点
node.next=node.next.next;
}
}
234.回文链表
package leetcode.链表;
/**
* @author lin
* @creat 2023--01--15:01
*/
public class $_234_回文链表 {
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;
}
}
/**
* 双指针+反转链表
* @param head
* @return
*/
public boolean isPalindrome(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;
}
// 如果快指针不为null,则表示是奇数个
if (fast != null) {
slow = slow.next;
}
// 反转链表
ListNode after = null;
while (slow != null) {
ListNode temp = slow.next;
slow.next = after;
after = slow;
slow = temp;
}
fast = head;
// 遍历正反链表,对比数据
while (fast != null && after != null) {
if (fast.val != after.val) {
return false;
}
fast = fast.next;
after = after.next;
}
return true;
}
}
剑指 Offer 24.反转链表
剑指 Offer 24. 反转链表 - 力扣(LeetCode)
package leetcode.链表;
/**
* @author lin
* @creat 2023--01--18:11
*/
public class $_剑指_Offer_24_反转链表 {
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
public ListNode reverseList(ListNode head) {
ListNode pre=null;
ListNode cur=head;
while (cur!=null){
ListNode temp=cur.next;//保存下一个节点,要不然找不到
//将链表方向进行改变
cur.next=pre;
//将cur赋值给pre
pre=cur;
//将temp赋值给cur
cur=temp;
}
//返回新链表的头节点,此时cur指向null
return pre;
}
}
剑指 Offer 52.两个链表的第一个公共节点
剑指 Offer 52. 两个链表的第一个公共节点 - 力扣(LeetCode)
package leetcode.链表;
/**
* @author lin
* @creat 2023--01--11:30
*/
public class $_剑指_Offer_52_两个链表的第一个公共节点 {
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headB==null || headA==null){
return null;
}
ListNode n1=headA;
ListNode n2=headB;
while (n1!=n2){
n1=n1==null ? headB:n1.next;
n2=n2==null ? headA:n2.next;
}
return n1;
}
}
86.分隔链表
package leetcode.链表;
/**
* @author lin
* @creat 2023--01--12:18
*/
public class $_86_分隔链表 {
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;
}
}
public ListNode partition(ListNode head, int x) {
//创建两个链表,一个存放小于x,一个存放大于等于x的
ListNode lowerNode=new ListNode(-1);
ListNode highNode=new ListNode(-1);
//创建两个指针分别指向这两个链表
ListNode lowerHead=lowerNode;
ListNode highHead=highNode;
//看头节点的下一个节点是否为空
while (head!=null){
if (head.val<x){//如果当前这个值是小于x,则将其存放在lowNode这个链表中
lowerNode.next=head;
lowerNode=lowerNode.next;
}else {//如果当前这个值是大于或等于x,则将其存放在highNode这个链表中
highNode.next=head;
highNode=highNode.next;
}
//原来的链表的指针向后移动
head=head.next;
}
//将小于x的链表拼接到大于或等于x的链表前面
lowerNode.next=highHead.next;
//大于或者等于x链表的最后一个节点的next指向Null
highNode.next=null;
return lowerHead.next;
}
}
92.反转链表||
package leetcode.链表;
import 链表.List;
/**
* @author lin
* @creat 2023--01--13:18
*/
public class $_92_反转链表II {
public class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public ListNode reverseBetween(ListNode head, int m, int n) {
//判断输入的m和n是否相等
if (m==n) return head;
//创建一个虚拟头节点
ListNode dummy=new ListNode(-1);
dummy.next=head;
//创建一个指针指向虚拟节点
ListNode pre=dummy;
//分别创建两个指针指向m和n所在的位置
ListNode m_node=head;
ListNode n_node=head;
//分别将两个指针指向m和n所在的位置
for (int i=0;i<m;i++){
//pre记得也要往下遍历
pre=pre.next;
m_node=m_node.next;
}
for (int i=0;i<n;i++){
n_node=n_node.next;
}
//只要n和m没有指向同一个数值的时候,就要一直转换
while (m_node!=n_node){
/*1(pre)->2(m_node)->3->4(n_node)->5->null*/
//1.先让1的next指向3
pre.next=m_node.next;
//2.让2的next指向5
m_node.next=n_node.next;
//3.让4的next指向2
n_node.next=m_node;
//4.改变m_node的指向,要求指向下一个数值(即下一个值---3)
m_node=pre.next;
}
return dummy.next;
}
}
328.奇偶链表
package leetcode.链表;
/**
* @author lin
* @creat 2023--01--21:17
*/
public class $_328_奇偶链表 {
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;
}
}
public ListNode oddEvenList(ListNode head) {
if (head==null || head.next==null){
return head;
}
//创建一个节点指向第一个节点(奇数节点)
ListNode odd=head;
//创建一个节点指向第二个节点(偶数节点)
ListNode even=head.next;
//创建一个指针指向第一个节点,因为最后奇数节点链表的最后一个节点指向偶数链表的第一个节点
ListNode evenHead=head.next;
//要判断偶数的下一个节点还存不存在
while (even!=null && even.next!=null){
//1.将奇数的next指向偶数的下一个节点
odd.next=even.next;
//2.将奇数的指针移动到下一个奇数上
odd=odd.next;
//3.偶数的next将指向新的奇数指针的下一个位置上
even.next=odd.next;
//4.将偶数的指针移动到下一个偶数上
even=even.next;
}
//将奇数链表和偶数链表连接起来
odd.next=evenHead;
//因为第一个元素就是奇数链表的第一个节点
return head;
}
}
445.两数相加 ||
package leetcode.链表;
import java.util.Stack;
/**
* @author lin
* @creat 2023--01--22:53
*/
public class $_445_两数相加_II {
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;
}
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//分别创建两个栈来存放链表的数值
Stack<Integer> stack1=new Stack<Integer>();
Stack<Integer> stack2=new Stack<Integer>();
//分别将两个链表的值放入栈中
while (l1!=null){
stack1.push(l1.val);
//不断的遍历下一个值
l1=l1.next;
}
while (l2!=null){
stack2.push(l2.val);
//不断的遍历下一个值
l2=l2.next;
}
//创建一个值查看是否要进位
int next1=0;
//创建一个指针指向新节点
ListNode cur=null;
//判断两个栈是否为空,如果不为空就出栈
//为什么是“||”--因为可能一个先结束
while (!stack1.isEmpty() || !stack2.isEmpty()){
//创建一个值来记录新链表的节点值
int sum=0;
if (!stack1.isEmpty()){
sum+=stack1.pop();
}
if (!stack2.isEmpty()){
sum+=stack2.pop();
}
//当前的节点值是l1.val+l2.val+next1(是否进位)
sum+=next1;
//创建一个新的节点存放新数值
ListNode newNode=new ListNode(sum%10);
//next1表示是否要进位--就是十位数
next1=sum/10;
//将新节点往旧节点的前面插入---即:将新节点的next指向cur
newNode.next=cur;
//将cur指向新节点
cur=newNode;
}
//最后要判断是否要进位,如果要进位就要新创建一个节点存放
if (next1!=0){
ListNode newNode1=new ListNode(next1);
newNode1.next=cur;
cur=newNode1;
}
return cur;
}
}
1171.从链表中删去总和为零的连续节点
1171. 从链表中删去总和值为零的连续节点 - 力扣(LeetCode)