数据结构-链表


1.对于链表的问题 先确定prev curr next三个节点要用到哪些 然后不断遍历链表进行操作即可 有时可能会用到伪节点
2.一般新建一个或者两个链表(需要定义链表的头 尾 一般都会用到虚拟头结点) 用来存放结果 也可能更多 具体看需要几个链表 然后返回新建的链表
3.链表必须掌握的知识点:翻转链表(代码背下来) 节点的插入与删除 快慢指针求中心节点 计算链表的长度

2. 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。请你将两个数相加,并以相同形式返回一个表示和的链表。

示例1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]

解:
1.新建虚拟头节点res和遍历结果链表的指针node 最初node指向res
2.flag用来表示是否有进位 为0表示没进位 为1表示有进位 两数相加的和val=n1 + n2 + flag;其中n1 n2分别表示l1.val l2.val
3.val可能>=10 可能<10 若>=10则val=val%10 val中保存个位值 flag=1表示有进位 若<10 则flag=0表示无进位
4.新建一个值为val的节点 将链表的next指向该新建节点 由于node就是用来遍历结果链表的 所以node.next = new ListNode(val);
5.考虑如果l1 l2长度不一样该怎么办:任何一个链表为空都让他的值为0
6.l1 l2都为null时 退出循环(2-5)
7.注意判断flag的值 若为1 表明还有进位 需要把这个进位添加到结果链表的最后 也就是说 新建一个值为1的节点 将node的next指向该新建节点
8.返回结果链表 也就是虚拟头节点的next
代码如下:
JS:

var addTwoNumbers = function(l1, l2) {
    let res = new ListNode(-1);
    let node = res;
    let val;
    let flag = 0;
    let n1;
    let n2;
    while (l1 !== null || l2 !== null){
        n1 = l1 ? l1.val : 0;
        n2 = l2 ? l2.val : 0;
        val = n1 + n2 + flag;
        if (val >= 10) {
            val %= 10;
            flag = 1;
        }else {
            flag = 0;
        }
        node.next = new ListNode(val);
        node = node.next;
        l1 = l1 === null ? l1 : l1.next;
        l2 = l2 === null ? l2 : l2.next;
    }
    
    if (flag !== 0) {
        node.next = new ListNode(flag);
    }
    return res.next;
};

JAVA:

	public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode listNode1 = l1;
        ListNode listNode2 = l2;
        int num = 0;
        int val = 0;
        int x = 0;
        int y = 0;
        ListNode listNode = new ListNode(val);
        ListNode curr = listNode;
        while (listNode1 != null || listNode2 != null ){
            x = (listNode1 != null) ? listNode1.val : 0;
            y = (listNode2 != null) ? listNode2.val : 0;
            val = x + y + num;
            num = val / 10;
            curr.next = new ListNode(val % 10);
            curr = curr.next;
            if (listNode1 != null) listNode1 = listNode1.next;
            if (listNode2 != null) listNode2 = listNode2.next;
        }
        if (num > 0){
            curr.next = new ListNode(num);
        }
        return listNode.next;
    }

剑指 Offer 22. 链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。

示例:给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.

解:定义两个指针l r都指向head r先走k步 此时l r相距k步 然后l r一起走 当r指向null时 l刚好位于倒数第k个节点
代码如下:
JS:

var getKthFromEnd = function(head, k) {
    let l = head, r = head;
    while (k > 0) {
        r = r.next;
        k--;
    }
    while (r !== null) {
        r = r.next;
        l = l.next;
    }
    return l;
};

21. 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

例1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]

解:
1.新建一个链表ans用来存放结果 一个指针node用来遍历ans
2.只要l1 l2不为空就比较l1 l2的值 谁小node.next就指向谁 l1/l2指向下一个节点
3.退出循环时l1 l2肯定有一个为空 node.next直接指向不为空的那个链表
代码如下:
JS:

