def hanoiProcess(n, fromStr, toStr, helpStr): if n == 1: print("Move 1 from " + fromStr + ' to ' + toStr) else: hanoiProcess(n - 1, fromStr, helpStr, toStr) print("Move " + str(n) + ' from ' + fromStr + ' to ' + toStr) hanoiProcess(n - 1, helpStr, toStr, fromStr) def printAllSub(str1, index, resultStr): """打印字符串所有的子序列""" if index == len(str1): print(resultStr) return printAllSub(str1, index + 1, resultStr) printAllSub(str1, index + 1, resultStr + str1[index]) def minPath1(matrix): return minPath1Process(matrix, 0, 0) def minPath1Process(matrix, i, j): res = matrix[i][j] if i == len(matrix) - 1 and j == len(matrix[0]) - 1: return matrix[i][j] if i == len(matrix) - 1 and j != len(matrix[0]) - 1: return res + minPath1Process(matrix, i, j + 1) if i != len(matrix) - 1 and j == len(matrix[0]) - 1: return res + minPath1Process(matrix, i + 1, j) return res + min(minPath1Process(matrix, i + 1, j), minPath1Process(matrix, i, j + 1)) def isSum1(arr1, aim): return isSumProcess1(arr1, 0, 0, aim) def isSumProcess1(arr1, index, sum, aim): if index == len(arr1): return sum == aim return isSumProcess1(arr1, index + 1, sum, aim) or isSumProcess1(arr1, index + 1, sum + arr1[index], aim) def getIndexOf(str1, str2): """使用KMP算法,检验str2是否是str1的子串""" index1, index2 = 0, 0 nextArr = getNextArray(str2) while index1 < len(str1) and index2 < len(str2): if str1[index1] == str2[index2]: index1 += 1 index2 += 1 elif nextArr[index2] == -1: index1 += 1 else: index2 = nextArr[index2] return index1 - index2 if index2 == len(str2) else -1 # 在求位置i的最长前缀时,用到了位置i-1的最长前缀。如果位置i-1的最长前缀长度是x,则在求位置i的最长前缀长度时, # 只需要检验位置x的字符和位置i-1的字符是否相等,如果相等,则位置i的最长前缀的长度就是x+1,否则继续以划分寻找。 # 例如str1="ababcababak",在寻找最后一个字符k的最长前缀长度时,先看位置9字符"a"的最长前缀是"abab",长度是4, # 而位置4的字符和位置9的字符并不相同,所以继续看位置4的字符"c"的最长前缀是"ab",长度为2,而位置2的字符"a" # 与位置9的字符相同,因此位置10的最长前缀是"aba",长度是3 def getNextArray(str1): """输入字符串,返回各位置字符串的最长前缀(或最长后缀)的长度""" if len(str1) == 1: return [-1] nextArr = [0] * len(str1) nextArr[0], nextArr[1] = -1, 0 index = 2 cn = 0 while index < len(nextArr): if str1[index - 1] == str1[cn]: cn += 1 nextArr[index] = cn index += 1 elif cn > 0: cn = nextArr[cn] else: nextArr[index] = 0 index += 1 return nextArr def manacherString(str1): # 将str1增添成['#', 'a', '#', 'b', '#', 'c', '#'] resList = [0] * (len(str1) * 2 + 1) index = 0 for i in range(len(resList)): if i & 1 == 0: resList[i] = '#' else: resList[i] = str1[index] index += 1 return resList # R:代表了遍历过程中所有回文半径中最靠右的边界。'#a#b#c#b#a#'从头遍历时,R分别是0, 2, 2, 4, 4, 10, 10, 10... # C:第一次取得最右边界的中心位置 # 在从头遍历的过程中,想要取得每个回文串的最大长度。 # if i > R : 则需要从i向两边暴力括,R和C相应变化 # if i < R : 有如下三种情况(对应i'的最大回文串在范围内,范围外,正好在范围边界) # tFabaktkabaFs...: 此时最大回文串"FabaktkabaF",中心位置C为字符't',当想求位置9的字符'b'的最大回文串长度时, # 由中心位置C求得i对应的位置i'(位置3的字符'b'),而i'的最大回文串在[L, R]范围中,因此pArr[i]=pArr[i'] # abcEcbatabcEcF...: 此时最大回文串"cEcbatabcEc",中心位置C为字符't',当相求位置11的字符'E'的最大回文串长度时, # 由于对应的i'(位置3的字符'E')最大回文串"abcEcba",不在整体最大范围[L, R]中,因此pArr[i]=R-i # aEaFaEaF...: 此时最大回文串"aEaFaEa",当相求位置5的字符"E"的最大回文串时,由于对应的i'(位置1的字符E)的最大回文串正好在整体 # 最大回文串的边界[L, R]的左边界,因此pArr[i]最低是R-i,还要在此基础上继续往外暴力扩,才能确定最终的该位置最大回文串长度 # 而代码中所写,不在明确划分是哪种情况,pArr[i]都会有一个最小值,然后每一个都会继续往外扩,如果是第二、第三种情况,会扩张失败,直接返回 # 其余都会正常扩,因此减少了整体代码。R从-1开始向右扩张,一旦扩到了最右边界,则会终止,整体时间复杂度为O(N)。 def maxLcpsLength(str1): """Manacher算法""" if str1 is None or len(str1) == 0: return 0 strArr = manacherString(str1) pArr = [0] * len(strArr) C, R = -1, -1 maxLen = 0 for i in range(len(strArr)): # 首先确保了每个点至少有一个回文串半径 pArr[i] = min(pArr[2 * C - i], R - i) if R > i else 1 # pArr[2 * C - i]为i关于最大回文串中心点C对应的i' while i + pArr[i] < len(strArr) and i - pArr[i] > -1: if strArr[i + pArr[i]] == strArr[i - pArr[i]]: pArr[i] += 1 else: break if i + pArr[i] > R: R = i + pArr[i] C = i maxLen = max(pArr[i], maxLen) return maxLen - 1 def shortestEnd(str1): """利用Manacher算法求解在字符串末尾添加最短字符串使整体构成回文串""" if str1 == "" or len(str1) == 0: return "" strArr = manacherString(str1) pArr = [0] * len(strArr) index, pR = -1, -1 maxContainsEnd = -1 for i in range(len(strArr)): pArr[i] = min(pArr[2 * index - i], pR - i) if pR > i else 1 while i + pArr[i] < len(strArr) and i - pArr[i] > -1: if strArr[i + pArr[i]] == strArr[i - pArr[i]]: pArr[i] += 1 else: break if i + pArr[i] > pR: pR = i + pArr[i] index = i if pR == len(pArr): maxContainsEnd = pArr[i] break res = [0] * (len(str1) - maxContainsEnd + 1) for i in range(len(res)): res[len(res) - 1 - i] = strArr[i * 2 + 1] return "".join(res) # 使用快速排序方法,随机选择一个数,应用partition过程,将数组排序成:小于、等于、大于。通过看等于区域的范围,如果k在小于区域内, # 则在小于区域内在随机选取一个数继续partition直至找到第k个数。同理如果在大的区域一样进行 # BFPRT算法:在无序数组中,找到第k个小的数(或大的数) # 1. 首先将数组进行分组,5个分为一组,最后不足5个成一组。 # 2. 组内排序,组间不排序。时间复杂度O(N) # 3. 取出每个数组的中位数(不组的组取上中位数),组成一个新数组new_arr,长度为(N/5) # 4. 递归调用bfprt(new_arr,len(new_arr)/2),返回值作为快速排序中的临界值,即将整个数组按照:小于、等于、大于进行分区 def getMinKNumsByBFPRT(alist, k): """应用BFPRT算法求解数组中第k个小的数,并将前k个数返回""" if k < 1 or k > len(alist): return alist minKth = getMinKthByBFPRT(alist, k) res = [0] * k index = 0 for i in range(len(alist)): if alist[i] < minKth: res[index] = alist[i] index += 1 while index < len(res): res[index] = minKth index += 1 return res def getMinKthByBFPRT(alist, k): """应用BFPRT算法求解数组中第k个小的数""" copyArr = copyArray(alist) return bfprt(copyArr, 0, len(copyArr) - 1, k - 1) def bfprt(alist, begin, end, index): # 在[begin, end]范围内,求第index小的数 if begin == end: return alist[begin] pivot = medianOfMedians(alist, begin, end) # 将中位数数组中的中位数当做划分值 pivotRange = partitionBFPRT(alist, begin, end, pivot) if pivotRange[0] <= index <= pivotRange[1]: return alist[index] elif index < pivotRange[0]: return bfprt(alist, begin, pivotRange[0] - 1, index) else: return bfprt(alist, pivotRange[1] + 1, end, index) def medianOfMedians(alist, begin, end): num = end - begin + 1 offset = 0 if num % 5 == 0 else 1 midArr = [0] * (num // 5 + offset) for i in range(len(midArr)): beginI = begin + i * 5 endI = beginI + 4 midArr[i] = getMedian(alist, beginI, min(end, endI)) # 调用bfprt()函数求数组中的中位数,因为一个数组的中位数也就是这个数组中第(len(alist)/2)个小的数,因此可以调用求解 return bfprt(midArr, 0, len(midArr) - 1, len(midArr) // 2) def getMedian(alist, begin, end): alist = insertionSort(alist) mid = (begin + end) // 2 + (begin + end) % 2 return alist[mid] def partitionBFPRT(alist, begin, end, pivotValue): """将pivotValue作为临界值,使用快速排序,返回的是等于部分的范围值""" small = begin - 1 cur = begin big = end + 1 while cur != big: if alist[cur] < pivotValue: small += 1 alist[small], alist[cur] = alist[cur], alist[small] cur += 1 elif alist[cur] > pivotValue: big -= 1 alist[cur], alist[big] = alist[big], alist[cur] else: cur += 1 result = (small + 1, big - 1) return result def getMaxWindow(alist, w): """应用双端队列,求解固定大小为w的窗口内的最大值""" if alist == [] or w < 1 or len(alist) < w: return None maxList = [] resultList = [0] * (len(alist) - w + 1) index = 0 for i in range(len(alist)): while maxList != [] and alist[maxList[-1]] <= alist[i]: maxList.pop() maxList.append(i) if maxList[0] == i - w: # 移动窗口,检测最大值列表的第一个索引是否过期 maxList.pop(0) if i >= w - 1: # 窗口形成之后,才会向结果队列中加数 resultList[index] = alist[maxList[0]] index += 1 return resultList def getNum(arr, num): """应用双端队列求解,满足子数组中最大值和最小值的差值小于num的子数组的数量""" if arr == [] or len(arr) == 1: return 0 minList = [] maxList = [] start, end, res = 0, 0, 0 while start < len(arr): # 以start为开头的前提下,end一直往右走,直到不能往右为止,即在[start, end]区间内最大值和最小值的差值大于num while end < len(arr): while minList != [] and arr[minList[-1]] >= arr[end]: minList.pop() minList.append(end) while maxList != [] and arr[maxList[-1]] <= arr[end]: maxList.pop() maxList.append(end) if arr[maxList[0]] - arr[minList[0]] > num: break end += 1 # 在找完以start开头的区间内,所满足条件的数组后。需要将start右移,需要调整maxList和minList if minList[0] == start: minList.pop(0) if maxList[0] == start: maxList.pop(0) res += (end - start - 1) start += 1 return res def drabStrack(alist): """利用单调栈求解数组中左面离它最近的比它小的数的索引和右面最近的比它小的数的索引""" resultList = [] helpList = [] for i in range(len(alist)): if helpList == []: helpList.append([alist[i], i]) # 压入栈中的元素是一个列表[value, index] else: if alist[i] > helpList[-1][0]: # 如果满足将要压入栈中的值比栈顶元素大,则直接压入 helpList.append([alist[i], i]) elif alist[i] == helpList[-1][0]: helpList[-1].append(i) else: while helpList != [] and alist[i] < helpList[-1][0]: res = helpList.pop() num = len(res) for x in range(len(res) - 1): if helpList == []: resultList.append([[res[x], res[-1] + x], None, [alist[i], i]]) else: resultList.append([[res[x], res[-1] + x], helpList[-1], [alist[res[-1] + 1], res[-1] + 1]]) helpList.append([alist[i], i]) while helpList != []: res = helpList.pop() if helpList != []: resultList.append([res, helpList[-1], None]) else: resultList.append([res, None, None]) return resultList def maxRecFromBottom(heightList): """应用单调栈求分布直方图中的最大面积""" if heightList == [] or len(heightList) == 0: return 0 maxArea = 0 stackList = [] for i in range(len(stackList)): while stackList is not [] and heightList[i] <= heightList[stackList[-1]]: temp = stackList.pop() k = -1 if stackList == [] else stackList[-1] # k是左边界,也就是左边离它最近的比它大的数的索引 curArea = (i - k - 1) * heightList[temp] maxArea = max(maxArea, curArea) stackList.append(i) while stackList is not []: temp = stackList.pop() k = -1 if stackList == [] else stackList[-1] curArea = (len(heightList) - k -1) * heightList[temp] maxArea = max(maxArea, curArea) return maxArea def maxRecSize(matrix): """全是1的所有矩形区域中,最大的矩形区域为1的数量""" if matrix == [] or len(matrix) == 0 or len(matrix[0]) == 0: return 0 maxArea = 0 heightList = [0] * len(matrix[0]) for i in range(len(matrix)): for j in range(len(matrix[0])): heightList[j] = 0 if matrix[i][j] == 0 else heightList[j] + 1 maxArea = max(maxRecFromBottom(heightList), maxArea) return maxArea class Pair(object): def __init__(self, value): self.value = value self.times = 1 def getInternalSum(n): return 0 if n == 1 else n * (n - 1) / 2 # 假设一个循环数组,由其中一个数向两边找比它大的数构成一对,一共可以有多少对. # 例如[1, 2, 4, 5, 3],可以有[1, 2], [1, 3], [2, 4], [4, 5], [3, 5], [2, 3], [3, 4] # 求解思路:首先找出数组中最大值和次大值,那么除了这两个点,任意位置的数字向左向右都可以找到一个比它大的数字构成组,一共是2(n-2), # 再加上最大值和次大值构成一组,所以一共是2*n-3对 # 对于含有相同数字的循环数组,可以应用单调栈进行处理。 # 首先找出数组的第一个最大值作为第一个压入单调栈的元素,如果压入的是相同的元素,则栈中相应元素的次数+1. # 如果预压入的元素值大于栈顶元素,则栈顶元素弹出,此时计算弹出的栈顶元素所能构成的数对,根据栈顶元素的次数times, # 相同元素值之间都可以互相构成数对,一共是C(2, times),弹出的栈顶元素与两边的大值又能构成数对,是2*times, # 所以弹出栈顶元素所能构成的数对总共是(C(2, times) + 2*times) # 当索引值再次回到最开始压入最大值的索引时,结束遍历,如若此时栈里仍有元素,则需要一次弹出,并计算相关的构成数组对数。 # 如果栈里的元素的个数大于2个,则依次弹出,直至栈里还有2个元素,对数的计算和上面相同 # 栈里元素还有2个时,倒数第二个的元素构成的数组对和栈底元素的次数有关,如果栈底元素(也就是最大值)有2次及以上, # 则对数仍然是(C(2, times) + 2*times),如果最大值的次数仅是1次,则对数是(C(2, times) + times) # 栈里元素仅剩最大值时,对数是C(2, times) def communications(alist): if alist == [] or len(alist) < 2: return 0 listSize = len(alist) maxIndex = 0 for i in range(listSize): maxIndex = i if alist[maxIndex] < alist[i] else maxIndex maxValue = alist[maxIndex] index = nextIndex(listSize, maxIndex) stackList = [] res = 0 stackList.append(Pair(maxValue)) while index != maxIndex: value = alist[index] while stackList != [] and stackList[-1].value < value: times = stackList.pop().times res += getInternalSum(times) + 2 * times if stackList != [] and stackList[-1].value == value: stackList[-1].times += 1 else: stackList.append(Pair(value)) index = nextIndex(listSize, index) while stackList != []: times = stackList.pop().times res += getInternalSum(times) if stackList != []: res += times if len(stackList) > 1: res += times else: res += 1 if stackList[-1].times > 1 else 0 return res def nextIndex(size, i): return i + 1 if i < size - 1 else 0 # Morris遍历:利用叶节点的左右指针指向空的这个空间实现了额外空间复杂度为O(1)的遍历 # 遍历到当前节点,记为cur # 1) 如果cur无左孩子,cur向右移动(cur=cur.right) # 2) 如果cur有左孩子,找到cur左子树上最右的节点记为mostright # 1. 如果mostright的right指针指向空,让其指向cur,cur向左移动(cur=cur.left) # 2. 如果mostright的right指针指向cur,让其指向空,cur向右移动(cur=cur.right) # Morris遍历,利用当前节点的左子树的最右节点的右指针是否指向自己来判断是第几次来到该节点 # 如果左子树的最右节点的右指针指向空,则表明是第一次来到该节点。 # 如果左子树的最右节点的右指针指向自己,则表明是第二次回到该节点 def morris(pHead): if pHead is None: return pHead cur1 = pHead while cur1 is not None: mostRight = cur1.left if mostRight is not None: while mostRight.right is not None and mostRight.right is not cur1: mostRight = mostRight.right if mostRight.right is None: mostRight.right = cur1 cur1 = cur1.left continue else: mostRight.right = None # print(cur1.value + " ") cur1 = cur1.right # 由Morris遍历改成先序遍历,时间复杂度为O(N),额外空间复杂度为O(1) def morrisPre(pHead): if pHead is None: return pHead cur1 = pHead while cur1 is not None: mostRight = cur1.left if mostRight is not None: while mostRight.right is not None and mostRight.right is not cur1: mostRight = mostRight.right if mostRight.right is None: # 左子树最右节点的指针指向空,表明第一次来到该节点,则打印 mostRight.right = cur1 print(cur1.value + " ") cur1 = cur1.left continue else: mostRight.right = None else: # 该节点没有左子树,作为先序遍历,则直接打印该节点,然后cur向右移动 print(cur1.value + " ") cur1 = cur1.right # 由Morris遍历改成中序遍历,时间复杂度为O(N),额外空间复杂度为O(1) def morrisIn(pHead): if pHead is None: return pHead cur1 = pHead while cur1 is not None: mostRight = cur1.left if mostRight is not None: while mostRight.right is not None and mostRight.right is not cur1: mostRight = mostRight.right if mostRight.right is None: mostRight.right = cur1 cur1 = cur1.left continue else: mostRight.right = None print(cur1.value + " ") # 如果该节点有左子树,则应该将打印放在最后一次来到该节点的时候,打印之后,移向了右子树 cur1 = cur1.right # # 由Morris遍历改成后序遍历,时间复杂度为O(N),额外空间复杂度为O(1) # 后序遍历,只关注两次来到的节点,在第二次来到该节点时,逆序打印该节点的左子树的右边界。最后在打印整棵树的右边界 def morrisPos(pHead): if pHead is None: return pHead cur1 = pHead while cur1 is not None: mostRight = cur1.left if mostRight is not None: while mostRight.right is not None and mostRight.right is not cur1: mostRight = mostRight.right if mostRight.right is None: mostRight.right = cur1 cur1 = cur1.left continue else: mostRight.right = None printEdge(cur1.left) cur1 = cur1.right printEdge(pHead) def printEdge(pHead): tail = reverseEdge(pHead) cur = tail while cur is not None: print(cur.value + " ") cur = cur.right reverseEdge(tail) def reverseEdge(fromNode): preNode = None while fromNode is not None: nextNode = fromNode.right fromNode.right = preNode preNode = fromNode fromNode = nextNode return preNode
数据结构与算法--python学习笔记三(听了左神的课,自己整理的,左神还是强啊)
最新推荐文章于 2023-10-16 16:39:21 发布