本文深入探讨了多线程编程中两种核心的锁机制:悲观锁和乐观锁。内容涵盖了互斥锁、自旋锁、读写锁的原理和适用场景,并详细对比了悲观锁与乐观锁的思想、实现方式及优缺点,帮助你更好地理解并发控制,应对面试挑战。

Read more »

问题描述 给你链表的头结点 head,请将其按 升序 排列并返回 排序后的链表。 示例 示例 1: 12输入:head = [4,2,1,3]输出:[1,2,3,4] 示例 2: 12输入:head = [-1,5,3,4,0]输出:[-1,0,3,4,5] 示例 3: 12输入:head = []输出:[] 提示 链表中节点的数目在范围 [0, 5 * 10^4] 内 -10^5 <= Node.val <= 10^5 进阶 你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗? 解题思路 要排序链表,最直观的方法是先转换成数组,排序后再转回链表。但是这种方法需要 O(n) 的额外空间。如果要满足进阶要求,我们需要使用对链表友好的归并排序。 对于链表排序,可以考虑两种归并排序实现方式: 自顶向下:递归实现,空间复杂度为 O(log n) 自底向上:迭代实现,空间复杂度为 O(1) 方法一:自底向上归并排序 自底向上的归并排序是一种非递归的归并排序,它的基本思想是: 先将链表拆分成长度为 1 的多个子链表 然后两两合并成长度为 2 的子链表 再两两合并成长度为 4 的子链表 以此类推,直到整个链表排序完成 这种方法的空间复杂度是 O(1),满足进阶要求。 代码实现(优化版) 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102/** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */func sortList(head *ListNode) *ListNode { // 处理空链表或单节点链表 if head == nil || head.Next == nil { return head } // 计算链表长度 length := getLength(head) // 创建哑节点,简化边界情况处理 dummy := &ListNode{Next: head} // 自底向上归并排序 // 每次迭代子链表长度乘以2(1->2->4->8...) for subLength := 1; subLength < length; subLength <<= 1 { prev, curr := dummy, dummy.Next // 每轮迭代处理整个链表 for curr != nil { // 第一个子链表的头节点 head1 := curr // 切割第一个子链表 for i := 1; i < subLength && curr != nil && curr.Next != nil; i++ { curr = curr.Next } // 第二个子链表的头节点 head2 := curr.Next curr.Next = nil // 断开第一个子链表 curr = head2 // 切割第二个子链表 for i := 1; i < subLength && curr != nil && curr.Next != nil; i++ { curr = curr.Next } // 记录下一次要处理的节点 var next *ListNode = nil if curr != nil { next = curr.Next curr.Next = nil // 断开第二个子链表 } // 合并两个子链表 merged := mergeTwoLists(head1, head2) prev.Next = merged // 移动prev到合并后链表的末尾 for prev.Next != nil { prev = prev.Next } // 处理下一对子链表 curr = next } } return dummy.Next}// 获取链表长度func getLength(head *ListNode) int { length := 0 for node := head; node != nil; node = node.Next { length++ } return length}// 合并两个有序链表func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode { dummy := &ListNode{} curr := dummy for l1 != nil && l2 != nil { if l1.Val < l2.Val { curr.Next = l1 l1 = l1.Next } else { curr.Next = l2 l2 = l2.Next } curr = curr.Next } // 连接剩余部分 if l1 != nil { curr.Next = l1 } else { curr.Next = l2 } return dummy.Next} 方法二:自顶向下归并排序 自顶向下的归并排序是一种递归实现,它的基本思想是: 使用快慢指针找到链表中点,将链表分为两半 递归排序两个子链表 合并两个排序后的子链表 这种方法的空间复杂度是 O(log n),因为递归栈的深度是 log n。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950func sortList(head *ListNode) *ListNode { // 递归终止条件 if head == nil || head.Next == nil { return head } // 使用快慢指针找到链表中点 slow, fast := head, head.Next for fast != nil && fast.Next != nil { slow = slow.Next fast = fast.Next.Next } // 切分链表 mid := slow.Next slow.Next = nil // 递归排序两个子链表 left := sortList(head) right := sortList(mid) // 合并排序后的子链表 return mergeTwoLists(left, right)}// 合并两个有序链表func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode { dummy := &ListNode{} curr := dummy for l1 != nil && l2 != nil { if l1.Val < l2.Val { curr.Next = l1 l1 = l1.Next } else { curr.Next = l2 l2 = l2.Next } curr = curr.Next } // 连接剩余部分 if l1 != nil { curr.Next = l1 } else { curr.Next = l2 } return dummy.Next} 方法比较 方面 自底向上(迭代) 自顶向下(递归) 时间复杂度 O(n log n) O(n log n) 空间复杂度 O(1) O(log n) 优点 常数空间复杂度 代码简洁易懂 缺点 实现较复杂 需要额外的递归栈空间 推荐度 ★★★★★ ★★★★☆ 错误分析与总结 在尝试实现自底向上归并排序时,我遇到了几个典型的链表操作边界问题。以下是我的错误代码和具体分析,希望能帮助我(以及他人)避免重蹈覆辙。 初始错误代码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970func sortList(head *ListNode) *ListNode { if head == nil || head.Next == nil { return head } dummyHead := &ListNode{} dummyHead.Next = head length := 0 cur := head for cur != nil { cur = cur.Next length++ } for k := 1; k < length; k <<= 1 { prev, cur := dummyHead, dummyHead.Next for cur != nil { head1 := cur // 这里应该是1开始 for i := 0; i < k && cur.Next != nil; i++ { cur = cur.Next } head2 := cur.Next cur.Next = nil // 可能会带来cur = nil cur = head2 for i := 0; i < k && cur != nil && cur.Next != nil; i++ { cur = cur.Next } var next *ListNode if cur != nil { next = cur.Next cur.Next = nil } prev.Next = mergeSorted(head1, head2) for prev.Next != nil { prev = prev.Next } // 这里没必要加,因为后面的处理完自然会加上 prev.Next = next cur = next } } return dummyHead.Next}func mergeSorted(head1, head2 *ListNode) *ListNode { dummyHead := &ListNode{} cur := dummyHead for head1 != nil && head2 != nil { if head1.Val < head2.Val { cur.Next = h...
Read more »