分治法
分治法
描述:
1、分:将问题柴分为几个子问题,这些子问题和原问题相似只是量级上小一些。
2、治:递归地解决每一个子问题,然后结合这些子问题的解决方案构造出原问题的解决方案。
例:二分搜索、快速排序、归并排序
习题
一、快速指数
题:计算 an
def fast_power(x, n): if n == 0: return 1.0 elif n < 0: return 1 / fast_power(x, -n) elif n % 2: return fast_power(x * x, n // 2) * x else: return fast_power(x * x, n // 2)
二、搜索峰值
题:给定一个没有重复数的数组,其有多个峰值,返回任意一个峰值的index,You may imagine that num[-1] = num[n] = -∞.
def search_peak(alist): return peak_helper(alist, 0, len(alist) - 1) def peak_helper(alist, start, end): if start == end: return start if (start + 1 == end): if alist[start] > alist[end]: return start return end mid = (start + end) // 2 if alist[mid] > alist[mid - 1] and alist[mid] > alist[mid + 1]: return mid if alist[mid - 1] > alist[mid] and alist[mid] > alist[mid + 1]: return peak_helper(alist, start, mid - 1) return peak_helper(alist, mid + 1, end)
三、查找中值/第k个元素
方法一:排序:O(nlogn)
方法二:冒泡:O(nk)
方法三:快排:O(n)
# O(n) time, quick selection def findKthLargest(nums, k): # convert the kth largest to smallest start = time.time() rst = findKthSmallest(nums, len(nums)+1-k) t = time.time() - start return rst, len(nums), t def findKthSmallest(nums, k): if nums: pos = partition(nums, 0, len(nums)-1) if k > pos+1: return findKthSmallest(nums[pos+1:], k-pos-1) elif k < pos+1: return findKthSmallest(nums[:pos], k) else: return nums[pos] # choose the right-most element as pivot def partition(nums, l, r): low = l while l < r: if nums[l] < nums[r]: nums[l], nums[low] = nums[low], nums[l] low += 1 l += 1 nums[low], nums[r] = nums[r], nums[low] return low
四、计算逆序对
题:对数组做逆序对计数—距离数组的排序结果还有“多远”。如果一个数组已经排 好序(升序),那么逆序对个数为0;如果数组是降序排列的,则逆序对个数最多。 在形式上,如果有两个元素a[i], a[j],如果a[i] > a[j] 且 i < j,那么a[i], a[j]构成一个逆序对。 例如序列2, 4, 1, 3, 5 有三个逆序对,分别是(2, 1), (4, 1), (4, 3)
方法一:O(n^2)
# O(n^2) def countInv(arr): n = len(arr) inv_count = 0 for i in range(n): for j in range(i+1, n): if (arr[i] > arr[j]): inv_count += 1 return inv_count
方法二:归并O(nlogn)
def merge(left,right): result = list() i,j = 0,0 inv_count = 0 while i < len(left) and j < len(right): if left[i] < right[j]: result.append(left[i]) i += 1 elif right[j] < left[i]: result.append(right[j]) j += 1 inv_count += (len(left)-i) result += left[i:] result += right[j:] return result,inv_count # O(nlgn) def countInvFast(array): if len(array) < 2: return array, 0 middle = len(array) // 2 left,inv_left = countInvFast(array[:middle]) right,inv_right = countInvFast(array[middle:]) merged, count = merge(left,right) count += (inv_left + inv_right) return merged, count
五、在已排序数组中找到多余元素的索引
题:给定两个排好序的数组。这两个数组只有一个不同的地方:在第一个数组某个位 置上多一个元素。请找到这个元素的索引位置
方法一:O(N)
## Returns index of extra element in arr1[]. def find_extra(arr1, arr2): for i in range(len(arr2)): if (arr1[i] != arr2[i]): return i return len(arr1)-1
方法二:O(logn)
def find_extra_fast(arr1, arr2): index = len(arr2) # left and right are end points denoting # the current range. left, right = 0, len(arr2) - 1 while (left <= right): mid = (left + right) // 2; # If middle element is same of both # arrays, it means that extra element # is after mid so we update left to mid+1 if (arr2[mid] == arr1[mid]): left = mid + 1 # If middle element is different of the # arrays, it means that the index we are # searching for is either mid, or before # mid. Hence we update right to mid-1. else: index = mid right = mid - 1; # when right is greater than left our # search is complete. return index
六、加和值最大的子序列问题
方法一:O(n^2)
# O(n^2) def subarray1(alist): result = -sys.maxsize for i in range(0, len(alist)): sum = 0 for j in range (i, len(alist)): sum += alist[j] if sum > result: result = sum return result
方法二:分治
# O(n lgn) def subarray2(alist): return subarray2_helper(alist, 0, len(alist)-1) def subarray2_helper(alist, left, right): if (left == right): return alist[left] mid = (left + right) // 2 return max(subarray2_helper(alist, left, mid), subarray2_helper(alist, mid+1, right), maxcrossing(alist, left, mid, right)) def maxcrossing(alist, left, mid, right): sum = 0 left_sum = -sys.maxsize for i in range (mid, left-1, -1): sum += alist[i] if (sum > left_sum): left_sum = sum sum = 0 right_sum = -sys.maxsize for i in range (mid+1, right+1): sum += alist[i] if (sum > right_sum): right_sum = sum return left_sum + right_sum
方法三:动态规划
# O(n) def subarray3(alist): result = -sys.maxsize local = 0 for i in alist: local = max(local + i, i) result = max(result, local) return result
七、快速整数乘法
方法一:
import functools def prod(x, y): # x, y are strings --> returns a string of x*y return str(eval("%s * %s" % (x, y))) def plus(x, y): # x, y are strings --> returns a string of x+y return str(eval("%s + %s" % (x, y))) def one_to_n_product(d, x): """d is a single digit, x is n-digit --> returns a string of d*x """ print(d, x) result = "" carry = "0" for i, digit in enumerate(reversed(x)): #print("d: ", d, " digit: ", digit) r = plus(prod(d, digit), carry) #print("r: ", r) if (len(r) == 1): carry = '0' else: carry = r[:-1] digit = r[-1] #print(" c: ", carry, " d: ", digit) result = digit + result return carry + result def sum_middle_products(middle_products): # middle_products is a list of strings --> returns a string max_length = max([len(md) for md in middle_products]) for i, md in enumerate(middle_products): middle_products[i] = "0" * (max_length - len(md)) + md print(middle_products) carry = "0" result = "" for i in range(1, max_length + 1): row = [carry] + [md[-i] for md in middle_products] r = functools.reduce(plus, row) carry, digit = r[:-1], r[-1] result = digit + result return carry + result def algorithm(x, y): x, y = str(x), str(y) middle_products = [] for i, digit in enumerate(reversed(y)): middle_products.append(one_to_n_product(digit, x) + "0" * i) print(middle_products) return int(sum_middle_products(middle_products))
方法二:分治O(nlog23)
def karatsuba(x,y): """Function to multiply 2 numbers in a more efficient manner than the grade school algorithm""" if len(str(x)) == 1 or len(str(y)) == 1: return x*y else: n = max(len(str(x)),len(str(y))) nby2 = n // 2 a = x // 10**(nby2) b = x % 10**(nby2) c = y // 10**(nby2) d = y % 10**(nby2) ac = karatsuba(a,c) bd = karatsuba(b,d) ad_plus_bc = karatsuba(a+b,c+d) - ac - bd # this little trick, writing n as 2*nby2 takes care of both even and odd n prod = ac * 10**(2*nby2) + (ad_plus_bc * 10**nby2) + bd return prod
八、对于多项式乘法的快速傅里叶变换
暴力解:
def mults(A, B): m, n = len(A), len(B) result = [0] * (m + n - 1) for i in range (m): for j in range(n): result[i + j] += A[i] * B[j] return result def printPoly(poly): n = len(poly) show = "" for i in range(n-1, -1, -1): show += str(poly[i]) if (i != 0): show = show + "x^" + str(i) if (i != 0): show = show + " + " print(show)
九、水槽问题
题:给定一个容量为C升的水槽,初始时给这个水槽装满水。每天都会给水槽中 加入I升的水,若有溢出,则舍弃多余的水。另外在第i天,水槽中有i升的水会被喝掉。请计 算在哪一天水槽中的水第一次用完。
举例: • 输入:容量C=5,I=2。 • 输出:4 • 在第一天开始时,水槽中有5升水;第一天结束时有(5-1=)4升水。 • 在第二天开始时,水槽中有4+2=6升水,但是容量为5,所以有5升水。在第二天结束时, 有5-2=3升水。 • 在第三天开始时,水槽中有3+2=5升水,在第三天结束时,有5-3=2升水。 • 在第四天开始时,水槽中有2+2=4升水,在第四天结束时,有4-4=0升水。 • 所以最后结果为4.
# Utility method to get # sum of first n numbers def getCumulateSum(n): return (n * (n + 1)) // 2 # Method returns minimum number of days # after which tank will become empty def minDaysToEmpty(C, l): # if water filling is more than # capacity then after C days only # tank will become empty if (C <= l) : return C # initialize binary search variable lo, hi = 0, 1e4 # loop until low is less than high while (lo < hi): mid = int((lo + hi) / 2) # if cumulate sum is greater than (C - l) # then search on left side if (getCumulateSum(mid) >= (C - l)): hi = mid # if (C - l) is more then # search on right side else: lo = mid + 1 # Final answer will be obtained by # adding l to binary search result return (l + lo)
另:数学法
import math def solve(a, b, c): r = pow(b, 2) - 4 * a * c if (r < 0): raise ValueError("No Solution") return (-b + math.sqrt(r)) / (2 * a) def minDaysToEmpty(C, l): co = -2 * (C - l) return math.ceil(solve(1, 1, co)) + l
十、奇偶数换序问题
题:
给定一个含有2n个元素的数组,形式如: { a1, a2, a3, a4, ….., an, b1, b2, b3, b4, …., bn }.
举例: • 输入: arr[] = { 1, 2, 9, 15 } • 输出:1 9 2 15 • 输入: arr[] = { 1, 2, 3, 4, 5, 6 } • 输出: 1 4 2 5 3 6
方法一:
def shuffleArray(a, n): # Rotate the element to the left i, q, k = 0, 1, n while(i < n): j = k while(j > i + q): print(i, j, q, k) a[j - 1], a[j] = a[j], a[j - 1] j -= 1 for ii in range(0, 2 * n): print(a[ii], end = " ") print() i += 1 k += 1 q += 1
方法二:分治
def shufleArray(a, left, right): # If only 2 element, return if (right - left == 1): return # Finding mid to divide the array mid = (left + right) // 2 # Using temp for swapping first # half of second array temp = mid + 1 # Mid is use for swapping second # half for first array mmid = (left + mid) // 2 # Swapping the element for i in range(mmid + 1, mid + 1): (a[i], a[temp]) = (a[temp], a[i]) temp += 1 # Recursively doing for # first half and second half shufleArray(a, left, mid) shufleArray(a, mid + 1, right)
十一、用最小步数收集银币
给定几摞硬币,这些摞列的硬币相邻排列。我们要用最少的步数收集所有 的硬币,其中每一步可以沿水平线或者垂直线连续 收集。 举例: • 输入: height[] = [2 1 2 5 1] • 数组中每个值代表对应摞的高度(即硬币个数),此处给了我们5摞硬币。其中第一摞有2个硬币 ,第二摞有1个硬币,其余依次对应。 • 输出:4
解:
def minSteps(height): def minStepHelper(height, left, right, h): if left >= right: return 0 m = left for i in range(left, right): if height[i] < height[m]: m = i return min(right - left, minStepHelper(height, left, m, height[m]) + minStepHelper(height, m + 1, right, height[m]) + height[m] - h) return minStepHelper(height, 0, len(height), 0)