• 01: 数组


    算法面试其他篇

    目录:

    1.1 简单数组题

      1、去除列表中相加等于指定数后的列表(x+y=4)

          [1,3,5,7,1,2]   ==>   [5,7,1,2]                 [1,3,3,5,7,1,2]   ==>   [3,5,7,1,2]

    #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    def func(l, tag=4):
        for i in range(len(l)):
            a = l[i]
            if a != None:
                b = tag - a
                try:
                    index = l.index(b)
                    if index != i:  # 避免 2 + 2 = 4只有一个2也没重置为None
                        l[i] = None
                        l[index] = None
                    else:
                        if l.count(b) > 2:  # 如果有两个就都重置为 None
                            l[i] = None
                            l[l.index(b)] = None
                except Exception as e:
                    pass
        print l  # [None, None, 5, 7, 1, 2]
        while None in l:
            l.remove(None)
        return l  # [5, 7, 1, 2]
    
    l = [1,3,5,7,1,2]
    print func(l, 4)
    '''
    [1,3,5,7,1,2]
    [None, None, 5, 7, 1, 2]
    [5, 7, 1, 2]
    '''
    去除列表中 x+y=4 的数

      2、合并两个有序列表

    #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    def loop_merge_sort(l1, l2):
        tmp = []
        while len(l1) > 0 and len(l2) > 0:
            if l1[0] < l2[0]:
                tmp.append(l1[0])
                del l1[0]
            else:
                tmp.append(l2[0])
                del l2[0]
        tmp.extend(l1)
        tmp.extend(l2)
        return tmp
    
    l1 = [1,4,5]
    l2 = [2,3,6]
    print loop_merge_sort(l1, l2)  # [1, 2, 3, 4, 5, 6]
    合并两个有序列表(删除合并)
    #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    def loop_merge_sort(l1, l2):
        n1, n2 = 0, 0
        len1, len2 = len(l1), len(l2)
        tmp = []
        while n1 < len1  and n2 < len2:
            if l1[n1] < l2[n2]:
                tmp.append(l1[n1])
                n1 = n1 + 1
            else:
                tmp.append(l2[n2])
                n2 = n2 + 1
        else:
            while n1 < len1:
                tmp.append(l1[n1])
                n1 = n1 + 1
            while n2 < len2:
                tmp.append(l2[n2])
                n2 = n2 + 1
        return tmp
    
    l1 = [1,4,5]
    l2 = [2,3,6]
    print loop_merge_sort(l1, l2)  # [1, 2, 3, 4, 5, 6]
    合并两个有序列表(双游标)
    #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    def find_kth(l1, l2, k):
        val = None
        n = 1
        while len(l1) > 0 and len(l2) > 0:  # l1和l2都未取到最后一个数
            if l1[0] < l2[0]:
                val = l1[0]
                del l1[0]
            else:
                val = l2[0]
                del l2[0]
            if n == k:
                return val
            n = n + 1
    
        while len(l1) > 0:  # l2为空,l1不为空
            val = l1[0]
            del l1[0]
            if n == k:
                return val
            n = n + 1
    
        while len(l2) > 0:  # l1为空l2不为空
            val = l2[0]
            del l2[0]
            if n == k:
                return val
            n = n + 1
    
    l1 = [1,4,5,11,12,13,18]
    l2 = [2,3,6,7,8,9,10]
    print find_kth(l1, l2, 14)  # 18
    找两个有序列表第k大的数

      3、一个有序列表找中位数

    #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    def get_median(data):
            data.sort()
            half = len(data) // 2
            return (data[half] + data[-1-half]) / float(2)
    data = [1,2,3,4,5,6,7,8,9,10,11,12]
    print get_median(data)
    一个有序列表找中位数

      4、找素数(除了1和他本身没有其他的约数)

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import math
    def func(n):
        l = []
        for i in range(2, n+1):
            for j in range(2, int(math.sqrt(i))+1):
                if i%j == 0: #如果出现整除说明有因子
                    break #跳出最外层for循环判断下一个
            else: #如果第二层循环结束还没有跳出的话
                l.append(i) #说明是素数,加到列表里
        return l
    
    print func(20)  # [2, 3, 5, 7, 11, 13, 17, 19]
    法1:普通方法
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import math
    def func(n):
        l = [2,3,5]
        for i in range(6, n+1):
            for j in l:
                if j <= ( math.sqrt(i) + 1 ) and i%j == 0:
                    break #跳出最外层for循环判断下一个
            else: #如果第二层循环结束还没有跳出的话
                l.append(i) #说明是素数,加到列表里
        return l
    
    print func(10)  # [2, 3, 5, 7]
    '''
    说明:判断100是否是质素只需要对比4次
    1、不需要循环[1,2,3,4,5,6,7,8,9,10]依次与100取余
    2、只需要循环[2, 3, 5, 7] 依次与100取余即可
    '''
    法2:求素数 时间复杂度最低方法

      5、列表去重 

    #1、set去重
    a=[1,2,3,4,1,2,3,4]
    print( list(set(a)) )    # [1, 2, 3, 4]
    
    #2、使用字典去重
    b = {}
    b=b.fromkeys(a).keys()
    print(list(b))                #  [1, 2, 3, 4]
    列表去重:改变顺序
    a=[1,2,3,4,1,2,3,4]
    
    #1、去重不改变顺序
    d = {}
    tmp = []
    for i in a:
        if not d.get(i):
            tmp.append(i)
            d[i] = True
    print(tmp)             #  [1, 2, 3, 4]
    列表去重:不改变顺序

      6、实现enumerate函数

    l = [1,2,3,4]
    def my_enumerate(l):
        n = 0
        for val in l:
            print(n,val)
            n += 1
    my_enumerate(l)
    实现enumerate函数

      7、求两个列表 交集、差集、并集

    list_1 = set([1,2,3,4,5])
    list_2 = set([4,5,6,7,8])
    
    #1、交集(在list_1和list_2中都有的元素4,5)
    print(list_1.intersection(list_2))                      #交集: {4, 5}
    
    #2、差集
    print(list_1.difference(list_2))                        #差集:在list_1中有在list_2中没有:   {1, 2, 3}
    print(list_2.difference(list_1))                        #差集:在list_1中有在list_2中没有:   {8, 6, 7}
    
    #3、并集(在list_1和list_2中的元素全部打印出来,重复元素仅打印一次)
    print(list_1.union(list_2))                             #并集: {1, 2, 3, 4, 5, 6, 7, 8}
    求两个列表 交集、差集、并集

      8、求股票最大利润

    def maxProfit(prices):
        min_p, max_p = max(prices), 0
        for i in range(len(prices)):
            min_p = min(min_p, prices[i])  # 到目前为止最小买入价格
            max_p = max(max_p, prices[i] - min_p)  # 最大利润 = 当天价格 - 历史最小价格
        return max_p
    
    l = [7,1,5,3,6,4]
    print(maxProfit(l))  # 5
    求股票最大利润

    1.2 找两个有序列表中位数(难)

      1、找中位数(两个列表)

          参考博客:https://www.jb51.net/article/152061.htm
          参考官网:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/

          1)给定两个大小为 m 和 n 的有序数组 nums1 和 nums2

          2)请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

    #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    def median(A, B):
        m, n = len(A), len(B)
        if m > n:
            A, B, m, n = B, A, n, m
        if n == 0:
            raise ValueError
    
        imin, imax, half_len = 0, m, (m + n + 1) / 2
        while imin <= imax:
            i = (imin + imax) / 2
            j = half_len - i
            if i < m and B[j-1] > A[i]:
                # i is too small, must increase it
                imin = i + 1
            elif i > 0 and A[i-1] > B[j]:
                # i is too big, must decrease it
                imax = i - 1
            else:
                # i is perfect
    
                if i == 0: max_of_left = B[j-1]
                elif j == 0: max_of_left = A[i-1]
                else: max_of_left = max(A[i-1], B[j-1])
    
                if (m + n) % 2 == 1:
                    return max_of_left
    
                if i == m: min_of_right = B[j]
                elif j == n: min_of_right = A[i]
                else: min_of_right = min(A[i], B[j])
    
                return (max_of_left + min_of_right) / 2.0
    
    
    nums1 = [1,2,4]
    nums2 = [3,5,6]
    print median(nums1, nums2)  # 3.5
    找两个列表中位数最优解

     1.3 从无序列表查找数组第K大的数(O(n))

        参考博客:https://blog.csdn.net/wenqiwenqi123/article/details/81669899

      1、原理分析

          1. 利用快排每次循环可以将元素分为左侧都比自己小,右侧都比自己大的思想

          2. 找到k大概的位置,每次去一半即可

          3. 为什么时间复杂度是O(n)而不是nlog(n)

            1) 第一次排序需要遍历所有元素,时间复杂度为:n

            2)第二次排序数量只有上次的一半:n/2

            3) 所以总时间复杂度: n + n/2 + n/4 + n/8 + ...... +  < 2n

          注:这种方法如果又重复元素很容易导致死循环

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    def partition(num, low, high):
        cur = num[low]
        while low < high:
            while low < high and num[high] > cur:    # 从右向左比,直到遇到右边一个元素小于cur为止
                high -= 1
            while low < high and num[low] < cur:     # 从左向右比,直到遇到左边一个元素大于cur为止
                low += 1
            num[low],num[high] = num[high],num[low]  # 交互目前最大和最小位置
        num[low] = cur
        return low                                   # 找到当前二分查找中间的位置下标
    
    def findkth(num, low, high, k):                      # 找到数组里第k个数
        index = partition(num, low, high)
        if index == k:                                   # 如果位置下标正好为k就已经找到
            return num[index]
        if index < k:                                    # 如果k大于index说明要找的值在右面
            return findkth(num, index + 1, high, k)
        else:                                            # 否则要找的k在左面
            return findkth(num, low, index - 1, k)
    
    
    l = [2, 3, 1, 5, 4, 6]  # [1,2,3,4,5,6]
    # pai = [2,2,2,2,2]  # [1,2,3,4,5,6]
    
    print findkth(l, 0, len(l) - 1, 3)  # 第3号位置应该是:4
    从无序列表中找第k大的元素(利用快排思想O(n))
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    def heap_build(parent, heap):
        child = 2 * parent + 1
        while child < len(heap):
            if child + 1 < len(heap) and heap[child + 1] < heap[child]:
                child = child + 1
            if heap[parent] <= heap[child]:
                break
            heap[parent], heap[child] = heap[child], heap[parent]
            parent, child = child, 2 * child + 1
        return heap
    
    def Find_heap_kth(array, k):
        if k > len(array):
            return None
        heap = array[:k]
        for i in range(k, -1, -1):
            heap_build(i, heap)
        for j in range(k, len(array)):
            if array[j] > heap[0]:
                heap[0] = array[j]
                heap_build(0, heap)
        return heap[0]
    
    l = [2, 1, 4, 3, 5, 9, 8, 0, 1, 3, 2, 5]
    print(Find_heap_kth(l, 6))
    堆排 O(n * logk)

     1.4 求丑数

       1、丑数定义

          1. 根据丑数的定义,丑数应该是另一个丑数乘以2、3或者5的结果(1除外)。

          2. 因此我们可以创建一个数组,里面的数字是排好序的丑数。

          3. 里面的每一个丑数是前面的丑数乘以2、3或者5得到的。

    #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    def finduglynum(n):
        uglynum = []
        i = 1
        count = 0
        while True:
            temp = i
            while temp % 2 == 0:
                temp = temp // 2
            while temp % 3 == 0:
                temp = temp // 3
            while temp % 5 == 0:
                temp = temp // 5
            if temp == 1:
                uglynum.append(i)
                count += 1
            if count >= n:
                break
            i += 1
        return uglynum
    
    
    # 测试
    print finduglynum(8)  # [1, 2, 3, 4, 5, 6, 8, 9]
    方法一:简单粗暴,分解每一个数,看他的因数是不是只有2,3,5
    #!usr/bin/env python
    #encoding:utf-8
    
    def finduglynum2(n):
        uglynum = [1]
        i = 1
        t2 = m2 = 0
        t3 = m3 = 0
        t5 = m5 = 0
        while i < n:
            for x in range(t2, len(uglynum)):
                m2 = uglynum[x] * 2
                if m2 > uglynum[-1]:
                    t2 = x
                    # print("t2:",t2)
                    break
            for x in range(t3, len(uglynum)):
                m3 = uglynum[x] * 3
                if m3 > uglynum[-1]:
                    t3 = x
                    break
            for x in range(t5, len(uglynum)):
                m5 = uglynum[x] * 5
                if m5 > uglynum[-1]:
                    t5 = x
                    break
            uglynum.append(min(m2, m3, m5))
            i += 1
        return uglynum
    
    # 测试
    print finduglynum2(10)  # [1, 2, 3, 4, 5, 6, 8, 9, 10, 12]
    方法二:优化算法
    这种思路的关键在于怎样确保数组里面的丑数是排好序的。
    我们假设数组中已经有若干个丑数,排好序后存在数组中,我们把现有的最大丑数记做M。
    现在我们来生成下一个丑数,该丑数肯定是前面某一个丑数乘以2、3或者5的结果。
    我们首先考虑把已有的每个丑数乘以2。
    在乘以2的时候,能得到若干个结果小于或等于M的。
    由于我们是按照顺序生成的,小于或者等于M肯定已经在数组中了,我们不需再次考虑;
    我们还会得到若干个大于M的结果,但我们只需要第一个大于M的结果,因为我们希望丑数是按从小到大顺序生成的,其他更大的结果我们以后再说。
    我们把得到的第一个乘以2后大于M的结果,记为M2。
    同样我们把已有的每一个丑数乘以3和5,能得到第一个大于M的结果M3和M5。
    那么下一个丑数应该是M2、M3和M5三个数的最小者。
    前面我们分析的时候,提到把已有的每个丑数分别都乘以2、3和5,事实上是不需要的,因为已有的丑数是按顺序存在数组中的。
    对乘以2而言,肯定存在某一个丑数T2,排在它之前的每一个丑数乘以2得到的结果都会小于已有最大的丑数
    在它之后的每一个丑数乘以2得到的结果都会太大。
    我们只需要记下这个丑数的位置,同时每次生成新的丑数的时候,去更新这个T2。对乘以3和5而言,存在着同样的T3和T5。
    优化算法思路

     1.5 最长递增子序列LIS的O(nlogn)的求法

      1、说明

          1. 最长递增子序列是指n个数的序列的最长单调递增子序列。

          2. 比如,A = [1,3,6,7,9,4,10,5,6]的LIS是1 3 6 7 9 10。

      2、实现一个复杂度O(nlogn)的算法

          1. 如果 x 比所有的tails都大,说明x可以放在最长子序列的末尾形成一个新的自许下,那么就把他append一下,并且最长子序列长度增加1

          2. 如果tails[i-1] < x <= tails[i],说明x需要替换一下前面那个大于x的数字,以便保证tails是一个递增的序列,那么就更新tails[i]

          3. 这样维护一个tails变量,最后的答案就是这个长度。

    #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    def lengthOfLIS(nums):
        tails = [0] * len(nums)
        size = 0
        for x in nums:
            low = 0
            high = size  # size  0 1 2 3 3 统计当前列表最大单调长度
            while low != high:
                mid = (low + high) // 2  # 使用二分法找到x应该的位置
                if tails[mid] < x:  # x第一次为tials中间位置,如果x比较大,去tails后面的一半列表比较
                                    # 直到找到tails中大于x元素的下标位置
                    low = mid + 1  # 使tails下标m加一(i加一后m就会加一)
                else:
                    high = mid
            tails[low] = x  # 如果x比tails最后一个元素还大,就追加到tails末尾,否则替换嗲tials中第一个大于x的元素
            size = max(low + 1, size)
        return size
    
    print lengthOfLIS([3, 4, 7, 2, 5])  # 3
    最长递增子序列
    比如我们的目标数组是[3, 4, 7, 2, 5]。
    ####1. 第一步:x = 3
    '''
    1)此时i = 0,直接令tails[0] = 3,tails = [3, 0, 0, 0, 0]。
    2)说明到目前为止长度为1的递增子序列末尾最小为3。 
    '''
    
    ####2. 第二步:x = 4
    '''
    1)此时i != j,但是x大于tails的末尾,直接另tail[1] = 4, tails = [3, 4, 0, 0, 0]。
    2)说明到目前为止长度为1的递增子序列末尾最小为3,长度为2的递增子序列末尾最小为4。 
    '''
    
    ####3. 第三步:x = 7
    '''
    1)大于tails的末尾,直接令tails[2] = 7,tails = [3, 4, 7, 0, 0]。
    2)说明到目前为止长度为1的递增子序列末尾最小为3,长度为2的递增子序列末尾最小为4,长度为3的递增子序列末尾最小为7. 
    '''
    
    ####4. x = 2
    '''
    1)此时x小于tails的末尾,需要用二分查找到比x大的最小的那个数更新之,
    2)查找到tails中比2大的最小数是3,更新tail[0] = 2,此时tails = [2, 4, 7, 0, 0]。
    3)说明到目前为止长度为1的递增子序列末尾最小为2,长度为2的递增子序列末尾最小为4,长度为3的递增子序列末尾最小为7。
    '''
    
    ####对第四步重点说明:
    '''
    1)这一步理解很关键,[2, 4, 7, 0, 0]的存在并不是说目前为止的递增子序列是2 4 7,
    2)而是长度分别为1,2, 3的递增子序列目前所能得到的最小结尾元素是2,4,7。
    3)我们这样做的目的就是,通过维护tails中的元素,保证每次对于长度为i+1的一个子序列对应的tails[i]元素最小,
    
    4)“虽然在我之前,你们形成了一个长度为m的递增序列,但是呢,你们长度为m这个序列的末尾最大的一个数比我还大,
    5)不如把我和末尾最大的那个元素换一下,这样你看咱们还是一个递增序列,长度也不变,但是我和你们更亲近”,
    6)别的元素一听是这么个道理啊,于是就踢出最后一个元素,换上了这个新的更小的元素。
    '''
    举例推演
    tails的第i个位置记录nums中长度为i+1的所有递增子序列中,结尾最小的数字。 
    我们很容易证明,tails是一个递增的数组。
    首先,tails[0]一定是所有元素中最小的那个数字min1,因为长度为1的子序列中,结尾最小的数字就是序列中最小的那个。
    同样,长度为2的子序列中,结尾最小的的那个子序列的结尾元素一定大于min1,
    因为首先所有长度为2的递增子序列,第二个元素一定比第一个元素大,如果长度为2的子序列中某个子序列的结尾元素小于min1,
    那么在第一次操作中,这个元素就会更新为min1。
    对于长度为3的子序列,假设之前tails已经存储了前两个结尾最小数[a, b],
    若长度为三的子序列结尾数字c3小于b,即[c1, c2, c3]是一个递增子序列,且c3 < b,则必然有c2 < b,
    这样和之前的结论b是长度为2的递增子序列结尾最小元素矛盾。
    所以,通过这样的一步步的反证法,很容易证明tails一定是一个递增的数组。
    那么很容易通过二分查找, 找到在tails数组中需要被更新的那个数。 
    原理说明

    11111111

  • 相关阅读:
    matplotlib直方图绘图并且标注数字
    爬虫最基础知识串行和异步进阶代码
    卡方检验
    德国VoIP 系统中发现秘密后门
    python 处理字典键值合并
    python 读取json大文件
    上下文管理器的使用
    服务端高并发分布式架构
    上下文管理器连接数据库示例
    上下文管理器
  • 原文地址:https://www.cnblogs.com/xiaonq/p/10488342.html
Copyright © 2020-2023  润新知