(一)数字在升序数组中出现的次数
主要考察二分查找,建议背诵。唯一的区别在二分查找条件这一行代码。
def GetNumberOfK(self , data: List[int], k: int) -> int:
# write code here
return self.upper_bound(k, data)-self.lower_bound(k, data)
def lower_bound(self, k, data):
left = 0
right = len(data)
while left <right:
mid = int((left +right)/2)
# 二分查找的目标位置:第一个大于等于k的数
if data[mid]>=k:
right = mid
else:
left = mid+1
return right
def upper_bound(self, k, data):
left = 0
right = len(data)
while left <right:
mid = int((left+right)/2)
# 二分查找的目标位置:第一个大于k的数
if data[mid] > k:
right = mid
else:
left = mid +1
return right
(二)旋转数组的最小数字
极端例子:[1,0,1,1,1] 和 [1,1,1,0,1]
只要能够建立mid与left,right的关系,二分查找也适用,不仅仅是顺序数组。
这里最大的疑虑还是出现在mid的数值如果与端点相同,如何处理?可以选择right-1的方式,逐步缩小区间,不会漏掉结果。
def minNumberInRotateArray(self , rotateArray: List[int]) -> int:
# write code here
left=0
right=len(rotateArray)-1
while right>left:
mid=(right+left)//2
if rotateArray[mid]<rotateArray[right]:
right=mid
elif rotateArray[mid]>rotateArray[right]:
left=mid+1
else:
right-=1
return rotateArray[right]
(三)字符串的全排列
递归的解法:空间复杂度 O(n!),时间复杂度 O(n!)
def Permutation(self , str: str) -> List[str]:
# write code here
if len(str)<=1:
return str
res = []
self.helper(str,'',res)
return res
def helper(self,array,solution,res):
if len(array)==0:
if solution not in res:
res.append(solution)
return
for i in range (len(array)):
newarray=array[:i]+array[i+1:]#删除第一个元素
newsolution=solution+array[i]#加入新元素
self.helper(newarray,newsolution,res)#寻找剩余元素的排列组合
常用的排序算法,有冒泡排序o(n2),选择排序o(n2),插入排序o(n2),归并排序o(nlogn),希尔排序o(nlogn),快速排序o(nlogn)
冒泡排序:
def bubble_sort(alist):
length = len(alist)
for i in range(length - 1):
# i表示比较多少轮
for j in range(length - i - 1):
# j表示每轮比较的元素的范围,因为每比较一轮就会排序好一个元素的位置,
# 所以在下一轮比较的时候就少比较了一个元素,所以要减去i
if alist[j] > alist[j + 1]:
alist[j], alist[j + 1] = alist[j + 1], alist[j]
选择排序: 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置。
插入排序:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
归并排序:归并排序中递归地将待排序列表进行拆分,直到拆分成只有一个元素的列表,然后调用merge(left_array, right_array)函数进行合并,这样依次递归合并,最终实现归并排序。
def merge_sort(array):
if len(array) == 1:
return array
left_array = merge_sort(array[:len(array)//2])
right_array = merge_sort(array[len(array)//2:])
return merge(left_array, right_array)
def merge(left_array, right_array):
left_index, right_index, merge_array = 0, 0, list()
while left_index < len(left_array) and right_index < len(right_array):
if left_array[left_index] <= right_array[right_index]:
merge_array.append(left_array[left_index])
left_index += 1
else:
merge_array.append(right_array[right_index])
right_index += 1
merge_array = merge_array + left_array[left_index:] + right_array[right_index:]
return merge_array
希尔排序(缩小增量排序):固定间隔的几个元素之间排序,然后再缩小这个间隔,直至为1,就成了有序数列。计算复杂度为o(logn~o(n2))
def shell_sort(arr):
gap = len(arr) // 2
while gap > 0:
for i in range(gap, len(arr)):
j = i
current = arr[i]
while j - gap >= 0 and current < arr[j - gap]:
arr[j] = arr[j - gap]
j -= gap
arr[j] = current
gap //= 2
return arr
快速排序:给基准数据找其正确索引位置的过程。
def quick_sort(alist, start, end):
"""快速排序"""
if start >= end: # 递归的退出条件
return
mid = alist[start] # 设定起始的基准元素
low = start # low为序列左边在开始位置的由左向右移动的游标
high = end # high为序列右边末尾位置的由右向左移动的游标
while low < high:
# 如果low与high未重合,high(右边)指向的元素大于等于基准元素,则high向左移动
while low < high and alist[high] >= mid:
high -= 1
alist[low] = alist[high] # 走到此位置时high指向一个比基准元素小的元素,将high指向的元素放到low的位置上,此时high指向的位置空着,接下来移动low找到符合条件的元素放在此处
# 如果low与high未重合,low指向的元素比基准元素小,则low向右移动
while low < high and alist[low] < mid:
low += 1
alist[high] = alist[low] # 此时low指向一个比基准元素大的元素,将low指向的元素放到high空着的位置上,此时low指向的位置空着,之后进行下一次循环,将high找到符合条件的元素填到此处
# 退出循环后,low与high重合,此时所指位置为基准元素的正确位置,左边的元素都比基准元素小,右边的元素都比基准元素大
alist[low] = mid # 将基准元素放到该位置,
# 对基准元素左边的子序列进行快速排序
quick_sort(alist, start, low - 1) # start :0 low -1 原基准元素靠左边一位
# 对基准元素右边的子序列进行快速排序
quick_sort(alist, low + 1, end) # low+1 : 原基准元素靠右一位 end: 最后
堆排序:是一种选择排序算法,最好和最坏的计算复杂度都是logn,不稳定。核心步骤是大根堆调整,将堆的末端子节点作调整,使得子节点永远小于父节点。
def build_max_heap(heap): # 构造一个堆,将堆中所有数据重新排序
heapSize = len(heap)
for i in range((heapSize-2)//2,-1,-1): # 自底向上建堆
max_heapify(heap, heapSize, i)
def max_heapify(heap,heapSize,root): # 调整列表中的元素并保证以root为根的堆是一个大根堆
"""
给定某个节点的下标root,这个节点的父节点、左子节点、右子节点的下标都可以被计算出来。
父节点:(root-1)//2
左子节点:2*root + 1
右子节点:2*root + 2 即:左子节点 + 1
"""
left = 2*root + 1
right = 2*root + 2
larger = root
# 小根堆只需要把下面and后面的条件改成:heap[larger] < heap[left] 和 heap[larger] < heap[right]
# 当然,为了能见名知义,可以把larger换成smaller
if left < heapSize and heap[larger] < heap[left]:
larger = left
if right < heapSize and heap[larger] < heap[right]:
larger = right
if larger != root: # 如果做了堆调整则larger的值等于左节点或者右节点的值,这个时候做堆调整操作,交换此时的最大值到root节点
heap[larger], heap[root] = heap[root], heap[larger]
# 递归的对子树做调整
max_heapify(heap, heapSize, larger)
def heap_sort(heap): # 将根节点取出与最后一位做对调,对前面len-1个节点继续进行堆调整过程。
build_max_heap(heap)
# 调整后列表的第一个元素就是这个列表中最大的元素,将其与最后一个元素交换,然后将剩余的列表再递归的调整为最大堆
for i in range(len(heap)-1, -1, -1):
heap[0], heap[i] = heap[i], heap[0]
max_heapify(heap, i, 0)
(四)数组中的逆序对
分治的解法
def InversePairs(self , data: List[int]) -> int:
# write code here
return self.recur(data)%1000000007
def recur(self,arr):
if len(arr) <= 1:
return 0
b,s = [],[]
res,pre=0,arr[0]
for n in arr[1:]:
if n > pre:
b.append(n)
else:
s.append(n)
res += len(b) + 1
return res + self.recur(b) + self.recur(s)