文章简要概述
-
本篇主要是复习链表相关的内容,部分算法题之前可能写过,这里进行回顾,进一步加深印象。
刷题不是目的,目的是理解与掌握。 -
本文一共有5道题,主要介绍leetcode中
两数相加 II、重排链表、面试题 02.08. 环路检测、剑指 Offer 18. 删除链表的节点和设计链表的解题思路。
与链表相关算法
两数相加 II
题目大意:
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
示例
输入: l1 = [7,2,4,3], l2 = [5,6,4]
输出: [7,8,0,7]
解题思路:
- 从题意中可以看出,把两个链表从后往前每个节点值相加,大于十位的向前进位。
- 本题比较简单的做法是将两个链表反转,然后从头遍历,将两个链表的值求和即可。
- 这里采用栈的方式处理。将两个链表的值按照顺序放入栈中,然后出栈求和,知道栈为空或进位值为0。
代码:
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
function addTwoNumbers (l1, l2) {
const stack1 = [];
const stack2 = [];
while(l1) {
stack1.push(l1.val);
l1 = l1.next;
}
while(l2) {
stack2.push(l2.val);
l2 = l2.next;
}
let tem = 0;
let ans = null
while(stack1.length > 0 || stack2.length > 0 || tem !== 0) {
const val1 = stack1.length ? stack1.pop() : 0;
const val2 = stack2.length ? stack2.pop() : 0;
let sum = val1 + val2 + tem;
tem = Math.floor(sum / 10);
sum = sum % 10;
const res = new ListNode(sum);
res.next = ans;
ans = res;
}
return ans;
};
重排链表
重排链表 -- leetcode
题目大意:
给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
输入: head = [1,2,3,4]
输出: [1,4,2,3]
解题思路:
- 第一步找到链表的中间节点,这里采用快慢指针的方式,快指针是慢指针的两倍速度,快指针到达链表尾部,慢指针的位置就是链表的中间位置。
- 将链表中间后面的节目进行反转。
- 将链表前面的部分和反转后的链表按照题意拼接到一起即可。
代码:
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {void} Do not return anything, modify head in-place instead.
*/
function getMiddle (node) {
let first = node;
let second = node;
while(first.next !== null && first.next.next !== null) {
first = first.next.next;
second = second.next;
}
return second;
}
function reserveNode (node) {
let prev = null;
while(node) {
const tem = node.next;
node.next = prev;
prev = node;
node = tem;
}
return prev;
}
function mergeNode (l1, l2) {
let tem1, tem2
while(l1 && l2) {
tem1 = l1.next;
tem2 = l2.next;
l1.next = l2;
l1 = tem1;
l2.next = l1;
l2 = tem2;
}
}
var reorderList = function(head) {
if (!head) return head;
const mid = getMiddle(head);
const l1 = head;
let l2 = mid.next;
mid.next = null;
l2 = reserveNode(l2);
mergeNode(l1, l2);
};
面试题 02.08. 环路检测
题目大意:
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况
示例:
输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。
解题思路:
- 这道题和之前做的环形链表II是一样的,就是找到链表环形的入口节点。
- 采用快慢指针来实现,具体解析看之前的文章
代码:
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
function detectCycle (head) {
if (!head) return null;
let fast = slow = head;
while(fast?.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
slow = head;
while(slow !== fast) {
slow = slow.next;
fast = fast.next;
}
return fast;
}
}
return null;
};
剑指 Offer 18. 删除链表的节点
剑指 Offer 18. 删除链表的节点 -- leetcode
题目大意:
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
**注意:** 此题对比原题有改动
示例:
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
解题思路:
- 本题与上一篇文章最后一题很相似。首先我们是找到待删除节点位置。
- 遍历链表,当链表不为空且当前节点不是待删除节点,就继续遍历链表。
- 找到待删除节点后,判断待删除节点之后是否还有节点,有就将当前节点删除,链接好原链表。
代码:
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @param {number} val
* @return {ListNode}
*/
function deleteNode (head, val) {
if (head.val === val) return head.next;
let prev = head;
let cur = head.next;
while(cur !== null && cur.val !== val) {
prev = cur;
cur = cur.next;
}
if (cur !== null) prev.next = cur.next;
return head;
};
设计链表
题目大意:
设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。
在链表类中实现这些功能:
get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
示例:
MyLinkedList linkedList = new MyLinkedList();
linkedList.addAtHead(1);
linkedList.addAtTail(3);
linkedList.addAtIndex(1,2); //链表变为1-> 2-> 3
linkedList.get(1); //返回2
linkedList.deleteAtIndex(1); //现在链表是1-> 3
linkedList.get(1); //返回3
解题思路:
- 这道题可以用单链表来处理,也可以用双链表。
- 采用双链表时间复杂度: addAtHead,addAtTail: O(1) get,addAtIndex,delete:O(min(k,N−k)),其中 kk 指的是元素的索引。 空间复杂度:所有的操作都是 \mathcal{O}(1)O(1)。
代码:
function ListNode (val, pre, next) {
this.val = val || 0;
this.pre = pre || null;
this.next = next || null;
}
var MyLinkedList = function() {
this.head = new ListNode(-1);
this.tail = new ListNode(-1);
this.head.next = this.tail;
this.tail.pre = this.head;
this.length = 0;
};
MyLinkedList.prototype.getNodeOfIndex = function (index) {
let node = this.head.next;
while(index) {
node = node.next
index--;
}
return node;
}
/**
* @param {number} index
* @return {number}
*/
MyLinkedList.prototype.get = function(index) {
if (index > this.length) return -1;
return this.getNodeOfIndex(index).val;
};
/**
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtHead = function(val) {
const tem = new ListNode(val, this.head, this.head.next);
this.head.next.pre = tem;
this.head.next = tem;
this.length++;
};
/**
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtTail = function(val) {
const tem = new ListNode(val, this.tail.pre, this.tail);
this.tail.pre.next = tem;
this.tail.pre = tem;
this.length++;
};
/**
* @param {number} index
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtIndex = function(index, val) {
if (index > this.length) return;
if (index === this.length) {
this.addAtTail(val);
return
}
if (index < 0) {
this.addAtHead(val);
return
}
const cur = this.getNodeOfIndex(index);
const tem = new ListNode(val, cur.pre, cur);
cur.pre.next = tem;
cur.pre = tem;
this.length++
};
/**
* @param {number} index
* @return {void}
*/
MyLinkedList.prototype.deleteAtIndex = function(index) {
if (index < 0 || index >= this.length) return;
const cur = this.getNodeOfIndex(index);
cur.pre.next = cur.next;
cur.next.pre = cur.pre;
this.length--;
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* var obj = new MyLinkedList()
* var param_1 = obj.get(index)
* obj.addAtHead(val)
* obj.addAtTail(val)
* obj.addAtIndex(index,val)
* obj.deleteAtIndex(index)
*/
结束语
数据结构与算法相关的练习题会持续输出,一起来学习,持续关注。当前是链表复习部分。后期还会有其他类型的数据结构,题目来源于leetcode。往期文章:
链表相关算法复习 数据结构与算法-栈一 数据结构与算法-栈二
数据结构与算法-队列一 数据结构与算法-队列二 数据结构与算法-链表一
有兴趣的可以一起来刷题,感谢点赞👍 , 关注!