关于链表的算法
翻转链表
1. 反转链表
反转链表
输入:{1,2,3}
输出:{3,2,1}
方法:改变链表指向,即反着指
function ReverseList(Head)
{
let pre = null, cur = Head
while(cur){
let temp = cur.next
cur.next = pre
pre = cur
cur = temp
}
return pre
}
2. 链表内指定区间反转
链表内指定区间反转
输入:{1,2,3,4,5},2,4
输出:{1,4,3,2,5}
方法①:沿用题目1的方法
先将待翻转的链表区域截取出来,对其进行翻转后再进行拼接
function reverseBetween(head, m, n) {
const newHead = new ListNode(0)
newHead.next = head
let start = newHead, end = newHead
// 指向start的前一个节点
while (m > 1) {
start = start.next
m--
}
// 指向end节点
while (n > 0) {
end = end.next
n--
}
const left = start.next, right = end.next
// 截断
start.next = null
end.next = null
// 使用基础版反转链表
reverseList(left)
// 再拼接
start.next = end
left.next = right
return newHead.next
// 需要遍历链表两次
}
function reverseList(head) {
let pre = null, cur = head
while (cur) {
const next = cur.next
cur.next = pre
pre = cur
cur = next
}
}
方法②:头插法
function reverseBetween(head, m, n) {
// 一次遍历,头插法
let newHead = new ListNode(0)
newHead.next = head
let pre = newHead
// 指向start的前一个节点
for (let i = 1; i < m; i++) {
pre = pre.next
}
let cur = pre.next
for (let j = 0; j < n - m; j++) {
let next = cur.next
cur.next = next.next
next.next = pre.next
pre.next = next
}
return newHead.next
}
3. 链表中的节点每k个一组翻转
链表中的节点每k个一组翻转
输入:{1,2,3,4,5},2
输出:{2,1,4,3,5}
方法:对每一组执行题目1的反转
function reverseKGroup(head, k) {
let newHead = new ListNode(0)
newHead.next = head
let pre = newHead,
end = newHead
while (end.next) {
// end移动到待翻转的末尾节点
for (let i = 0; i < k && end !== null; i++) {
end = end.next
}
if (end === null) {
break
}
// start:翻转的起始节点,next:翻转的最后一个节点的下一个节点
let start = pre.next,
next = end.next
// 截断
end.next = null
// 翻转
pre.next = reverseList(start)
// 拼接
start.next = next
// 重新定位
pre = start
end = pre
}
return newHead.next
}
function reverseList(head) {
let pre = null,
cur = head
while (cur) {
let next = cur.next
cur.next = pre
pre = cur
cur = next
}
return pre
}
环形链表
1. 判断链表中是否有环
判断链表中是否有环
输出:true or false
方法:快慢指针
function hasCycle(head) {
if (head === null) {
return false
}
// 快慢指针
let slow = head, fast = head
// 循环条件:快指针有下个和下下个节点
while (fast.next && fast.next.next) {
slow = slow.next
fast = fast.next.next
// 快慢指针相遇,则代表有环
if (slow === fast) return true
}
return false
}
2. 链表中环的入口结点
链表中环的入口结点
方法:先通过题目1的方法确定相遇的节点,然后一个节点从头节点发出,另一个节点从相遇节点出发,再次相遇的节点即为入环点
function EntryNodeOfLoop(pHead) {
// 快慢指针
// 先确定相遇的节点
if (pHead === null) {
return null
}
let slow = pHead, fast = pHead
while (fast.next && fast.next.next) {
slow = slow.next
fast = fast.next.next
// 相遇之后,一个节点从head触发,还有一个从相遇节点出发,他俩相遇的节点就是入环点
if (slow === fast) {
let start = pHead
while (start !== slow) {
start = start.next
slow = slow.next
}
return start
}
}
return null
}
合并链表
1. 合并两个排序的链表
合并两个排序的链表
输入:
{1,3,5},{2,4,6}
输出:
{1,2,3,4,5,6}
方法:递归
function Merge(pHead1, pHead2) {
// 如果有空链表,则直接返回另一个非空链表
if (pHead1 === null) {
return pHead2
}
if (pHead2 === null) {
return pHead1
}
if (pHead1.val <= pHead2.val) {
pHead1.next = Merge(pHead1.next, pHead2)
return pHead1
} else {
pHead2.next = Merge(pHead1, pHead2.next)
return pHead2
}
}
2. 合并k个已排序的链表
合并k个已排序的链表
输入:
[{1,2,3},{4,5,6,7}]
返回值:
{1,2,3,4,5,6,7}
方法:分治,两两合并
function mergeKLists(lists) {
// 分治
let len = lists.length
if (len === 0) return null
function mergeNlist(start, end) {
if (start === end) return lists[start]
let mid = (start + end) >> 1
let l1 = mergeNlist(start, mid)
let l2 = mergeNlist(mid + 1, end)
return Merge(l1, l2)
}
return mergeNlist(0, len - 1)
}
// 合并两个有序链表
function Merge(l1, l2) {
if (l1 === null) {
return l2
}
if (l2 === null) {
return l1
}
if (l1.val <= l2.val) {
l1.next = Merge(l1.next, l2)
return l1
} else {
l2.next = Merge(l1, l2.next)
return l2
}
}
单链表的排序
单链表的排序
输入:{1,3,2,4,5}
返回值:{1,2,3,4,5}
方法:分成两个链表,然后使用两个链表合并的方法
function sortInList( head ) {
// 分成两个链表,然后使用两个链表合并的方法
if(!head || !head.next) return head
let slow = head, fast = head
// preSlow始终是slow的前一个节点
let preSlow = null
while (fast && fast.next) {
preSlow = slow
slow = slow.next
fast = fast.next.next
}
// 截断,head到preslow,slow到末尾
preSlow.next = null
// 递归调用,因为不是有序的
let l1 = sortInList(head)
let l2 = sortInList(slow)
return mergeList(l1, l2)
// 合并【有序】链表
function mergeList(l1, l2){
if(l1 === null){
return l2
}
if(l2 === null){
return l1
}
if(l1.val <= l2.val){
l1.next = mergeList(l1.next, l2)
return l1
} else {
l2.next = mergeList(l1, l2.next)
return l2
}
}
}
倒数第N个节点
1. 链表中倒数最后k个结点
链表中倒数最后k个结点
输入:{1,2,3,4,5},2
返回值:{4,5}
方法:快慢指针,注意特判
function FindKthToTail(pHead, k) {
// 快慢指针
let slow = pHead, fast = pHead
for (let i = 0; i < k; i++) {
// 如果快指针为空,则表示链表长度小于k,返回null
if (fast === null) {
return null
}
fast = fast.next
}
while (fast) {
fast = fast.next
slow = slow.next
}
return slow
}
2. 删除链表的倒数第n个节点
删除链表的倒数第n个节点
输入:{1,2},2
返回值:{2}
方法:快慢指针,特判删除头节点的情况
function removeNthFromEnd(head, n) {
let fast = head, slow = head
for (let i = 0; i < n; i++) {
fast = fast.next
}
// 因为题目保证n一定是有效的,特判:删除头节点的情况
if (fast === null) {
slow = slow.next
return slow
}
while (fast.next) {
slow = slow.next
fast = fast.next
}
slow.next = slow.next.next
// 返回头节点
return head
}
两个链表的第一个公共结点
两个链表的第一个公共结点
方法:双指针,依次往后移,到达尾节点后重新回到头节点,根据到达公共节点走过的路程一致找到公共节点
function FindFirstCommonNode(pHead1, pHead2)
{
// write code here
if(!pHead1 || !pHead2) return null
let l1 = pHead1, l2 = pHead2
while(l1 !== l2){
l1 = (l1 === null ? pHead2 : l1.next)
l2 = (l2 === null ? pHead1 : l2.next)
}
return l1
}
链表相加
链表相加
输入:[9,3,7],[6,3]
返回值:{1,0,0,0}
方法:先进行链表反转,再做相加,注意有进位
function addInList( head1 , head2 ) {
// 链表反转
function reverseList(head){
let pre = null, cur = head
while(cur){
let next = cur.next
cur.next = pre
pre = cur
cur = next
}
return pre
}
// 链表求和
let l1 = reverseList(head1),
l2 = reverseList(head2)
let inNum = 0
// newHead始终不动
let newHead = new ListNode(null)
let p = newHead
while(true){
// 求和
let sum = inNum + (l1 ? l1.val : 0) + (l2 ? l2.val: 0)
inNum = Math.floor(sum / 10)
p.next = new ListNode(sum % 10)
p = p.next
if(l1) l1 = l1.next
if(l2) l2 = l2.next
if(!l1 && !l2 && !inNum){
break
}
}
return reverseList(newHead.next)
}
判断一个链表是否为回文结构
判断一个链表是否为回文结构
输入:{1,2,2,1}
返回值:true
方法:把链表变成数组,然后再比较
function isPail( head ) {
// 转数组
const newArr = []
while(head){
newArr.push(head.val)
head = head.next
}
for(let i = 0, j = newArr.length - 1; i <= j; i ++, j--){
if(newArr[i] !== newArr[j]){
return false
}
}
return true
}
删除有序链表中重复的元素
删除有序链表中重复的元素-I
删除有序链表中重复的元素-I
输入:{1,1,2}
返回值:{1,2}
输入:
{1,1,2}
复制
返回值:
{1,2}
方法:有下一个节点且与下一个节点的值相同,有下下个节点的情况下,则指向下下个节点,否则指向null
function deleteDuplicates(head) {
// write code here
let cur = head
while (cur) {
// 如果有下一个节点且与下一个节点的值相同
if (cur.next && cur.val === cur.next.val) {
// 如果有下下个节点,则直接指向下下个节点,否则直接指向null
if (cur.next.next) {
cur.next = cur.next.next
} else {
cur.next = null
}
} else {
// 不相同则指向下一个节点
cur = cur.next
}
}
return head
}
删除有序链表中重复的元素-II
删除有序链表中重复的元素-II
输入:{1,2,2}
返回值:{1}
本题和 I 的区别:本题删除所有重复的数字
方法:当当前元素的值与后一节点的值一致时,记录后一节点,移动后一节点直至与当前值不同
function deleteDuplicates(head) {
// 删除重复的元素
let dummy = new ListNode(-9999)
dummy.next = head
let pre = dummy,
cur = head
while (cur && cur.next) {
if (cur.val === cur.next.val) {
let temp = cur.next
// 重复的元素全部删除
while (temp && temp.val === cur.val) {
temp = temp.next
}
pre.next = temp
cur = temp
} else {
pre = pre.next
cur = cur.next
}
}
return dummy.next
}
链表的奇偶重排
链表的奇偶重排
输入:{1,2,3,4,5,6}
返回值:{1,3,5,2,4,6}
方法:计数,为奇数时插入到奇数链表
function oddEvenList(head) {
let p = head
// 奇偶链表的虚拟头结点
let l1 = new ListNode(-1),
l2 = new ListNode(-1)
let p1 = l1,
p2 = l2
// 计数
let count = 1
while (p) {
// 当为奇数时,插入到奇链表,且奇链表节点往后移
if (count % 2 === 1) {
p1.next = p
p1 = p1.next
} else {
p2.next = p
p2 = p2.next
}
p = p.next
count++
}
// 切断偶链表
p2.next = null
p1.next = l2.next
return l1.next
}