准备重启尘封一年的博客作为学习笔记,看看自己能坚持多久。
最近会记录做过的算法题,语言描述只用于会意,仅供参考。
文章目录
0.从尾到头获取链表的值(不是反转链表)
解答思路
- 最直观的想法是,遍历链表求出长度,创建空数组,再遍历链表将每个节点的值反向填进数组
- 递归解法很简洁,思路类似于树的后根遍历(先根->递归过程自顶向下,后根->递归过程自底向上),从想象一棵单叉的树(其实就是链表),叶节点向根节点前进,就能得到反向的顺序
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 返回从尾部到头部的列表值序列,例如[1,2,3]
def printListFromTailToHead(self, listNode):
if listNode is None:
return []
return self.printListFromTailToHead(listNode.next) + [listNode.val]
1.寻找/删除单链表倒数第k个节点
- 使用两个指针(forward backward),起始时都指向头节点,首先让forward指针向前k步,到达第k+1个节点,然后让两指针以相同速度前进,直到forward为None,此时的backward指向的就是倒数第k个节点。
- 对于删除问题,需要额外引入一个指针,指向backward之前的节点。此时会出现特殊情况:如果倒数第k个节点是链表的头节点,则需要特殊处理。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
// 特判
if (head.next == null && n == 1) {
return null;
}
ListNode a = head;
ListNode b = head;
ListNode pre_b = null;
// 先走a
for (int i=0; i<n; i++) {
a = a.next;
}
// ab同时走,直到a到达末尾
while (a != null) {
pre_b = b;
a = a.next;
b = b.next;
}
// 得到b是待删除结点
// 如果b是首个元素,返回b.next
if (pre_b == null) {
return b.next;
}
// b非首个元素
pre_b.next = b.next;
return head;
}
}
3.寻找单链表的中点
- 使用快慢指针法。每次步进fast走两步,slow走一步,当fast或fast.next指向None时,slow即为中点。如果链表长度为偶数,则slow指向中间并靠后的节点
- 注意循环判断条件
fast != null && fast.next != null
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode middleNode(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) { // 注意判断条件
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
4.判断链表是否成环 / 求成环链表的起点
- 快慢指针。如果fast最终能遇见None,则不成环。如果slow最终能遇见fast,则成环。
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (fast == slow) {
return true;
}
}
return false;
}
}
要求成环链表的起点,需要在fast和slow相遇时,将一个指针指向链表头,再让两指针以1步长前进直到相遇,相遇点即为环起点。
假设首次相遇时,fast比slow多走了k步,则这k步一定是在环内绕圈,即k是环长度的整数倍。此时让一个指针回到头节点,共同前进k-m步后一定会在环起点相遇。
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next;
if (fast != null) {
fast = fast.next;
}
// 有环
if (fast == slow) {
fast = head;
} else {
continue;
}
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}
// 无环
return null;
}
}
5.判断两个链表是否相交
- 关键在于解决两个链表的对齐问题。解决办法是,将链表A的末尾指向链表B的头节点,同理B的末尾指向A的头节点。使用两个指针a、b分别从A和B的头节点开始步进,直到遇见相同节点。
- 直观思路:
对于a,走过A的独有部分->相交部分->B的独有部分->相交部分
对于b,走过B的独有部分->相交部分->A的独有部分->相交部分 - 如果两个链表不相交,则 pa 和 pb 在对方末尾的 None 相等,返回 None
class Solution(object):
def getIntersectionNode(self, headA, headB):
"""
:type head1, head1: ListNode
:rtype: ListNode
"""
pa = headA
pb = headB
while (pa != pb):
pa = pa.next if pa is not None else headB
pb = pb.next if pb is not None else headA
return pa
6.反转整个链表
迭代法
对于每个节点分为三个步骤:
temp = node.next // 记录当前节点的下个节点,防止修改后找不到
node.next = pre // 反转
pre = node // 步进
node = temp // 步进
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
// 迭代
ListNode temp = head; // 从head开始处理,head.next最后指向null
ListNode pre_temp = null;
while (temp != null) {
ListNode next_node = temp.next; // 保存
temp.next = pre_temp; // 更改指针
pre_temp = temp; // 步进
temp = next_node;
}
return pre_temp;
}
}
递归法**
- 来自拉不拉东的算法小抄:写递归算法的关键是,明确函数的定义是什么,“相信定义”,用其推导出最终结果,而不陷入递归的细节。搞清楚根节点该做什么,剩下的交给前中后序遍历
- 写递归时要有遍历树的思想。下面的代码即为树的后根遍历
- 反转等操作(类似题1)都能用后根的思想解决
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
# 递归出口
if head is None or head.next is None:
return head
reversed = self.reverseList(head.next)
head.next.next = head
head.next = None
return reversed
7.反转链表的从第left到第right个节点
迭代法
- 首先定位到第left个节点,记录边界,并执行right-left次反转
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def reverseBetween(self, head, left, right):
"""
:type head: ListNode
:type left: int
:type right: int
:rtype: ListNode
"""
dummy = ListNode(val=-1, next=head)
pre_node = dummy
temp_node = head
# 移动到第left个节点
for i in range(left-1):
pre_node = temp_node
temp_node = temp_node.next
# 记录左侧未被反转的最后一个节点,和被反转的第一个节点
forward_last_node = pre_node
reverse_first_node = temp_node
# 从开始反转的第二个节点开始,改变连接关系
pre_node = pre_node.next
temp_node = temp_node.next
# 执行反转
for i in range(right-left):
temp = temp_node.next
temp_node.next = pre_node
pre_node = temp_node
temp_node = temp
# 连接
reverse_first_node.next = temp_node
forward_last_node.next = pre_node
return dummy.next
递归法
首先写一个反转头部前N个节点的函数reverseN
ListNode reverseBetween(ListNode head, int m, int n) {
// 递归出口
if (m == 1) {
return reverseN(head, n);
}
head.next = reverseBetween(head.next, m - 1, n - 1);
return head;
}
8.K个一组反转列表
class Solution:
def reverseKGroup(self , head , k ):
# write code here
if not head or k == 1:
return head
dummy = ListNode(-1)
node = head
last_left = None
while (self.enough(node, k)): # 每次反转k个
pre = node
cur = node.next
# 执行反转
for _ in range(k - 1):
temp = cur.next
cur.next = pre
pre = cur
cur = temp
if node == head: # 记录返回结果
dummy.next = pre
# 处理边界
if last_left:
last_left.next = pre
last_left = node
node = cur
if last_left: # 连接最后不到k个不反转的
last_left.next = node
return dummy.next if last_left else head
# 判断从当前节点开始是否够k个
def enough(self, node, k):
cnt = 0
while node:
node = node.next
cnt += 1
if cnt >= k:
return True
return False
就这么做!
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode prevLeftNode = null; // 记录上一组反转后的最后节点
ListNode res = null; // 记录结果
ListNode prev = head;
ListNode curr = head.next;
while (this.hasKNodesLeft(prev, k)) {
ListNode groupHead = prev; // 记录当前组反转前的首个节点
// 组内反转k-1次
for (int i = 0; i < k - 1; i++) {
ListNode temp = curr.next;
curr.next = prev;
prev = curr;
curr = temp;
}
// 更改和前一组的指针
if (prevLeftNode != null) {
prevLeftNode.next = prev; // 非第一组,改变指向
} else {
res = prev; // 第一组,记录结果
}
// 更改循环条件
prevLeftNode = groupHead;
prevLeftNode.next = curr; // 先把上一组的最后值指向下一组(可能不够k个)的首个位置,如果够k个再修改
prev = curr;
curr = curr == null ? null : curr.next; // 避免下一组无元素
}
return res;
}
/* 判断是否足够k个节点 */
public boolean hasKNodesLeft(ListNode node, int k) {
int count = 0;
while (node != null && count < k) {
count += 1;
node = node.next;
}
return count == k ? true : false;
}
}
9.判断回文链表
方法一,使用递归,使用树的后根遍历思想,实现栈“后进先出”的功能。
// 左侧指针
ListNode left;
boolean isPalindrome(ListNode head) {
left = head;
return traverse(head);
}
boolean traverse(ListNode right) {
if (right == null) return true;
boolean res = traverse(right.next);
// 后序遍历
res = res && (right.val == left.val);
left = left.next;
return res;
}
方法二,利用快慢指针寻找中点,反转中点以后的链表并进行比较,推荐
class Solution(object):
def isPalindrome(self , head ):
if not head:
return True
slow, fast = head, head
while (fast):
slow = slow.next
fast = fast.next
if fast:
fast = fast.next
# 反转后半部分
post = self.reverse(slow)
# 逐个比较
pre = head
while (pre and post):
if pre.val != post.val:
return False
pre = pre.next
post = post.next
return True
def reverse(self, node):
if not node:
return None
elif not node.next:
return node
r = self.reverse(node.next)
node.next.next = node
node.next = None
return r
10.复制复杂的链表
链表的每个节点都具有val, next, random属性。关键点是random如何获取——下标如何获取——借用list / 哈希表
"""
class Solution:
# 返回 RandomListNode
def Clone(self, pHead):
if not pHead:
return pHead
dummy = RandomListNode(-1)
head = RandomListNode(pHead.label)
dummy.next = head
# 先复制原链表,用两个数组分别记录新老链表的节点
olist = [pHead]
nlist = [head]
p = pHead.next
while (p):
head.next = RandomListNode(p.label)
head = head.next
nlist.append(head)
olist.append(p)
p = p.next
# 连接random
for i in range(len(olist)):
target = olist[i].random
if not target:
continue
idx = olist.index(target)
nlist[i].random = nlist[idx]
return dummy.next
更优的解法
class Solution {
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
List<Node> nodes = new ArrayList<>(); // 存放复制的节点
Map<Node, Integer> mapping = new HashMap<>(); // 用哈希表记录节点到下标的映射
// 复制值,并记录下标
Node node = head;
int index = 0;
while (node != null) {
nodes.add(new Node(node.val));
mapping.put(node, index);
index++;
node = node.next;
}
// 执行连接
node = head;
for (int i = 0; i < nodes.size(); i++) {
// 1.连接random
Node temp = nodes.get(i);
if (node.random != null) {
int randomIndex = mapping.get(node.random);
temp.random = nodes.get(randomIndex);
}
// 2.连接next
if (i < nodes.size() - 1) {
temp.next = nodes.get(i + 1);
}
node = node.next;
}
return nodes.get(0);
}
}
11.链表加法
- 借助栈实现后端对齐
- 处理最后一步的进位问题
class Solution:
def addInList(self , head1 , head2 ):
# write code here
# 用栈
s1, s2 = [], []
while head1:
s1.append(head1)
head1 = head1.next
while head2:
s2.append(head2)
head2 = head2.next
post = None
plus = 0
while (s1 and s2):
n1, n2 = s1.pop(), s2.pop()
val = n1.val + n2.val + plus
plus = 1 if val >= 10 else 0
val -= 10 if val >= 10 else 0
node = ListNode(val)
node.next = post
post = node
if s1:
while s1:
n1 = s1.pop()
val = n1.val + plus
plus = 1 if val >= 10 else 0
val -= 10 if val >= 10 else 0
node = ListNode(val)
node.next = post
post = node
elif s2:
while s2:
n2 = s2.pop()
val = n2.val + plus
plus = 1 if val >= 10 else 0
val -= 10 if val >= 10 else 0
node = ListNode(val)
node.next = post
post = node
if plus == 1:
res = ListNode(1)
res.next = post
return res
return post
12.删除排序链表的重复节点
- 递归解法
class Solution {
int lastDuplicate = -999; // 记录上次发生重复的值
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) {
return head;
}
// 删除后续节点的重复值
ListNode deleted = deleteDuplicates(head.next);
// 判断当前节点是否和后续重复
if (head.val != lastDuplicate && (deleted == null || deleted.val != head.val)) { // 1.不重复
head.next = deleted;
return head;
} else { // 2.发生重复
lastDuplicate = head.val;
if (deleted == null || deleted.val != lastDuplicate) { // delete节点的值不参与重复,不删除delete节点
return deleted;
} else {
return deleted.next; // 删除detele节点
}
}
}
}
13. 排序链表
- 快慢指针寻找链表中点,左右递归执行排序
- 合并排序结果
class Solution {
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode fast = head;
ListNode slow = head;
ListNode slowPrev = slow;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slowPrev = slow;
slow = slow.next;
}
// 划分并排序
slowPrev.next = null;
ListNode l = sortList(head);
ListNode r = sortList(slow);
// 合并
ListNode dummy = new ListNode(-1);
ListNode curr = dummy;
while (l != null && r != null) {
if (l.val <= r.val) {
curr.next = l;
curr = l;
l = l.next;
} else {
curr.next = r;
curr = r;
r = r.next;
}
}
curr.next = (l == null) ? r : l;
return dummy.next;
}
}