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

posted @ 2019-03-07 11:12  不做大哥好多年  阅读(365)  评论(0编辑  收藏  举报