var mergeTwoLists = function(l1, l2) {
    if (l1 === null) return l2;
    if (l2 === null) return l1;
    let ans = new ListNode(-1);
    let node = ans;
    while (l1 !== null && l2 !== null) {
        if (l1.val > l2.val) {
            node.next = l2;
            l2 = l2.next;
        }else {
            node.next = l1;
            l1 = l1.next;
        }
        node = node.next;
    }
    // if (l1 !== null) {
    //     node.next = l1;
    // }
    // if (l2 !== null) {
    //     node.next = l2;
    // }
    // 合并后l1和l2最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
    // 上面的代码可以合并为:
    node.next = l1 === null ? l2 : l1;
    return ans.next;
};

23. 合并K个升序链表

给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入:lists = []
输出:[]
示例 3:
输入:lists = [[]]
输出:[]

解:两两合并链表 借用21题代码 代码如下:
JS:

var mergeKLists = function(lists) {
    if (lists.length === 0) return [];
    let ans = lists[0];
    for (let i = 1; i < lists.length; i++) {
        ans = mergeTwoLists(ans, lists[i]);
    }
    return ans;
};

function mergeTwoLists(l1, l2) {
    if (l1 === null) return l2;
    if (l2 === null) return l1;
    let ans = new ListNode(-1);
    let node = ans;
    while (l1 !== null && l2 !== null) {
        if (l1.val > l2.val) {
            node.next = l2;
            l2 = l2.next;
        }else {
            node.next = l1;
            l1 = l1.next;
        }
        node = node.next;
    }
    node.next = l1 === null ? l2 : l1;
    return ans.next;
}

148. 排序链表

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

示例1:
输入:head = [4,2,1,3]
输出:[1,2,3,4]
示例2:
输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

