分治法

分治法

描述:

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)  

 

posted @ 2019-11-27 21:55  oldby  阅读(358)  评论(0编辑  收藏  举报