链表类题目心得
由于链表存取值不便利的特性,所以在操作链表前要使用一些变量来暂存关键节点 解题时一定要在理清题目的思路后确定关键节点再进行链表的相关操作,避免丢失关键节点的引用
对于头结点的特殊情况处理,可以建立一个临时的头结点方便后续节点统一处理
反转从位置 m 到 n 的链表
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* 关键节点:
* 1. m的前一个节点 翻转后其next应指向原n位置的节点
* 2. m节点 翻转后其next应指向原n位置的后一个节点
* 3. n节点
* 4. n的后一个节点
*/
/**
* @param {ListNode} head
* @param {number} m
* @param {number} n
* @return {ListNode}
* 1 ≤ m ≤ n ≤ 链表长度。
*/
var reverseBetween = function(head, m, n) {
let change_len = n - m + 1 // 需要反转的节点数量
let pre_head = null // 用于后续判断是不是从第一个节点开始反转
let result = head
while (head && --m) {
pre_head = head
head = head.next
}
let rev_head = head // 缓存m位置的节点
// 对m->n之间的节点进行反转
let pre = null
while (head && change_len) {
let next = head.next
head.next = pre
pre = head
head = next
change_len--
}
// m->n反转完毕后 此时pre指向原n位置节点(此时位于m位置) head指向原n位置的后一个节点
// rev_head(原m位置)此时位于原n位置
rev_head.next = head
if (pre_head) {
pre_head.next = pre // 不是从第一个开始反转
} else {
result = pre
}
return result
};
删除排序链表中的重复元素
输入: 1->1->2->3->3
输出: 1->2->3
/**
* @param {ListNode} head
* @return {ListNode}
*/
var deleteDuplicates = function(head) {
let cur = head
while (cur && cur.next) {
if (cur.val === cur.next.val) {
cur.next = cur.next.next
} else {
cur = cur.next
}
}
return head
};
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。你应当保留两个分区中每个节点的初始相对位置。
输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5
/**
* 思路:建立一个较大元素组成的链表和一个较小元素组成的链表,然后拼接
* 关键节点:大链表首尾元素和小链表的首尾元素
*/
/**
* @param {ListNode} head
* @param {number} x
* @return {ListNode}
*/
var partition = function(head, x) {
let max_head = new ListNode(0)
let min_head = new ListNode(0)
let max_prt = max_head
let min_prt = min_head
while (head) {
let val = head.val
if (val >= x) {
max_prt.next = head
max_prt = head
} else {
min_prt.next = head
min_prt = head
}
head = head.next
}
min_prt.next = max_head.next
max_prt.next = null
return min_head.next
};
环形链表 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
/**
* @param {ListNode} head
* @return {ListNode}
*/
var detectCycle = function(head) {
if (!head || !head.next) {
return null
}
// 步骤一:使用快慢指针判断链表是否有环
let fast = head
let slow = head
let hasCycle = false
while (fast.next && fast.next.next) {
slow = slow.next
fast = fast.next.next
if (slow === fast) {
hasCycle = true
break
}
}
// 步骤二:若有环,找到入环开始的节点
if (hasCycle) {
let start = head
while (start !== slow) {
start = start.next
slow = slow.next
}
return start
} else {
return null
}
};
相交链表 找到两个单链表相交的起始节点。
/**
* 思路: 如果两个链表相交,那么相交点之后的长度是相同的。
* 让两个链表从同距离末尾同等距离的位置开始遍历。这个位置只能是较短链表的头结点位置。
* 为此,我们必须消除两个链表的长度差
* 1. 指针 pA 指向 A 链表,指针 pB 指向 B 链表,依次往后遍历
* 2. 如果 pA 到了末尾,则 pA = headB 继续遍历
* 3. 如果 pB 到了末尾,则 pB = headA 继续遍历
* 4. 比较长的链表指针指向较短链表head时,长度差就消除了
*/
/**
* @param {ListNode} headA
* @param {ListNode} headB
* @return {ListNode}
*/
var getIntersectionNode = function(headA, headB) {
let a = headA
let b = headB
if(a === null || b === null) return null
// 不相交最后就是null===null
while(a != b) {
a = a === null ? headB : a.next
b = b === null ? headA : b.next
}
return a
};
合并两个有序链表 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
/**
* 思路:创建临时链表,遍历两个链表进行比较然后与临时链表进行拼接
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var mergeTwoLists = function(l1, l2) {
let tempHead = new ListNode(0)
let cur = tempHead
while (l1 && l2) {
if (l1.val < l2.val) {
cur.next = l1
cur = cur.next
l1 = l1.next
} else {
cur.next = l2
cur = cur.next
l2 = l2.next
}
}
// 任一为空,直接连接另一条链表
if (!l1) {
cur.next = l2
}
if (!l2) {
cur.next = l1
}
return tempHead.next
};
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
// 递归解法
var mergeTwoLists = function(l1, l2) {
if(l1 === null) return l2
if(l2 === null) return l1
if(l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2)
return l1
} else {
l2.next = mergeTwoLists(l2.next, l1)
return l2
}
};