2.27 链表算法题

67 阅读6分钟

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;
}
  • 变量必须先定义,才能使用。所以 node2node3 必须在 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函数,所以需要更改实现代码的函数名。