解:时间复杂度是O(nlogn) 的排序算法包括归并排序、堆排序和快速排序(快速排序的最差时间复杂度是 O(n^2),其中最适合链表的排序算法是归并排序。归并排序基于分治算法。最容易想到的实现方式是自顶向下的递归实现,考虑到递归调用的栈空间,自顶向下归并排序的空间复杂度是O(logn)。如果要达到O(1) 的空间复杂度,则需要使用自底向上的实现方式。
1.自顶向下归并排序
(1)分割 cut 环节: 找到当前链表中点,并从中点将链表断开 我们使用 fast,slow 快慢双指针法,奇数个节点找到中点,偶数个节点找到中心左边的节点。找到中点 slow 后,执行 slow.next = None 将链表切断。递归分割时,输入当前链表左端点 head 和中心节点 slow 的下一个节点 tmp(因为链表是从 slow 切断的)。cut 递归终止条件: 当head.next == None时,说明只有一个节点了,直接返回此节点。
(2)合并 merge 环节: 将两个排序链表合并,转化为一个排序链表。借用21题代码
在这里插入图片描述
2.自底向上归并排序
我也不会……

160. 相交链表

剑指 Offer 52. 两个链表的第一个公共节点

输入两个链表,找出它们的第一个公共节点。
解:
在这里插入图片描述
指针 A 先遍历完链表 headA ,再开始遍历链表 headB ,当走到 node 时,共走步数为:a + (b - c)
指针 B 先遍历完链表 headB ,再开始遍历链表 headA ,当走到 node 时,共走步数为:b + (a - c)
如下式所示:a + (b - c) = b + (a - c) 此时指针 A , B 重合,并有两种情况:
若两链表 有 公共尾部 (即 c > 0c>0 ) :指针 A , B 同时指向「第一个公共节点」node 。
若两链表 无 公共尾部 (即 c = 0c=0 ) :指针 A , B 同时指向 null。
因此返回 A 即可。

206. 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

解:

  1. 迭代:以例1为例 你要反转一个链表 首先你肯定需要两个节点prev和curr(分别代表上一个节点和当前节点)
    在这里插入图片描述
    你要让当前节点的next指向prev节点才算反转了当前节点对吧 但是当当前节点的next指向prev节点的话 当前节点和后面的节点就断了连接 如下图
    在这里插入图片描述
    所以我们还需要一个节点next用来保存curr.next 并且next = curr.next必须在curr.next = prev前面执行
    接下来两个节点往后移动一位 prev = curr curr = next即可
    在这里插入图片描述
    接下来又该反转了 反转之前先保存curr的下一个节点next = curr.next 然后让当前节点的next指向prev节点
    在这里插入图片描述
    直到当前节点为null 反转结束 返回prev
    在这里插入图片描述
    代码如下:
    JS:
// 注意写代码的时候 一上来定义三个指针就行 不要给这三个指针指定关系 不然会报错!切记!
var reverseList = function(head) {
    if (head === null) return null;
    let prev = null;
    let curr = head;
    let next;
    // prev.next = curr;
    while (curr !== null) {
        next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
};

JAVA:

public ListNode reverseList(ListNode head) {
    ListNode prev = null;
    ListNode curr = head;
    while (curr != null) {
        ListNode next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}
  1. 递归:递归包括“递”的过程和“归”的过程 “递”的过程就是将一个大问题拆成小问题 在本题中就是将一个链表拆分成一个头节点和除头节点之外的子链表 不停的拆就是不停的“递” 直到链表只有一个节点
    在这里插入图片描述
    在“归”的过程我们需要完成反转的动作 当链表只有一个节点时 不用反转 从一个节点“归”到有两个节点 此时需要反转这两个节点 然后将结果“归”到第三个节点 如何反转这两个节点呢?让head.next.next指向head
    在这里插入图片描述
    再让设置head.next指向null 因为反转后原来的head就是链表的最后一个节点 head.next = null
    在这里插入图片描述
    这样就完成了第一次“归” 我们再进行下一次“归” 在进行下一次“归”的时候除头节点意外的子链表是反转好了的 我们只需要反转头节点和头节点的next节点即可 依旧是让head.next.next指向head 然后让head.next = null
    在这里插入图片描述
    如此一层层递归 直到链表反转完成
    在这里插入图片描述
    代码如下:
    JS:
var reverseList = function(head) {
    if (head == null || head.next == null) return head
    const p = reverseList(head.next)
    head.next.next = head
    head.next = null
    return p
};

JAVA:

public ListNode reverseList(ListNode head) {
    // 1. 递归终止条件
    if (head == null || head.next == null) {
        return head;
    }
    ListNode p = reverseList(head.next);
    head.next.next = head;
    head.next = null;
    return p;
}

剑指 Offer 06. 从尾到头打印链表

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

示例1
输入:
{67,0,24,58}
返回值:
[58,24,0,67]

解:遍历链表、将每个元素从数组头部插入、实现倒序输出

var reversePrint = function(head) {
    let arr = [];
    let node = head;
    while(node){
        arr.unshift(node.val);
        node = node.next;
    }
    return arr;
};

剑指 Offer 18. 删除链表的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。

示例 1:
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.

解:定义虚拟头节点和三个指针 一个指向当前节点now 一个指向当前节点的上一个节点prev 一个指向当前节点的下一个节点next 当前节点的值等于目标值时让prev.next指向next 返回头节点 记得单独处理头节点的值等于目标值的情况

var deleteNode = function(head, val) {
    if (!head) return head;
    let prev = new ListNode(-1);
    let now = head;
    let next = now.next;
    prev.next = now;
    while (now) {
        if (now.val === val) {
            if (now === head) return head.next;
            prev.next = next;
            return head;
        }else {
            if (!next) return head;
            prev = now;
            now = next;
            next = next.next;
        }
    }
    return head;
};

剑指 Offer II 021. 删除链表的倒数第 n 个结点

给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。链接
在这里插入图片描述
在这里插入图片描述
解:使用双指针:两个指针,第一个指针指向虚拟头节点,第二个指针开始往后移动n步,只有这样,当第二个指针指向尾节点时,第一个指针指向的节点的下一个节点就是要删除的节点,那么直接让第一个指针指向的节点指向下下个节点:now.next = now.next.next;

public class offer_II021 {
    public static ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode pre = new ListNode(0, head);
        ListNode now = pre;
        ListNode listNode = head;
        for (int i = 1; i < n; i++) {
            listNode = listNode.next;
        }
        while (listNode.next != null) {
            listNode = listNode.next;
            now = now.next;
        }
        now.next = now.next.next;
        return pre.next;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值