这里总结一些《剑指Offer 名企面试官精讲典型编程题(纪念版)》上面的题目,都是用python3实现的,并且已经通过牛客网的在线编译。所有代码也可以直接在我的github账号中下载。如有问题,希望大家能够批评指正!小弟必将感激不尽。
剑指offer_1:二维数组中的查找
题目描述
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路
首先选取数组中右上角的数字。如果该数字等于要查找的数字,查找过程结束;如果该数字大于要查找的数字,剔除这个数字所在的列;如果该数字小于要查找的数字,剔除这个数字所在的行。也就是说如果要查找的数字不在数组的右上角,则每一次都在数组的查找范围中剔除一行或者一列,这样每一步都可以缩小查找的范围,直到找到要查找的数字,或者查找范围为空。
代码
# -*- coding:utf-8 -*-
class Solution:
# array 二维列表
def Find(self, target, array):
# write code here
row = 0
col = len(array[0]) - 1
while row < len(array) and col >= 0:
if target == array[row][col]:
return True
if target < array[row][col]:
col -= 1
if target > array[row][col]:
row += 1
return False
剑指offer_6:旋转数组的最小数字
题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
思路
我们注意到旋转之后的数组实际上可以划分为两个排序的子数组,而且前面的子数组的元素大于或者等于后面子数组的元素。我们还注意到最小的元素刚好是这两个子数组的分界线。在排序的数组中可以用二分查找实现O(logn)的查找。
- 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。 - 接着我们可以找到数组中间的元素。如果中间元素位于前面的递增子数组,那么它应该大于或者等于第一个指针指向的元素。此时最小元素应该位于该中间元素之后,然后我们把第一个指针指向该中间元素,移动之后第一个指针仍然位于前面的递增子数组中。
- 同样,如果中间元素位于后面的递增子数组,那么它应该小于或者等于第二个指针指向的元素。此时最小元素应该位于该中间元素之前,然后我们把第二个指针指向该中间元素,移动之后第二个指针仍然位于后面的递增子数组中。
- 第一个指针总是指向前面递增数组的元素,第二个指针总是指向后面递增数组的元素。最终它们会指向两个相邻的元素,而第二个指针指向的刚好是最小的元素,这就是循环结束的条件。
特殊情况
- 如果把排序数组的0个元素搬到最后面,这仍然是旋转数组,我们的代码需要支持这种情况。如果发现数组中的一个数字小于最后一个数字,就可以直接返回第一个数字了。
- 下面这种情况,即第一个指针指向的数字、第二个指针指向的数字和中间的数字三者相等,我们无法判断中间的数字1是数以前面的递增子数组还是后面的递增子数组。正样的话,我们只能进行顺序查找。
# -*- coding:utf-8 -*-
class Solution:
def minNumberInRotateArray(self, rotateArray):
# write code here
p = 0
q = len(rotateArray) - 1
mid = p
while rotateArray[p] >= rotateArray[q]:
if q - p == 1:
mid = q
break
mid = (p + q) / 2
if rotateArray[p] == rotateArray[q] and rotateArray[p] == rotateArray[mid]:
return self.min(rotateArray[p: q])
if rotateArray[p] <= rotateArray[mid]:
p = mid
elif rotateArray[mid] <= rotateArray[q]:
q = mid
return rotateArray[mid]
剑指offer_13:调整数组顺序使奇数位于偶数前面
题目描述
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
思路
创建双向队列,遍历数组,奇数前插入,偶数后插入。
代码
# -*- coding:utf-8 -*-
class Solution:
def reOrderArray(self, array):
# write code here
index = -1
for i in range(len(array)):
if array[i] & 1 != 0:
index += 1
array.insert(index, array.pop(i))
return array
剑指offer_28:数组中次数超过一半的数字
题目描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
思路
数组中有一个数字出现的次数超过数组长度的一半,也就是说它出现的次数比其他所有数字出现次数的和还要多。因此我们可以考虑在遍历数组的时候保存两个值:一个是数组的一个数字,一个是次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则次数加1;如果下一个数字和我们之前保存的数字不同,则次数减1。如果次数为零,我们需要保存下一个数字,并把次数设为1。由于我们要找的数字出现的次数比其他所有数字出现的次数之和还要多,那么要找的数字肯定是最后一次把次数设为1时对应的数字
代码
# -*- coding:utf-8 -*-
class Solution:
def MoreThanHalfNum_Solution(self, numbers):
# write code here
if not numbers:
return 0
ans, times = 0, 0
for item in numbers:
# 如果times为0,则把下一个元素赋值给ans,并将次数设为0
if times == 0:
ans = item
times = 1
# 如果ans与下一个元素的值相等,则将items的次数加1
elif ans == item:
times += 1
# 如果ans与下一个元素的值不相等,则将times的次数减1
else:
times -= 1
return ans if numbers.count(ans) * 2 > len(numbers) else 0
剑指offer_30:连续子数组的最大和
题目描述
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。你会不会被他忽悠住?(子向量的长度至少是1)
思路
数组分析:下图是我们计算数组(1,-2,3,10,-4,7,2,-5)中子数组的最大和的过程。通过分析我们发现,累加的子数组和,如果大于零,那么我们继续累加就行;否则,则需要剔除原来的累加和重新开始。
代码
# -*- coding:utf-8 -*-
class Solution:
def FindGreatestSumOfSubArray(self, array):
# tmp 表示加上item之后的子数组的最大和
ans, tmp = -999999, 0
for item in array:
if tmp > 0:
tmp += item
else:
tmp = item
if tmp > ans:
ans = tmp
return ans
剑指offer_32:把数组排成最小的数
题目描述
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
思路
遇到这个题,全排列当然可以做,但是时间复杂度为O(n!)。在这里我们自己定义一个规则,对拼接后的字符串进行比较。
排序规则如下:
若ab > ba 则 a 大于 b,
若ab < ba 则 a 小于 b,
若ab = ba 则 a 等于 b;
根据上述规则,我们需要先将数字转换成字符串再进行比较,因为需要串起来进行比较。比较完之后,按顺序输出即可。
代码
# -*- coding:utf-8 -*-
class Solution:
def PrintMinNumber(self, numbers):
# write code here
if not numbers:
return ''
numbers = list(map(str, numbers))
len_num = len(numbers)
for i in range(len(numbers) - 1):
for j in range(i + 1, len_num):
tmp = int(numbers[i] + numbers[j]) - int(numbers[j] + numbers[i])
if tmp > 0:
numbers[i], numbers[j] = numbers[j], numbers[i]
return ''.join(numbers)
剑指offer_35:数组中的逆序对
题目描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007。
思路
分治思想,采用归并排序的思路来处理,如下图,先分后治
先把数组分解成两个长度为2的子数组,再把这两个子数组分解成两个长度为1的子数组。接下来一边合并相邻的子数组,一边统计逆序对的数目。在第一对长度为1的子数组{7}、{5}中7>5,因此(7,5)组成一个逆序对。同样在第二对长度为1的子数组{6},{4}中也有逆序对(6,4),由于已经统计了这两对子数组内部的逆序对,因此需要把这两对子数组进行排序,避免在之后的统计过程中重复统计。
逆序对的总数 = 左边数组中的逆序对的数量 + 右边数组中逆序对的数量 + 左右结合成新的顺序数组时中出现的逆序对的数量
这是一个归并排序的合并过程,主要是考虑合并两个有序序列时,计算逆序对数。
对于两个升序序列,设置两个下标:两个有序序列的末尾。每次比较两个末尾值,如果前末尾大于后末尾值,则有”后序列当前长度“个逆序对;否则不构成逆序对。然后把较大值拷贝到辅助数组的末尾,即最终要将两个有序序列合并到辅助数组并有序。
这样,每次在合并前,先递归地处理左半段、右半段,则左、右半段有序,且左右半段的逆序对数可得到,再计算左右半段合并时逆序对的个数。
代码
# -*- coding:utf-8 -*-
class Solution:
def InversePairs(self, data):
# write code here
if not data:
return 0
temp = data[:]
return self.mergeSort(temp, data, 0, len(data) - 1) % 1000000007
def mergeSort(self, temp, data, low, high):
if low >= high:
temp[low] = data[low]
return 0
mid = (low + high) / 2
left = self.mergeSort(data, temp, low, mid)
right = self.mergeSort(data, temp, mid + 1, high)
count = 0
i = low
j = mid + 1
index = low
while i <= mid and j <= high:
if data[i] <= data[j]:
temp[index] = data[i]
i += 1
else:
temp[index] = data[j]
count += mid - i + 1
j += 1
index += 1
while i <= mid:
temp[index] = data[i]
i += 1
index += 1
while j <= high:
temp[index] = data[j]
j += 1
index += 1
return count + left + right
剑指offer_37:数字在排序数组中出现的次数
题目描述
统计一个数字在排序数组中出现的次数。
思路
既然是已经排序好的数组,那么第一个想到的就是二分查找法。做法就是使用二分法找到数字在数组中出现的第一个位置,再利用二分法找到数字在数组中出现的第二个位置。时间复杂度为O(nlogn + nlogn),最终的时间复杂度为O(nlogn)。
代码
class Solution:
def GetNumberOfK(self, data, k):
left = 0
right = len(data) - 1
left_k = self.get_left(data, k, left, right)
right_k = self.get_right(data, k, left, right)
return right_k - left_k + 1
def get_left(self, data, k, left, right):
while left <= right:
mid = (left + right) // 2
if data[mid] < k:
left = mid + 1
else:
right = mid - 1
return left
def get_right(self, data, k, left, right):
while left <= right:
mid = (left + right) // 2
if k < data[mid]:
right = mid - 1
else:
left = mid + 1
return right
剑指offer_40:数组中只出现一次的数字
题目描述
一个整型数组里除了两个数字之外,其他的数字都出现了偶数次。请写程序找出这两个只出现一次的数字。
思路
从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。因为这两个数字不同,所以异或结果肯定不为0,且能够区分开这两个数字的地方就是异或结果中为1的位。假设异或结果中从右向左数第n位为1,那么我们就可以把原来数组中的数字按照第n位是否为1分为两部分,每一部分就会只包含一个只出现一次的数字,且其他的数字都是成对出现。接下来只要分别对这两个数组求异或,就能找出每部分只出现一次的数字。
代码
class Solution:
# 返回[a,b] 其中ab是出现一次的两个数字
def FindNumsAppearOnce(self, array):
# write code here
xor = 0
for item in array:
xor ^= item
mark = 1
while xor & mark == 0:
mark <<= 1
num1, num2 = 0, 0
for item in array:
if item & mark == 0:
num1 ^= item
else:
num2 ^= item
return num1, num2
剑指offer_50:数组中重复的数
题目描述
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
思路
把当前序列当成是一个下标和下标对应值是相同的数组(时间复杂度为O(n),空间复杂度为O(1)); 遍历数组,判断当前位的值和下标是否相等:
若相等,则遍历下一位;若不等,则将当前位置i上的元素和a[i]位置上的元素比较:若它们相等,则找到了第一个相同的元素;若不等,则将它们两交换。换完之后a[i]位置上的值和它的下标是对应的,但i位置上的元素和下标并不一定对应;重复2的操作,直到当前位置i的值也为i,将i向后移一位,再重复2。
代码
class Solution:
def duplicate(self, numbers, duplication):
for i in range(len(numbers)):
if numbers[i] != i:
tmp = numbers[numbers[i]]
if tmp == numbers[i]:
duplication[0] = numbers[i]
return True
else:
numbers[numbers[i]] = numbers[i]
numbers[i] = tmp
return False
剑指offer_51:构建乘积数组
题目描述
给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]*A[1]*…*A[i-1]*A[i+1]*…*A[n-1]。不能使用除法。
思路
可以把B[i]=A[0]*A[1]*…*A[i-1]*A[i+1]*…*A[n-1]。看成A[0]*A[1]*…*A[i-1]和A[i+1]*…A[n-2]*A[n-1]两部分的乘积。
不妨设定C[i]=A[0]*A[1]*…*A[i-1],D[i]=A[i+1]*…*A[n-2]*A[n-1]。C[i]可以用自上而下的顺序计算出来,即C[i]=C[i-1]*A[i-1]。类似的,D[i]可以用自下而上的顺序计算出来,即D[i]=D[i+1]*A[i+1]
代码
# -*- coding:utf-8 -*-
class Solution:
def multiply(self, A):
# write code here
B = [1]
if not A:
return []
for i in range(1, len(A)):
B.append(B[i-1]*A[i-1])
tmp = 1
for i in range(len(A)-1)[::-1]:
tmp *= A[i+1]
B[i] *= tmp
return B
参考1: 剑指Offer系列刷题笔记汇总.
参考2: 负雪明烛.
参考3: 牛客网-剑指offer在线编程.