原题
请实现无重复数字的升序数组的二分查找。给定一个 元素升序的、无重复数字的整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标(下标从 0 开始),否则返回 -1
class Solution:
def search(self , nums: List[int], target: int) -> int:
if not nums:
return -1
l = 0
r = len(nums)-1
while l <= r:
m = l+(r-l)//2
if target < nums[m]:
r = m-1
elif target > nums[m]:
l = m+1
else:
return m
return -1
原题
在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]]
给定 target = 7,返回 true。给定 target = 3,返回 false。
思路:左到右递增、上到下递增
从右上角元素开始,左边的元素都比其小,下面的元素都比其大,因此可以通过判断target和该元素的值确定左移还是下移。
class Solution:
def Find(self , target: int, array: List[List[int]]) -> bool:
# write code here
if len(array) == 0:
return False
i = 0
j = len(array[0])-1
while i< len(array) and j >= 0:
if target < array[i][j]:
j -= 1
elif target > array[i][j]:
i += 1
else:
return True
return False
原题
给定一个长度为n的数组nums,请你找到峰值并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个所在位置即可。
1.峰值元素是指其值严格大于左右相邻值的元素。严格大于即不能有等于
2.假设 nums[-1] = nums[n] = −∞
3.对于所有有效的 i 都有 nums[i] != nums[i + 1]
4.你可以使用O(logN)的时间复杂度实现此问题吗?
思路1:
题目保证没有相同元素,峰值是左右元素严格小于本身,所以最大值一定是峰值,因此只需要找到最大值的位置即可。
class Solution:
def findPeakElement(self , nums: List[int]) -> int:
# write code here
return nums.index(max(nums))
思路2:分治法
假设数组两边是负无穷,则只要从两边往高处走,即可找到峰值。
- 取中间元素mid,按照二分法可以将数组划分为【l , mid】和【mid+1 , r】
- 比较nums[mid]和nums[mid+1]的值
- 如果左边值大,说明右区间在下降,显然向右走不符合“往高处走",因此将区间往左缩小,即 r = m i d r=mid r=mid
- 如果右边值大,说明向右走是在往高处走,因此将区间往右缩小, l = m i d + 1 l=mid+1 l=mid+1
- 当 l = = r l==r l==r时即到达峰值
class Solution:
def findPeakElement(self , nums: List[int]) -> int:
# write code here
l = 0
r = len(nums)-1
while l < r:
mid = (l+r) // 2
if nums[mid] <= nums[mid+1]:
l = mid + 1
else:
r = mid
return l
原题
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P mod 1000000007
思路:
归并排序过程中计算逆序对,对于a和b,逆序对个数=a逆序对+b逆序对+合并ab的逆序对
- 划分数组,直至子数组长度为1
- 归并排序合并子数组,并计算逆序对
- i指向相邻子数组左数组,j指向相邻子数组右数组;
- 左到右遍历合并,对于 d a t a ( l , m i d , r ) data(l,mid,r) data(l,mid,r),令i = l, j = mid+1,依次将最小的元素填入合并的排序数组中。此处需要辅助数组temp存储原始两个待合并数组元素, d a t a ( l : r + 1 ) data(l:r+1) data(l:r+1)用于存储合并后数组元素。k的范围为(l,r),如果 t e m p [ i ] < = t e m p [ j ] temp[i]<=temp[j] temp[i]<=temp[j],则将 t e m p [ i ] temp[i] temp[i]填入data[k]中,如果 t e m p [ i ] > t e m p [ j ] temp[i]>temp[j] temp[i]>temp[j]大,说明存在逆序对,需要计算个数。考虑到temp(i : mid)本身就是有序的,所以如果temp[i]大于temp[j],那么 t e m p ( i + 1 , m i d ) temp(i+1,mid) temp(i+1,mid)都大于 t e m p [ j ] temp[j] temp[j],所以对于 t e m p [ j ] temp[j] temp[j]而言,前面可以有 m i d − i + 1 mid-i+1 mid−i+1个元素和其构成逆序对。因此当出现 t e m p [ i ] > t e m p [ j ] temp[i] > temp[j] temp[i]>temp[j]时,逆序对增加 m i d − i + 1 mid-i+1 mid−i+1个。
- 还有一种方式是从右到左遍历,即每次将最大的元素填入合并的排序数组中,过程和左到右遍历类似。
class Solution:
def InversePairs(self , data: List[int]) -> int:
# write code here
def divide(data,l,r,temp):
if l >= r:
return 0
mid = (l+r)//2
r1 = divide(data,l,mid,temp)
r2 = divide(data,mid+1,r,temp)
res = (r1 + r2) % 1000000007
i,j = l,mid+1
for k in range(l,r+1):
temp[k] = data[k]
for k in range(l,r+1):
if i == mid+1:
data[k] = temp[j]
j += 1
elif j == r+1 or temp[i] <= temp[j]:
data[k] = temp[i]
i += 1
else:
res += mid - i + 1
data[k] = temp[j]
j += 1
return res % 1000000007
temp = [0 for _ in range(len(data))]
return divide(data, 0, len(data)-1,temp)
#从右到左遍历
class Solution:
def InversePairs(self , data: List[int]) -> int:
# write code here
def divide(data,l,r,temp):
if l >= r:
return 0
mid = (l+r)//2
r1 = divide(data,l,mid,temp)
r2 = divide(data,mid+1,r,temp)
res = (r1 + r2) % 1000000007
i,j = mid,r
for k in range(l,r+1):
temp[k] = data[k]
for k in range(r,l-1,-1):
if j == mid:
data[k] = temp[i]
i -= 1
elif i == l-1 or temp[i] <= temp[j]:
data[k] = temp[j]
j -= 1
else:
res += j - mid
data[k] = temp[i]
i -= 1
return res % 1000000007
temp = [0 for _ in range(len(data))]
return divide(data, 0, len(data)-1,temp)
原题
有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。
思路1:
暴力法,原始非递减,旋转一部分元素放在数组后面,得到的旋转数组其实是两段非递减元素,因此只需要找到转折点,即后一个元素小于前面元素的位置,即为原来数组的第一个元素,也即最小元素。如果没有这个转折点,说明旋转数组的第一个元素就是最小的元素。
class Solution:
def minNumberInRotateArray(self , rotateArray: List[int]) -> int:
# write code here
for i in range(1,len(rotateArray)):
if rotateArray[i] < rotateArray[i-1]:
return rotateArray[i]
return rotateArray[0]
思路2:
利用数组有序,可以使用二分法
- 初始化左右边界l,r
- 取中间值mid【对于数组[4,5,6,7,1,2],[4,5,6,7]为左排序数组,[1,2]为右排序数组】
- 如果中间值小于最右边的值,说明中间值一定在右排序数组中;此时就缩小区间,r = mid
- 如果中间值大于最右边的值,说明中间值一定在左排序区间中;此时缩小左区间,l = mid + 1
class Solution:
def minNumberInRotateArray(self , rotateArray: List[int]) -> int:
# write code here
l,r = 0,len(rotateArray)-1
while l < r:
mid = (l+r) // 2
if rotateArray[mid] < rotateArray[r]:
r = mid
elif rotateArray[mid] > rotateArray[r]:
l = mid + 1
else:
r -= 1
return rotateArray[l]
原题
牛客项目发布项目版本时会有版本号,比如1.02.11,2.14.4等等。现在给你2个版本号version1和version2,请你比较他们的大小。版本号是由修订号组成,修订号与修订号之间由一个"."连接。1个修订号可能有多位数字组成,修订号可能包含前导0,且是合法的。例如,1.02.11,0.1,0.2都是合法的版本号;每个版本号至少包含1个修订号。修订号从左到右编号,下标从0开始,最左边的修订号下标为0,下一个修订号下标为1,以此类推。
比较规则:
一. 比较版本号时,请按从左到右的顺序依次比较它们的修订号。比较修订号时,只需比较忽略任何前导零后的整数值。比如"0.1"和"0.01"的版本号是相等的
二. 如果版本号没有指定某个下标处的修订号,则该修订号视为0。例如,“1.1"的版本号小于"1.1.1”。因为"1.1"的版本号相当于"1.1.0",第3位修订号的下标为0,小于1
三. version1 > version2 返回1,如果 version1 < version2 返回-1,不然返回0.
思路:
使用**“.”**分割符将字符串划分,并将其转化为整数数组,就无需处理前导零,依次比较每个位置的元素
预处理:1、将两个数组长度对齐,短数组后部补0;2、记录最短长度遍历,遍历完再遍历另一个数组多出的元素,此处采用第一种预处理
class Solution:
def compare(self , version1: str, version2: str) -> int:
# write code here
v1 = list(map(int,version1.split('.')))
v2 = list(map(int,version2.split('.')))
if len(v1) < len(v2):
for j in range(len(v1),len(v2)):
v1.append(0)
elif len(v1) > len(v2):
for j in range(len(v2),len(v1)):
v2.append(0)
for i in range(len(v1)):
if v1[i] < v2[i]:
return -1
elif v1[i] > v2[i]:
return 1
return 0