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] '''
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
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]
#!/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取余即可 '''
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)
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
#!/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))
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]
#!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
作者:学无止境
出处:https://www.cnblogs.com/xiaonq
生活不只是眼前的苟且,还有诗和远方。