1. lc 23 - 合并K个升序链表

题目来源

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

1.1 解题思路

分治合并 - 将 k 个链表配对并将同一对中的链表合并; - 第一轮合并以后,k 个链表被合并成了 k/2 个链表 - 重复这一过程,链表合并为 k/4, k/8, 直到我们得到了最终的有序链表。

具体流程图:

1.2 时间复杂度

  • 时间复杂度:考虑递归「向上回升」的过程——第一轮合并 k/2 组链表,每一组的时间代价是 O(2n);第二轮合并 k/4 组链表,每一组的时间代价是 O(4n) ...... 递归的每一层是O(kn), 一共log(k)层。所以总的时间代价是 O (∑ k/2^i × 2^i*n) = O(kn×logk),故渐进时间复杂度为 O(kn×logk)。
  • 空间复杂度:一共log(k)层递归。递归会使用到 O(logk) 空间代价的栈空间。

1.3 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
if len(lists)==0 : # k=0时,返回None
return None
res = lists

while len(res) > 1:
merge_time = len(res)//2 # 标记合并次数
if len(res)%2 == 1: merge_time += 1 # 奇数的lists,合并次数再加一,最后一位直接转给下一层递归
tmp = []
for i in range(merge_time):
if (i+1)*2 <= len(res):
# print(i,res[i*2],res[i*2+1])
tmp.append(self.merge(res[i*2],res[i*2+1]))
else: # 最后一位直接转给下一层递归
# print(i,res[-1])
tmp.append(res[-1])
# print(tmp)
res = tmp
return res[0] # 分治合并后的结果存储在首位

# 两有序链表合并
def merge(self, l1: List[ListNode],l2: List[ListNode] ):
move = dummy_node = ListNode(10086)
while l1 or l2:

if not l1:
move.next = l2
l2 = l2.next
elif not l2:
move.next = l1
l1 = l1.next
elif l1.val <= l2.val and l1 and l2:
move.next = l1
l1 = l1.next
elif l1.val > l2.val and l1 and l2:
move.next = l2
l2 = l2.next
move = move.next
# print(dummy_node.next)
return dummy_node.next

2. lc 26 - 删除排序数组中的重复项

题目来源

给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

2.1 解题思路

简单题。删除重复元素,返回改动后列表长度。

2.2 时间复杂度

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

2.3 代码

1
2
3
4
5
6
7
8
9
10
11
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
pointer = 0
tmp = float('inf')
while pointer < len(nums):
if nums[pointer] != tmp:
tmp = nums[pointer]
pointer += 1
else:
del nums[pointer]
return len(nums)

3. lc 33 - 搜索旋转排序数组

题目来源

3.1 解题思路

看到排序问题,就想到二分查找。二分查找 难就难在,arr[mid]跟谁比。我们的目的是:当进行一次比较时,一定能够确定答案在mid的某一侧。

由于数组由两部分组成,左边递增区域,和右边递增区域。可以感知到,分2种情况讨论。

第一种情况:mid的值在左边区域内,我们可以判断,1)当target在left值和mid值之间时,target在一个显示的递增区间。我们将在left值和mid值之间进行二分查找;2)否则,我们在mid值到right值之间进行二分查找。

第二种情况:mid的值在右边区域内,我们可以判断,1)当target在mid值和right值之间时,target在一个显示的递增区间。我们将在mid值和right值之间进行二分查找;2)否则,我们在left值到mid值之间进行二分查找。

3.2 时间复杂度

  • 时间复杂度:O(logn)
  • 空间复杂度:O(1)

3.3 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution:
def search(self, nums: List[int], target: int) -> int:
if not nums: return -1
left, right = 0, len(nums)-1
while left <= right:
mid = (left + right)//2
if target == nums[mid]:
return mid
if nums[mid] < nums[left]: # 中间值在右边区域
if nums[mid] <= target and target <= nums[right]: # target 在mid 和 right 中间
left = mid + 1
else:
right = mid - 1
elif nums[mid] >= nums[left]: # 中间值在左边区域
if nums[left] <= target and target <= nums[mid]: # target 在 left 和 mid 中间
right = mid - 1
else:
left = mid + 1
return -1