学习笔记:学习算法第一天(链表)
·
链表基础概念
链表是一种线性数据结构,由一系列节点组成,每个节点包含数据域和指针域。与数组不同,链表在内存中非连续存储,通过指针实现逻辑上的顺序连接。
常见类型:
- 单链表:每个节点指向下一个节点,尾节点指向
NULL。 - 双链表:节点包含前驱和后继指针,支持双向遍历。
- 循环链表:尾节点指向头节点,形成闭环。
链表核心操作
插入与删除
- 插入:修改相邻节点的指针,时间复杂度通常为 $O(1)$(已知前驱节点)。
- 删除:调整指针跳过目标节点,注意处理头尾节点边界。
遍历与搜索
- 遍历:从头节点出发,依次访问每个节点直至
NULL。 - 搜索:线性查找,时间复杂度 $O(n)$。
求职应用场景
高频面试题
- 反转链表:通过迭代或递归实现指针方向反转。
- 检测环:快慢指针法(Floyd's Cycle Algorithm)。
- 合并有序链表:双指针比较节点值,逐步合并。
- 删除倒数第N个节点:快慢指针定位目标前驱。
实际应用
- 内存管理中的动态分配(如操作系统内核)。
- LRU缓存淘汰算法(结合哈希表实现)。
- 图的邻接表表示。
算法示例(golang)
1. 题目:
给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。
数据范围: 0≤n≤10000≤n≤1000
要求:空间复杂度 O(1)O(1) ,时间复杂度 O(n)O(n) 。
如当输入链表{1,2,3}时,
经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。
以上转换过程如下图所示:

解题思路:
双链表法
原链表 1->2->3->nil
新链表 nil
第一次: 1->2->3->nil
1->nil
第二次: 1->2->3->nil
2->1->nil
第三次: 1->2->3->nil
3->2->1->nil
具体代码:
func ReverseList(head *ListNode) *ListNode {
//首先判断链表是否为空
if head == nil {
return head
}
var newHead *ListNode = nil // 你口中的“新链表”
for head != nil { // head 是原链表头
next := head.Next // 先保存原链下一个节点
head.Next = newHead // 头插到新链表
newHead = head // 新链表头更新
head = next // 原链表头后移
}
return newHead
}
题目:
将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n)O(n),空间复杂度 O(1)。
例如:
给出的链表为 1→2→3→4→5→NULL, m=2,n=4,
返回 1→4→3→2→5→NULL.
解题思路:
可以看出这道题的解题思路是和上面那道题类似
我们需要找到m,n的位置然后将这段链表反转
具体代码:
func reverseBetween(head *ListNode, m int, n int) *ListNode {
// write code here
//首先判断链表是否为空和m和n是否相等
if head == nil || m == n {
return head
}
// 虚拟头节点,仅用于统一边界情况,不复制数据
dummy := &ListNode{Next: head}
prev := dummy
//将prev移动到m-1的位置
for i := 0; i < m-1; i++ {
prev = prev.Next
}
//开始反转的部分的头节点
start := prev.Next
then := start.Next
// 反转从第 m 个节点到第 n 个节点的部分
for i := m; i < n; i++ {
start.Next = then.Next
then.Next = prev.Next
prev.Next = then
then = start.Next
}
return dummy.Next
}
题目
将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表
如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样
你不能更改节点中的值,只能更改节点本身。
给定的链表是 1→2→3→4→5
对于 k=2 , 你应该返回 2→1→4→3→5
对于 k=3 , 你应该返回 3→2→1→4→5
解题思路
先判断给出的信息能不能完成要做的操作
再使用头插法分块完成链表反转
最后在递归
具体代码
func reverseKGroup(head *ListNode, k int) *ListNode {
if k <= 1 || head == nil {
return head
}
// 先测这一段够不够 k 个
tail := head
for i := 0; i < k; i++ {
if tail == nil { // 不足 k 个,直接返回
return head
}
tail = tail.Next
}
// 反转这 k 个:用头插法
var prev *ListNode = nil
curr := head
for i := 0; i < k; i++ {
next := curr.Next
curr.Next = prev
prev = curr
curr = next
}
// 此时 prev 是这一组的新头,curr 是下一组的头
// 递归处理后面,并把反转后的尾(原来的 head)连上去
head.Next = reverseKGroup(curr, k)
return prev
}
迭代法
func reverseKGroup(head *ListNode, k int) *ListNode {
if k <= 1 {
return head
}
dummy := &ListNode{Next: head}
groupPrev := dummy // 上一组的尾巴
for {
kth := groupPrev
// 数 k 步,看本组够不够
for i := 0; i < k && kth != nil; i++ {
kth = kth.Next
}
if kth == nil { // 不足 k 个,收工
break
}
groupNext := kth.Next // 记下下一组的头
prev, curr := groupPrev.Next, groupPrev.Next.Next
// 头插法反转本组(k-1 次即可)
for i := 0; i < k-1; i++ {
next := curr.Next
curr.Next = prev
prev = curr
curr = next
}
// 把反转后的段落重新接回去
tail := groupPrev.Next // 原来的头,现在是尾
tail.Next = groupNext
groupPrev.Next = kth // kth 现在是新头
groupPrev = tail // 继续下一组
}
return dummy.Next
}
学习建议
- 手写练习:手动模拟指针变化过程,加深理解。
- 复杂度分析:明确各操作的时间与空间复杂度。
- 题目拓展:尝试LeetCode题库(如 #206、#141、#21)。
链表是算法面试的基石,掌握其原理和变体对求职至关重要。
更多推荐


所有评论(0)