2.27 链表
链表是一种数据结构,由一系列节点组成
每个节点有两个属性。val表示当前节点值,next表示下一个指向的节点。
// 定义节点
class ListNode {
constructor(val, next = null) {
this.val = val; // 小朋友的名字
this.next = next; // 小朋友的右手拉的是谁
}
}
next=null表示默认下一节点为空
//创建节点
const node1 = new ListNode(1,node2);
const node2 = new ListNode(2,node3);
const node3 = new ListNode(3);
//这样会报错
//按照3,2,1的顺序写,先写next=null的节点,不然会报错。
const node3 = new ListNode(3);
const node2 = new ListNode(2,node3);
const node1 = new ListNode(1,node2);
//如果非要按照1,2,3的顺序写,那么先定义各个节点的val,再定义next指针
const node1 = new ListNode(1);
const node2 = new ListNode(2);
const node3 = new ListNode(3);
node1.next = node2;
node2.next = node3;
//打印链表
let current = node1;
//定义当前状态,方便遍历
while(current !== null){
console.log(current.val);
//console.log是打印的意思,把要打印的东西放到后面的括号里
current = current.next;
}
- 变量必须先定义,才能使用。所以
node2和node3必须在node1之前定义。 - 推荐的写法是:从后往前创建节点,即先创建最后一个节点,再依次创建前面的节点。
算法题(一),反转链表
一. 迭代法
反转链表的核心思想是改变每个节点的指针方向。
函数
//反转链表的函数
function reverseList(head) {
let prev = null; // 已经反转部分的头节点
let current = head; // 当前正在处理的节点
while (current !== null) {
const nextTemp = current.next; // 临时保存下一个节点
current.next = prev; // 反转当前节点的指针
prev = current; // 更新 prev 为当前节点
current = nextTemp; // 更新 current 为下一个节点
}
return prev; // prev 就是新的头节点
}
小白思路:
1->2->3->4->5->null
p c nT
p c nT
p c nT
针对current进行遍历
先定义nextTump= current.next;
然后修改指针 目前c是指向nT的,改为指向prev,current.next=prev;
完成了,我们更新current以便下一轮的循环,用新一轮的各种值=上一轮的值
prev = current;
current = nextTump;
while结构结束以后记得加上返回,返回头节点prev
while时current是5,但是后面更新掉了prev变成5了,所以要返回prev而不是current.
完成函数
输入输出实现
需要实现空链表,单节点链表和多节点链表的翻转,用if结构实现
if(head===null){
return head;
}
if(head.next===null){
return head; }
所以完整的函数是
function reverseList(head) {
// 处理空链表
if (head === null) {
return null;
}
// 处理单节点链表
if (head.next === null) {
return head;
}
// 处理多节点链表
let prev = null;
let current = head;
while (current !== null) {
const nextTemp = current.next; // 保存下一个节点
current.next = prev; // 反转当前节点的指针
prev = current; // 更新 prev
current = nextTemp; // 更新 current
}
return prev; // prev 是反转后的头节点
}
如果要实现数组方式的输入输出,需要转换数据结构
// 将数组转换为链表
function arrayToList(arr) {
if (arr.length === 0) return null; // 空数组返回 null
let head = new ListNode(arr[0]); // 创建头节点
let current = head;
for (let i = 1; i < arr.length; i++) {
current.next = new ListNode(arr[i]); // 创建下一个节点
current = current.next; // 移动到下一个节点
}
return head; // 返回链表的头节点
}
function reverseList(head) {
let prev = null;
let current = head;
while (current !== null) {
const nextTemp = current.next; // 保存下一个节点
current.next = prev; // 反转当前节点的指针
prev = current; // 更新 prev
current = nextTemp; // 更新 current
}
return prev; // 返回反转后的头节点
}
//将链表转换为数组
function reverseList(head) {
let prev = null;
let current = head;
while (current !== null) {
const nextTemp = current.next; // 保存下一个节点
current.next = prev; // 反转当前节点的指针
prev = current; // 更新 prev
current = nextTemp; // 更新 current
}
return prev; // 返回反转后的头节点
}
问题
定义变量
let定义可以重新赋值的变量,const定义不可重新赋值的变量。
为什么第一遍没过,因为return prev的位置写错了!!return prev写到while循环里就是第一次循环直接输出结果了,所以应该把return prev写到while循环外边。
二. 递归法
递归是一种非常优雅的方式来实现链表翻转。递归是将问题分解为更小的问题。
function reverseList(head) {
// 递归终止条件。其实就是空节点和单节点情况。
if (head === null || head.next === null) {
return head;
}
//多节点情况:
// 递归反转剩余部分
const newHead = reverseList(head.next);
// 反转当前节点
head.next.next = head; // 将下一个节点的指针指向当前节点
head.next = null; // 将当前节点的指针置为 null
// 返回反转后的头节点
return newHead;
}
从1开始,reverseList(1), 在执行变量为1的这个函数的时候,会调用到reverseList(2),(2)调用(3),(3)调用(4),(4)调用(5).
(5)执行过程中会返回head,返回头节点为5的这个链表。
返回(4),把4->5指针反转,并且把4的指针设置为空,仍然返回5.
返回(3),把3->4指针反转,并且把3的指针设置为空,仍然返回5.
返回(2),把2->3指针反转,并且把2的指针设置为空,仍然返回5.
返回(1),把1->2指针反转,并且把1的指针设置为空,仍然返回5.
最终返回头节点为5的链表。
关键在于定义这个新的变量,newHead
算法题(二)删除链表的中间节点
快慢指针法,快指针一次走两步,慢指针一次走一步,快指针走完时满指针刚好走到中间。
找到中间节点的前一个节点A,将它的指针更改.
A.next=A.next.next;
function deleteMiddleNode(head) {
if (head === null || head.next === null) {
return null; // 如果链表为空或只有一个节点,直接返回 null
}
let fast = head;
let slow = head;
let prev = null;
//结束条件,快指针走完
while(fast!==null && fast.next!==null){
//&&是且,||是或,快指针已经快指针下一个节点都不是空的情况下,继续循环。
fast = fast.next.next;
prev = slow;
slow = slow.next;
}
//当slow走到中间节点的时候,跳出循环
prev.next = slow.next;
return head;
}
算法题(三)奇偶链表
var oddEvenList = function(head) {
if(head===null||head.next===null||head.next.next===null){
return head;
}
//设置奇项和偶项
let odd = head;
let even = head.next;
const evenHead = head.next;//设置偶数项的第一个节点
while(even!==null&&even.next!==null){//这个循环条件等下一起研究下
odd.next = even.next;
even.next = even.next.next;
odd = odd.next;
even = even.next; //更新节点并更新指针
}
odd.next = evenHead;
return head;
};
算法题(四)链表最大孪生和
链表长度n为偶数,要找互相对称的两个数字的和的最大值。
思路:找到链表的中间节点,这里走个实例发现是偏后的那个中间节点,1,2,3,4,5,6.返回的节点是4
把4,5,6反转
现在有两个链表1,2,3和4,5,6,设置两个指针,每次相加两个指针的数值,更新最大值。
调用函数的时候通过设变量的方式来调用。
测试代码调用了pairSum函数,所以需要更改实现代码的函数名。