相交链表
编写一个程序,找到两个单链表相交的起始节点。
如下面的两个链表:
在节点 c1 开始相交
难易度: 简单
解题思路
设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a
当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B;同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点
如果不存在交点,那么 a + b = b + a,以下实现代码中 l1 和 l2 会同时为 null,从而退出循环
var getIntersectionNode = function(headA: LinkedNode, headB: LinkedNode) {
let l1 = headA, l2 = headB
while(l1 != l2) {
l1 = l1 ? l1.next : headB // 如果l1为null 代表l1已经遍历完,将l1尾部链接至l2尾部
l2 = l2 ? l2.next : headA
}
return l1
};
两数相加
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆 序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
- 难易程度: 中等
解题思路
- 平常思维,按序取出链表中的值,得出相加的结果,再利用结果倒序生成新的链表
class LinkedNode {
value: number
next: LinkedNode
constructor(value: number) {
this.next = null
this.value = value
}
}
const a = new LinkedNode(2)
const b = new LinkedNode(4)
const c = new LinkedNode(3)
const d = new LinkedNode(5)
const e = new LinkedNode(6)
const f = new LinkedNode(4)
a.next = b
b.next = c
d.next = e
e.next = f
function getNode(linkedList: LinkedNode, values: number[]) {
values.push(linkedList.value)
if (linkedList.next !== null) {
getNode(linkedList.next, values)
}
}
// 按序取出链表中的值的数组
function getNodeArr(linkedList: LinkedNode) {
let values: number[] = []
getNode(linkedList, values)
return values
}
const addTwoNumbers = function (l1: LinkedNode, l2: LinkedNode) {
let value1 = getNodeArr(l1).reverse().join('')
let value2 = getNodeArr(l2).reverse().join('')
let res = (+value1) + (+value2) + ''
// 存储结果的每个节点
const nodeArr: LinkedNode[] = []
res.split('').reverse().forEach(r => {
nodeArr.push(new LinkedNode(+r))
})
for (let i = 0, len = nodeArr.length; i < len - 1; i++) {
// 构建结果链表
nodeArr[i].next = nodeArr[i + 1]
}
return nodeArr[0]
};
console.log(addTwoNumbers(a, d)) // 7 -> 0 -> 8
- 两个链表相加,因为是倒序的,则两数相加大于10,向后进位,低位相加。
2 -> 4 -> 3
5 -> 6 -> 4 +
----------------------
7 -> 0 -> 8
因为6+4大于等于10, 所以需要向后进位, 3 + 4 + 1 = 8
function addTwoNumbers2(l1: LinkedNode, l2: LinkedNode): LinkedNode {
let node = new LinkedNode(0);
let temp = node;// temp节点
let add = 0;// 是否进一
let sum = 0;// 新链表当前未取余的值 = 链表1值 + 链表2值 + add;
// 遍历, 以最长链表为结束点
while (l1 || l2) {
sum = (l1 ? l1.value : 0) + (l2 ? l2.value : 0) + add;
temp.next = new LinkedNode(sum % 10);// 取余则为新链表的值
temp = temp.next;
add = sum >= 10 ? 1 : 0; // 相加大于10,则需向后进位(+1)
l1 && (l1 = l1.next); // 改变指向,继续循环
l2 && (l2 = l2.next);
}
add && (temp.next = new LinkedNode(add));
return node.next;
}
反转链表
反转一个单链表。
示例
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
难易度:简单
解题思路
从头逆序
构造一个新的头,新头的next等于head
var reverseList = function (head: LinkedNode) {
if (!head || !head.next) return head
let newHead = new LinkedNode(-1)
while (head) {
let temp = head.next
head.next = newHead.next // 实现反转
newHead.next = head
head = temp
}
return newHead.next
};
递归
function reverseList2 (head: LinkedNode) {
if (head == null || head.next == null) return head;
let next = head.next;
let newHead = reverseList2(next);
next.next = head;
head.next = null;
return newHead;
}
合并链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
难度:容易
解题思路:
- 暴力解法
递归两个链表,取出所有val,对这些val进行排序,将排序之后的val在生成一个新链表输出。
class ListNode {
val: number = 0
next: ListNode = null
constructor(val: number, next: ListNode = null) {
this.val = val
this.next = next
}
}
function getNodeVal(l: ListNode, values: number[]) {
if (!l) return
values.push(l.val)
getNodeVal(l.next, values)
}
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var mergeTwoLists = function (l1: ListNode, l2: ListNode): ListNode {
if (l1 && l2) {
let values: number[] = []
getNodeVal(l1, values)
getNodeVal(l2, values)
values.sort((a, b) => a - b)
let res: ListNode[] = values.map(v => new ListNode(v))
res.reduce((prev, cur) => prev.next = cur)
return res[0]
} else {
return l1 || l2
}
};
- 不生成新的链表
既然两个链表都是升序排列,那么只需要把两个列表的节点相互比较,然后插入到愿列表中即可
1. 如果l1的当前节点比l2的当前节点大,那么就应该是l1的当前节点插入l2的当前节点之后
2. 反之亦然
3. 递归比较没一个节点
var mergeTwoLists2 = function (l1: ListNode, l2: ListNode): ListNode {
if (!l1 || !l2) return l1 || l2
if (l1.val > l2.val) {
l2.next = mergeTwoLists2(l1, l2.next)
return l2
} else {
l1.next = mergeTwoLists2(l1.next, l2)
return l1
}
}
删除有序链表中的重复元素
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例
输入: 1->1->2->3->3
输出: 1->2->3
难度:容易
解题思路
因为这个链表是有序的,所以如果链表的next 等于 当前位, 那么就可以直接舍弃当前位,让next指向 next.next
var deleteDuplicates = function (head: ListNode): ListNode {
if (!head || !head.next) return head
head.next = deleteDuplicates(head.next)
return head.val == head.next.val ? head.next : head
};
删除链表的倒数第N个节点
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5
难度: 中等
解题思路
快慢双指针
快指针先走 n+1 步
慢指针从头开始走,两个指针以同样的速度往后走
直到快指针为 nll 此时,说明快指针已经走完了整个链表
此时慢指针的位置就是倒数第n个节点
删除这个节点
var removeNthFromEnd = function(head: ListNode, n: number): ListNode {
let fast = head
// 快指针先走 n+1 步
while (n > 0) {
fast = fast.next
n--
}
// 如果快指针走完n+1步,整个链表就走完了,说明只需要删除头节点
if (fast == null) return head.next
let slow = head
// 慢指针从头开始走,两个指针以同样的速度往后走
while (fast.next) {
slow = slow.next
fast = fast.next
}
slow.next = slow.next.next // 删除这个节点
return head
};
两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例
给定 1->2->3->4, 你应该返回 2->1->4->3.
难度: 中等
解题思路
var swapPairs = function (head: ListNode): ListNode {
let newListNode = new ListNode(-1) // 添加dummy节点
newListNode.next = head
let temp = newListNode
while(temp.next && temp.next.next) {
let s = temp.next
let e = s.next
// 交换s 和 e 两个节点
temp.next = e;
s.next = e.next;
e.next = s;
temp = s;// 向后两个节点,继续交换
}
return newListNode.next
};