数组
既然要一次介绍那么多数组问题,就有必要老生常谈地聊聊数组:数组(Array)是有序的元素序列。组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下标变量。用于区分数组的各个元素的数字编号称为下标。数组是在程序设计中,为了处理方便, 把具有相同类型的若干元素按有序的形式组织起来的一种形式。
早在本科学C++的时候,大家就已经开始接触数组,和数组一起出现的往往是时间复杂度和空间复杂度的考察,比如求两个有序数组的交集,时间复杂度O(m+n);求两个有序数组的中位数,时间复杂度log(len1+len2)等等。对于《剑指offer》中的问题也一样,如果我们考虑暴力求解自然是可以的,但是当面试的时候,考官对时空复杂度提出要求的时候,我们往往要另辟蹊径。
本次出现的5个问题,我以AC为目标,不时出现了一些暴力方法。这些方法虽然能AC,但是并不是坠吼的,请各位读者自行思考是否存在更优解。
数组中出现次数过半的数
offer35的要求是给出一个数组,要求数组中出现次数过半的数,如果不存在则输出0。
这道题的方法就多种多样了。
语法糖
利用sort和count轻松便捷加愉快。
# offer35-solution 1
def MoreThanHalf(self, numbers):
numbers.sort()
p = numbers[len(numbers)//2]
if numbers.count(p)> len(numbers)//2:
return p
else:
return 0
相互抵消的计数器
想要写出空间复杂度是O(1)的代码,就要考察题目本身的内涵——我们通过写一个计数器。设置一个中间变量,比较数组中相邻数字是否相同,若不相同,则相互抵消计数器减1,若相同则计数器加1,中间变量为该值,继续依次比较,最后剩下的数字有可能是次数超过一半的数字。
这种方法需要再写一个额外的for循环得出该数字的次数与数组长度的一半比较。
# offer35-solution 2
def MoreThanHalf(self, numbers):
if not numbers:
return 0
if len(numbers) == 1:
return numbers[0]
s = [1]
p = numbers[0]
for i in range(1, len(numbers), 1):
if numbers[i] == p:
# s就是一个计数器
s.append(s[i-1]+1)
else:
s.append(s[i-1]-1)
if s[i] == 0:
s[i] += 1
p = numbers[i]
sum = 0
for i in range(0, len(numbers), 1):
if numbers[i] == p:
sum += 1
if sum > len(numbers)//2:
return p
else:
return 0
顺序操作法
如果不追求空间复杂度,只要求写出时间和空间复杂度都是O(n)的代码,那就挺轻松了
下面的代码没有用到语法糖。
# offer35-solution 3
def MoreThanHalf(self, numbers):
numCount = {}
for num in numbers:
if num in numCount:
numCount[num] += 1
else:
numCount[num] = 1
if numCount[num] > (len(numbers)>>1):
return num
return 0
数组中最小的k个数
offer36要求求出数组中最小的k个数,简直就是语法糖展示教程。注意sort排序的时间复杂度是O(nlogn),我曾在一次面试中被问及说“你用sort,那你能写一个sort出来不?”然而机智的我早就把sort的原理+代码背熟了,看在我只是搞数据的份上,面试官并没有继续为难我(当然那次面试也挂了哈哈哈哈哈哈)
# offer36-solution
def GetLeastNumbers_candy(self, tinput, k):
if k > len(tinput):
return []
tinput.sort() # sort排序的时间复杂度是O(nlogn)
return tinput[:k]
# offer36-solution 2
def GetLeastNumbers_candy2(self, tinput, k):
import heapq
if k > len(tinput):
return []
return heapq.nsmallest(k, tinput) # nsmallest的时间复杂度是O(nlogk)
数据流中的中位数
offer37要求先读取数据流,然后求其中位数。
边读数据流边做sort就好了。python没有方便的求中位数的糖,但自己写也不难。
# offer37-solution
class Solution:
def __init__(self):
self.array = []
def Insert(self, num):
# write code here
self.array.append(num)
self.array.sort()
def GetMedian(self, M):
# write code here
length = len(self.array)
if len(self.array) % 2 == 1:
return self.array[length // 2]
else:
return (self.array[length // 2 - 1] + self.array[length // 2]) / 2.0
连续子数列的最大和
offer38要求连续子数列的最大和,意即给定一个数字序列
[
A
1
,
A
2
,
A
3
,
…
A
n
]
[A1,A2,A3,…An]
[A1,A2,A3,…An],求
i
,
j
(
1
<
=
i
<
=
j
<
=
n
)
i,j(1<=i<=j<=n)
i,j(1<=i<=j<=n)使得
A
i
…
A
j
Ai…Aj
Ai…Aj和最大。
思路总结为两点:
1、最大和子数列的第一项必不为负数
2、如果前面数的累加值加上当前数后的值会比当前数小,说明累计值对整体和是有害的;如果前面数的累加值加上当前数后的值比当前数大或者等于,则说明累计值对整体和是有益的。
以2作为更新遍历条件,构建变量判断累加值是否大于最大值:如果大于最大值,则最大和更新;否则保留之前的最大和即可。
我也没想到别的什么好方法和好思路,代码也写得略显冗余。
# offer38-solution
class Solution:
def FindGreatestSumOfSubArray(self, array):
if not array:
return
if max(array) <= 0:
return max(array)
p = sum = array[0]
for i in range(1,len(array),1):
if array[i] >= 0 and sum >= 0:
sum = sum + array[i]
p = sum
elif array[i]>=0 and sum < 0:
p = sum = array[i]
else:
sum = sum + array[i]
return p
把数组排成最小的数
offer40的要求是,输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印出能拼接出的所有数字中最小的一个。例如:
input:[1,32,9,565]
output:1325659
先将整数处理成字符串,通过比较字符串的组合,得到组成顺序。基于这个思路,即便是暴力方法也是可以AC的。
那就奔着AC去了……
# offer40-solution
import itertools
class Solution:
def PrintMinNumber(self, numbers):
# 暴力解法
if len(numbers) <= 0:
return ""
str_numbers = [str(i) for i in numbers]
premu = itertools.permutations(str_numbers)
res = [''.join(i) for i in premu]
return min(res)
使用itertools模块的permutations可以事半功倍。
参考
Python3 List sort()方法
使用Python模块:heapq模块(堆)
python之itertools模块
剑指Offer 45.把数组排成最小数 | LeetCode 179.最大数(Python2 / 3)