题目合集:
方法1:二分法
**二分法:
mid=(left+right)/2
left=mid+1
right=mid
例题1:不在排序数组内的数字
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例 1:
输入: [0,1,3]
输出: 2
示例 2:
输入: [0,1,2,3,4,5,6,7,9]
输出: 8
不多废话直接看代码:
class Solution(object):
def missingNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
def helper(b1,b2):#p为下表处理
if b1==b2:
if b1==nums[b1]:
return b1+1
else:
return b1
# 计算新的中位下标=======
if (b1+b2)%2==1:
mid=int((b1+b2-1)/2)
else:
mid=int((b1+b2)/2-1)
if mid==nums[mid]:
return helper(mid+1,b2)
else:
return helper(b1,mid)
return helper(0,len(nums)-1)
例题2:target开始与结束位置
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
n = len(nums); L = 0; R = n
while L < R:
mid = int(L + (R-L)/2)
if nums[mid] == target:
start = mid - 1; end = mid + 1
while start >= 0 and nums[start] == target: start-=1
while end < n and nums[end] == target: end+=1
return [start+1, end-1]
elif nums[mid] < target: L = mid + 1
else: R = mid
return [-1, -1]
例题3:旋转数组的最小值
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
们考虑数组中的最后一个元素 x:在最小值右侧的元素,它们的值一定都小于等于 x;而在最小值左侧的元素,它们的值一定都大于等于 x。因此,我们可以根据这一条性质,通过二分查找的方法找出最小值。
class Solution(object):
def minArray(self, numbers):
"""
:type numbers: List[int]
:rtype: int
"""
if len(numbers)==1: return numbers[0]
i,j=0,len(numbers)-1
while i<j:
mid=(i+j)//2
#如果mid在最小值左侧
if numbers[mid]>numbers[j]:
i=mid+1
#如果mid在最小值左侧
elif numbers[mid]<numbers[j]:
j=mid
else:
j-=1
return numbers[i]
例题4:排序数组中target的个数
统计一个数字在排序数组中出现的次数。
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
count=0
def helper(left,right):
if left>=right: return left,right
mid=(left+right)//2
if target<nums[mid]:
return helper(left,mid)
elif target>nums[mid]:
return helper(mid+1, right)
else:
return left,right
l,r= helper(0,len(nums)-1)
if l==r:
return 1 if nums[r]==target else 0
count=0
for i in range(l,r+1):
if nums[i]==target:
count+=1
return count
s=Solution()
print(s.search([5,7,7,8,8,10],6))
方法2:摩尔投票法
哈希表统计法: 遍历数组 nums ,用 HashMap 统计各数字的数量,即可找出 众数 。此方法时间和空间复杂度均为 O(N)O(N) 。
数组排序法: 将数组 nums 排序,数组中点的元素 一定为众数。
摩尔投票法: 核心理念为 票数正负抵消 。此方法时间和空间复杂度分别为 O(N)O(N) 和 O(1)O(1) ,为本题的最佳解法。
例题1:不在数组内的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
摩尔投票法:
设输入数组 nums 的众数为 x ,数组长度为 n 。
推论一: 若记 众数 的票数为 +1 ,非众数 的票数为 −1 ,则一定有所有数字的 票数和 >0(题目条件:总存在一个多数元素) 。
推论二: 若数组的前 a个数字的 票数和 = 0,则 数组剩余(n−a) 个数字的票数和一定仍 >0 ,即后 (n−a) 个数字的 众数仍为 x (题目条件:总存在一个多数元素)
根据以上推论,假设数组首个元素 n_1。
当发生 票数和=0 时,剩余数组的众数一定不变 ,这是由于:
当 n_1=x: 抵消的所有数字,有一半是众数 x 。
当 n_1≠x : 抵消的所有数字,少于或等于一半是众数 x 。
利用此特性,每轮假设发生 票数和 = 0=0 都可以 缩小剩余数组区间 。当遍历完成时,最后一轮假设的数字即为众数。
class Solution:
def majorityElement(self, nums: List[int]) -> int:
votes = 0
for num in nums:
if votes == 0: x = num
if num == x:
votes += 1
else:
votes += 1
return x
方法3:滑动窗口法
滑动窗口可以看成数组中框起来的一个部分。在一些数组类题目中,我们可以用滑动窗口来观察可能的候选结果。当滑动窗口从数组的左边滑到了右边,我们就可以从所有的候选结果中找到最优的结果。
例题1:和为target的连续序列
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
输入:target = 9
输出:[[2,3,4],[4,5]]
题解:当窗口的和小于 target 的时候,窗口的和需要增加,所以要扩大窗口,窗口的右边界向右移动。
当窗口的和大于 target 的时候,窗口的和需要减少,所以要缩小窗口,窗口的左边界向右移动。
当窗口的和恰好等于 target 的时候,我们需要记录此时的结果。设此时的窗口为 [i, j),那么我们已经找到了一个 i 开头的序列,也是唯一一个 i 开头的序列,接下来需要找 i+1开头的序列,所以窗口的左边界要向右移动。
class Solution(object):
def findContinuousSequence(self, target):
"""
:type target: int
:rtype: List[List[int]]
"""
result=[]
stack=[1]
n=1
while stack:
if sum(stack)<target:
n+=1
stack.append(n)
elif sum(stack)>target:
stack.pop(0)
else:
if len(stack)>1:
result.append(stack[:])
n += 1
stack.append(n)
return result
典型例题:数组扁平化
数组的扁平化,就是将一个嵌套多层的数组 array (嵌套可以是任何层数)转换为只有一层的数组。
输入:[1, [2, [3, 4]]]
输出:[1, 2, 3, 4]
let arr = [1, [2, [3, 4]]];
function flattern(arr) {
let result = [];
for(let i = 0; i < arr.length; i++) {
if(Array.isArray(arr[i])) {
flattern(arr[i])
} else {
result.push(arr[i])
}
}
return result;
}
console.log(flattern(arr));