在工作与面试准备中,常常需要快速回顾基础算法的核心实现。本文正是一份为此类场景打造的 Go 语言算法速查手册。
本文不追求冗长的理论推导,而是聚焦于提供清晰、可运行的核心代码实现。内容涵盖了链表、字符串处理与排序算法等关键主题,旨在成为一份可以随时查阅、即拿即用的代码参考,助高效巩固基础。
排序
冒泡排序
基本思路:通过重复遍历要排序的列表,比较相邻的两个元素,如果它们的顺序错误,则交换它们的位置,直到所有元素排序完成。
时间复杂度:O(n^2),n 为数组的长度。
图解:

func bubbleSort(arr []int) {
for i := 0; i < len(arr)-1; i++ {
for j := 0; j < len(arr)-i-1; j++ {
// 比较相邻元素,如果前一个元素大于后一个元素,则交换它们
if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
}
选择排序
基本思路:循环遍历列表,每轮从未排序部分选择最小元素放到已排序部分的末尾,直到所有元素排序完成。
时间复杂度:O(n^2),n 为数组的长度。
图解:

func selectionSort(arr []int) {
for i := 0; i < len(arr)-1; i++ {
minIndex := i
for j := i + 1; j < len(arr); j++ {
// 找到最小的元素索引
if arr[j] < arr[minIndex] {
// 更新最小元素索引
minIndex = j
}
}
// 交换元素
arr[i], arr[minIndex] = arr[minIndex], arr[i]
}
}
插入排序
基本思路:将数组分为已排序部分和未排序部分,从未排序部分中取出一个元素,将其插入已排序部分的正确位置,直到所有元素排序完成。
时间复杂度:O(n^2),n为数组的长度。
图解:

func insertionSort(arr []int) {
for i := 1; i < len(arr); i++ {
key := arr[i]
j := i - 1
// 将大于 key 的元素向后移动
for j >= 0 && arr[j] > key {
arr[j+1] = arr[j]
j--
}
// 将 key 插入已排序部分的正确位置
arr[j+1] = key
}
}
链表
反转链表
题目链接:https://leetcode.cn/problems/reverse-linked-list/
基本思路:使用迭代法,维护三个指针:prev(前驱)、curr(当前)和 next(后继)。在遍历过程中,逐个改变节点的指向。
图解:
初始: 1 -> 2 -> 3 -> 4 -> 5 -> nil
第一步: nil <- 1 2 -> 3 -> 4 -> 5 (prev=nil, curr=1, next=2, 将curr.Next指向prev)
第二步: nil <- 1 <- 2 3 -> 4 -> 5 (指针移动,重复上述过程)
完成: nil <- 1 <- 2 <- 3 <- 4 <- 5 (新的头节点是5)
type ListNode struct {
Val int
Next *ListNode
}
func reverseList(head *ListNode) *ListNode {
var prev *ListNode
curr := head
for curr != nil {
nextTemp := curr.Next // 保存下一个节点
curr.Next = prev // 反转当前节点的指针
prev = curr // prev 前移
curr = nextTemp // curr 后移
}
return prev // 返回新的头节点
}
环形链表判断
题目链接:https://leetcode.cn/problems/linked-list-cycle/
基本思路:使用快慢指针(Floyd判圈算法)。快指针每次走两步,慢指针每次走一步。如果链表有环,快慢指针最终会相遇。
示例:
输入: head = [3,2,0,-4], pos = 1 (形成环)
快慢指针移动:
步1: slow=3, fast=2
步2: slow=2, fast=0
步3: slow=0, fast=2
步4: slow=-4, fast=-4 (相遇,说明有环)
func hasCycle(head *ListNode) bool {
if head == nil {
return false
}
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
if slow == fast {
return true
}
}
return false
}
合并两个有序链表
题目链接:https://leetcode.cn/problems/merge-two-sorted-lists/
基本思路:使用递归或迭代。迭代法创建一个哑节点(dummy node)作为新链表的起始点,比较两个链表的节点值,将较小的节点依次连接到新链表上。
示例:
输入: l1 = 1 -> 3 -> 5, l2 = 2 -> 4 -> 6
比较: 1 < 2 -> 新链表接1
比较: 3 > 2 -> 新链表接2
最终结果: 1 -> 2 -> 3 -> 4 -> 5 -> 6
func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode {
// 哑节点,简化边界处理
dummy := &ListNode{}
tail := dummy
for list1 != nil && list2 != nil {
if list1.Val <= list2.Val {
tail.Next = list1
list1 = list1.Next
} else {
tail.Next = list2
list2 = list2.Next
}
tail = tail.Next
}
// 将剩余非空链表直接接上
if list1 != nil {
tail.Next = list1
} else {
tail.Next = list2
}
return dummy.Next
}
删除链表的倒数第 N 个结点
题目链接:https://leetcode.cn/problems/remove-nth-node-from-end-of-list/
基本思路:使用双指针,让快指针先走 N 步,然后快慢指针同步向后移动。当快指针到达链表末尾时,慢指针正好指向待删除节点的前一个节点,执行删除操作。
图解:
链表: 1 -> 2 -> 3 -> 4 -> 5, n = 2
初始: fast = 1, slow = 1 (dummy)
fast 先走2步: fast = 3, slow = 1
同步移动: fast到5(nil)时,slow到3 (要删除4,需让3.Next指向5)
func removeNthFromEnd(head *ListNode, n int) *ListNode {
// 哑节点,防止删除头节点时出错
dummy := &ListNode{Next: head}
fast, slow := dummy, dummy
// 快指针先走 n 步
for i := 0; i < n; i++ {
fast = fast.Next
}
// 快慢指针同步移动,直到快指针到达末尾
for fast.Next != nil {
fast = fast.Next
slow = slow.Next
}
// 删除慢指针后面的节点
slow.Next = slow.Next.Next
return dummy.Next
}
相交链表判断
题目链接:https://leetcode.cn/problems/intersection-of-two-linked-lists/
基本思路:使用双指针技巧。指针A遍历链表A后遍历链表B,指针B遍历链表B后遍历链表A。如果两个链表相交,指针A和指针B会在相交点相遇(因为走的总长度相同)。
示例:
链表A: a1 -> a2 -> c1 -> c2
链表B: b1 -> b2 -> b3 -> c1 -> c2
指针A路径: a1 a2 c1 c2 b1 b2 b3 c1
指针B路径: b1 b2 b3 c1 c2 a1 a2 c1
在c1处相遇
func getIntersectionNode(headA, headB *ListNode) *ListNode {
if headA == nil || headB == nil {
return nil
}
pa, pb := headA, headB
for pa != pb {
if pa == nil {
pa = headB
} else {
pa = pa.Next
}
if pb == nil {
pb = headA
} else {
pb = pb.Next
}
}
// 要么相遇于交点,要么同时为nil(不相交)
return pa
}
字符串
反转字符串
题目链接:https://leetcode.cn/problems/reverse-string/
基本思路:使用双指针技术,一个指针从字符串开头向中间移动,另一个从末尾向中间移动,交换两个指针所指的字符。
图解:
初始: ['h','e','l','l','o']
第一步: ['o','e','l','l','h'] (交换h和o)
第二步: ['o','l','l','e','h'] (交换e和l)
完成: ['o','l','l','e','h']
func reverseString(s []byte) {
left, right := 0, len(s)-1
for left < right {
s[left], s[right] = s[right], s[left]
left++
right--
}
}
验证回文串
题目链接:https://leetcode.cn/problems/valid-palindrome/
基本思路:使用双指针技术,在忽略非字母数字字符且忽略大小写的情况下,判断字符串正反读是否相同。
示例:
输入: "A man, a plan, a canal: Panama"
处理: "amanaplanacanalpanama"
结果: true(是回文串)
func isPalindrome(s string) bool {
s = strings.ToLower(s)
left, right := 0, len(s)-1
for left < right {
// 跳过非字母数字字符
for left < right && !isAlnum(s[left]) {
left++
}
// 跳过非字母数字字符
for left < right && !isAlnum(s[right]) {
right--
}
// 判断字符是否相同
if s[left] != s[right] {
return false
}
left++
right--
}
return true
}
func isAlnum(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')
}
最长公共前缀
题目链接:https://leetcode.cn/problems/longest-common-prefix/
基本思路:纵向扫描,以第一个字符串为基准,比较所有字符串的对应位置字符。
示例:
输入: ["flower","flow","flight"]
比较:
第1列: f,f,f → 相同
第2列: l,l,l → 相同
第3列: o,o,i → 不同 → 结果: "fl"
func longestCommonPrefix(strs []string) string {
if len(strs) == 0 {
return ""
}
for i := 0; i < len(strs[0]); i++ {
for j := 1; j < len(strs); j++ {
// 遇到不同字符,返回结果
if i >= len(strs[j]) || strs[j][i] != strs[0][i] {
return strs[0][:i]
}
}
}
return strs[0]
}
字符串相加
题目链接:https://leetcode.cn/problems/add-strings/
基本思路:模拟竖式加法,从字符串末尾开始逐位相加,处理进位。
示例:
输入: "123", "456"
计算:
个位: 3+6=9 → 进位0, 当前位9
十位: 2+5=7 → 进位0, 当前位7
百位: 1+4=5 → 进位0, 当前位5
结果: "579"
func addStrings(num1 string, num2 string) string {
var res []byte
// 从末尾开始计算
i, j := len(num1)-1, len(num2)-1
// 进位
carry := 0
for i >= 0 || j >= 0 || carry != 0 {
var x, y int
if i >= 0 {
x = int(num1[i] - '0')
i--
}
if j >= 0 {
y = int(num2[j] - '0')
j--
}
sum := x + y + carry
res = append(res, byte(sum%10+'0'))
carry = sum / 10
}
// 反转结果
for l, r := 0, len(res)-1; l < r; l, r = l+1, r-1 {
res[l], res[r] = res[r], res[l]
}
return string(res)
}
最长不重复子串
题目链接:https://leetcode.cn/problems/longest-substring-without-repeating-characters/
基本思路:滑动窗口技术,维护窗口和字符位置映射,遇到重复字符时调整窗口起始位置。
示例:
输入: "abcabcbb"
窗口变化:
[a]bcabcbb → [ab]cabcbb → [abc]abcbb
发现a重复: a[bca]bcbb → 继续滑动...
最长子串: "abc" (长度3)
func lengthOfLongestSubstring(s string) int {
maxLen := 0
left := 0
charIndex := make(map[byte]int)
for right := 0; right < len(s); right++ {
// 遇到重复字符,更新窗口起始位置
if idx, exists := charIndex[s[right]]; exists && idx >= left {
left = idx + 1
}
// 维护字符位置映射
charIndex[s[right]] = right
if right-left+1 > maxLen {
maxLen = right - left + 1
}
}
return maxLen
}
评论