年前刷过的题已经有些忘了,建议大家刷题每天要固定时间,定量的刷效率才高点,不然三天打鱼两天晒网就会像我现在这样。。。
1.实现二维数组的查找(easy)
题目: 在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数
思路:
从二维数组的右上角的元素开始判断,因为此元素是它所在行的最大数,是它所在的列的最小数。
如果它等于要查找的数字,则查找过程结束。
如果它大于要查找的数字,则可以排除它所在的列,说明在左边,列-1
如果它小于要查找的数字,则可排除它所在的行,说明下方,行+1
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
if not matrix:
return False
row = len(matrix)
col = len(matrix[0])-1
i=0
while i<row and col>=0:
n = matrix[i][col]
if n==target:
return True
elif n>target:
col-=1
else:
i+=1
return False
2.寻找数组的中心索引(easy)
题目:给定一个整数类型的数组 nums,请编写一个能够返回数组“中心索引”的方法。定义数组中心索引的:数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。如果数组不存在中心索引,返回 -1。如果数组有多个中心索引,返回最靠近左边的那一个
思路:依次遍历求左边的和,当索引左边的和等于右边的和,即左边的和=(总和-当前索引的值)/2,则为中心索引
def pivotIndex(self, nums: List[int]) -> int:
total = sum(nums)
p = 0
for i,j in enumerate (nums):
if p == (total-j)/2:
return i
p += j
return -1
3.和为K的子数组(medium)
题目:给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
思路:1)暴力法,先确定一个数,然后依次遍历求和其与剩下的数,即遍历所有可能的子数组,并对每个子数组求和,用count计算个数
def subarraySum(self, nums: List[int], k: int) -> int:
count=0
for i in range(len(nums)):
total=0
for j in range(i,len(nums)):#注意,从i开始,不是i+1(会遗漏k=nums[i+1]的情况)
total+=nums[j]
if total==k:
count+=1
return count
2)利用哈希表存储total 以及其出现的次数,如果total-k在哈希表存在,则count加上其索引(出现的次数,类似求两数和,下一道题就是了),并且检查当前total是否存在哈希表内,没有则添加,有则次数再加一
def subarraySum(self, nums: List[int], k: int) -> int:
count=0
total=0
hash ={0:1}
for i in range(len(nums)):
total+=nums[i]
if total-k in hash:
count+=hash[total-k]
if total in hash:
hash[total]+=1
else:
hash[total]=1
return count
4.两数之和(easy)
题目:给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
思路:1)暴力法:从前往后遍历,将 索引 i 与其之后的值相加是否等于target
def twoSum(self, nums: List[int], target: int) -> List[int]:
for i in range(len(nums)):
m = target-nums[i]
for j in range(i+1,len(nums)):
if m == nums[j]:
return [i,j]
2)哈希法,先把nums的索引和值存入哈希表,再遍历数组,a=target-num[i],求对应的哈希表中对应的索引值h[a]。升级:两者合并,可以边查询边存入
def twoSum(self, nums: List[int], target: int) -> List[int]:
res = {}
for index,v in enumerate (nums):
if target-v in res: #查询
return [index,res[target-v]]
res[v]=index #存入
5.三数之和(medium)
题目:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组
思路:双指针+排序
1)先对数组排序,固定一个起始数,设置left和right指针分别指向n+1和最后一个数,通过这三数的和来判断指针的去向。
当nums[n] + nums[left] +nums[right]=0, 则添加这三个元素,left+=1,right-=1
当nums[n] + nums[left] +nums[right]>0,说明数字太大,right-=1
当nums[n] + nums[left] +nums[right]<0, left+=1
2)考虑出现重复数字的情况
三种情况:起始数,left和right所指向的数。因为数字重复直接跳过本次循环操作,进入下一个数字
3)考虑特殊情况,加快运行速度
以下两种情况直接返回 [ ]
a.数组长度小于3
b.数组长度大于3 并且起始数就大于0(排序过,第一个数大于0,再加上剩下的肯定大于0)
def threeSum(self, nums: List[int]) -> List[List[int]]:
n=len(nums)
nums = sorted(nums)
res=[]
if not nums or n<3:
return res
for i in range(n):
if i>0 and nums[i]==nums[i-1]: #考虑起始数重复情况
continue
left =i+1
right= n-1
target = 0-nums[i]
if nums[i]>0:
return res
while left<right:
if nums[left]+nums[right]==target:
res.append([nums[i],nums[left],nums[right]])
while left<right and nums[left+1]==nums[left]: #考虑left重复情况
left+=1
while left<right and nums[right]==nums[right-1]:#考虑right重复情况
right-=1
right-=1
left+=1
elif nums[left]+nums[right]>target:
right-=1
else:
left+=1
return res
6.四数之和(medium)
题目: 给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
思路:与求三数之和相似,还是采用双指针,多了一层循环。
注意在考虑特殊情况时,从四个元素的极大值和极小值思考过滤重复计算
1)针对起始数–极大值情况:[i]+nums[n-1]+nums[n-2]+nums[n-3]<target,说明最大的三个数和最小的初始数相加还小于target,则起始数要增大。
极小值情况–排序后的nums的起始四个数为最小,最小还大于target说明不用再找了
2)针对第二个元素同理
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums= sorted(nums)
n= len(nums)
res=[]
if not nums:
return res
for i in range(n-3):
if i>0 and nums[i]==nums[i-1]: #去除重复项
continue
if nums[i]+nums[n-1]+nums[n-2]+nums[n-3]<target:
continue
if nums[i]+nums[i+1]+nums[i+2]+nums[i+3]>target:
break
for j in range(i+1,n-2):
if j-i>1 and nums[j]==nums[j-1]:#注意j-i>1,去除重复项
continue
if nums[i]+nums[j]+nums[n-1]+ nums[n-2]<target:
continue
if nums[i]+nums[j]+nums[j+1]+nums[j+2]>target:
break
l = j+1
r= n-1
while l<r:
if nums[i]+nums[j]+nums[l]+nums[r]==target:
res.append([nums[i],nums[j],nums[l],nums[r]])
while l<r and nums[l]==nums[l+1]:
l+=1
while l<r and nums[r]==nums[r-1]:
r-=1
l+=1
r-=1
elif nums[i]+nums[j]+nums[l]+nums[r]>target:
r-=1
else :
l+=1
return res
7.最接近的三数之和(medium)
题目:给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
思路:1)类似求解三数之和,再算多一步三数之和与target的差值,将和,差值存入哈希表,取哈希表差值最小对应的和。
2)也可以将差值比较后直接保存最小值,不用哈希表
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums= sorted(nums)
length = len(nums)
res={}
for n in range(length-2):
l= n+1
r= length-1
while l<r:
s = nums[n]+nums[l]+nums[r]
ans = abs(s-target)
res[ans]=s
if s>target:
r-=1
elif s<target:
l+=1
else:
return s
return res[min(res.keys())]
#第二种----------------------------------------------
nums= sorted(nums)
length = len(nums)
ans = nums[0]+nums[1]+nums[2]
for n in range(length-2):
l= n+1
r= length-1
while l<r:
s = nums[n]+nums[l]+nums[r]
if abs(s-target) <abs(ans-target):
ans= s
if s>target:
r-=1
elif s<target:
l+=1
else:
return s
return ans
8.最短无序连续子数组(easy)
题目:给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
思路:
1)初始化左边界left=len(nums),right=0,
从前往后,依次找到当前数值大于下一个数的情况,eg:[1,3,2],当前数 i 为3时,下一个数 j 为2,此时左边界应变成3的位置(当前的数与左边界对比取最小值即最左边不满足升序条件的),右边界应为2的位置(下一个数与右边界对比取最大值即最尾部不满足升序条件的)
注意:之前想当然以为从前往后,找到当前的数大于下一个数的作为左边界,从后往前,找到当前数小于前一个数的作为右边界。但这样是不行的,未考虑特殊情况,例如[1,3,2,2]当后面是有序,前面无序时,按照这种思路就默认了右边界,但此时不正确的数是比后面有序的数字还大需要再排到后面的。
def findUnsortedSubarray(self, nums: List[int]) -> int:
n= len(nums)
left=n
right=0
for i in range (n-1):
for j in range(i+1,n):
if nums[i]>nums[j]:
right=max(j,right)
left =min(i,left)
if right-left>0:
return right-left+1
else:
return 0
2)先对数组排序,再跟原数组对比,记录出现不一样的数字的索引,用从中这些索引中最大值减去最小值+1即为所求区间长度
def findUnsortedSubarray(self, nums: List[int]) -> int:
#使用了zip函数【将迭代器里对应的元素打包成一个个元组,返回打包为元组的列表】
#eg. a = [1,2] b = [4,5] z=zip(a,b) -->[(1, 4), (2, 5)]
diff=[]
for i,(a,b) in enumerate(zip(nums, sorted(nums))):
if a!= b:
diff.append(i)
return len(diff) and max(diff)-min(diff)+1
#---------------不用zip---------------------------
diff=[]
nums_sort = sorted(nums)
for i in range(len(nums)):
if nums[i] != nums_sort[i]:
diff.append(i)
return len(diff) and max(diff)-min(diff)+1
9.至少是其他数字两倍的最大数(easy)
题目:在一个给定的数组nums中,总是存在一个最大元素 。查找数组中的最大元素是否至少是数组中每个其他数字的两倍。如果是,则返回最大元素的索引,否则返回-1。
思路:只需要判断最大元素是否是第二大元素的2倍或者2倍以上即可,需先定义最大值和次大的值,以及最大值的索引
def dominantIndex(self, nums: List[int]) -> int:
m_index= 0
maxs=0
maxsec=0
for i in range(len(nums)):
if nums[i]>maxs:
maxsec=maxs
maxs=nums[i]
m_index=i
elif nums[i]>maxsec:
maxsec=nums[i]
if maxs>=maxsec*2:
return m_index
else:
return -1
10.合并区间(medium)
题目:给出一个区间的集合,请合并所有重叠的区间。
思路:先按照区间的第一个元素排序,依次遍历区间,合并的条件就是当前区间 i 的右边界 i[1] 要大于下一个区间的左边界(i+1)[0],合并后的右边界根据当前和下一个区间的右边界哪个大来决定,即i[1]与i+1[1]取最大值,如果不满足条件则保留区间
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort(key=lambda x: x[0]) # 先按区间左边界值由小到大排序
res =[]
i =0
while i < len(intervals):
l = intervals[i][0] #当前区间的左边界
r = intervals[i][1] #当前区间的右边界
while (i <len(intervals)-1 )and (intervals[i+1][0]<=r): #当前区间的右边界大于下一区间的左边界
i+=1
r = max(intervals[i][1],r) #取最大值为右边界
res.append([l,r])
i+=1
return res
11.螺旋矩阵(medium)
题目: 给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。
思路:初始化左右上下四个边界为l=0,r=len(metric[0])-1,t=0,b=len(metric)-1。result[]保存结果。当l<=r and t<=b,进入循环:【左上角为(t,l).右下角(d,r)】
从左到右:y从l到r,x=t,循环完t+=1(以防重复),并判断t>b,是的话退出循环
从上到下:x从t到b,y=r,循环完r-=1,并判断l>r
从右到左:y从r到l,x=b,循环完b-=1并判断t>b
从下到上,x从b到t,y = l,循环完 l+=1,并判断l>r
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if not matrix :
return []
t=0
b=len(matrix)-1
l=0
r = len(matrix[0]) - 1
result=[]
while t<=b and l<=r:
for y in range(l,r+1):
x=t
result.append(matrix[x][y])
t+=1
if t >b:break
for x in range(t,b+1):
y=r
result.append(matrix[x][y])
r-=1
if r<l:break
for y in range(r,l-1,-1):
x=b
result.append(matrix[x][y])
b-=1
if b<t:break
for x in range(b,t-1,-1):
y = l
result.append(matrix[x][y])
l+=1
if l>r:break
return result
12.螺旋矩阵 II (medium)
题目: 给定一个正整数 n,生成一个包含 1 到 n**2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
思路:在11题的基础上,将每次从矩阵中对应的位置取值改为对应位置添加值
def generateMatrix(self, n: int) -> List[List[int]]:
l=0
r=n-1
t=0
b=n-1
res = [[0 for _ in range(n)] for _ in range(n)]
num=1
while num<=n*n:
for y in range(l,r+1):
x=t
res[x][y]=num
num+=1
t+=1
for x in range(t,b+1):
y=r
res[x][y]=num
num+=1
r-=1
for y in range(r,l-1,-1):
x=b
res[x][y]=num
num+=1
b-=1
for x in range(b,t-1,-1):
y=l
res[x][y]=num
num+=1
l+=1
return res
13.盛最多水的容器(medium)
题目:给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。说明:你不能倾斜容器,且 n 的值至少为 2
思路:其实就是木桶效应,盛最多水的容器体积的宽取决于短边,长则为两条直线的距离。
于是问题转化为遍历数组,采用双指针,left指针指向第一条,right指向最后一条,
每求两条直线中的短边,计算此时的体积并保存。
当left为短边,向前遍历,长边保持不变;
当right为短边时,向后遍历,直到left和right相遇则遍历完数组。
计算最大体积时,可在保存体积时,顺便比较当前体积与之前保留的最大体积,每次保留最大的体积,最后保存的就是最大的体积了,比较笨的方法就是新建一个数组,每次都存进数组,最后再取数组中的最大值
def maxArea(self, height: List[int]) -> int:
res=0
l=0
r=len(height)-1
while l<r:
if height[l]>height[r]:
col=height[r]
r-=1
else:
col=height[l]
l+=1
row=r-l+1
res =max(res,row*col)
return res
14.接雨水(hard)
题目:给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
思路:有凹陷的地方才能存水,即高度为先减后增的趋势。因此数组包含的元素至少要3个以上。
思路:用栈来存储柱子下降的趋势即前一个柱子,存的是其索引值。
从左到右遍历:当stack 存在并且当前柱子大于stack顶元素时,弹出栈顶元素top,如果此时栈为空,则跳出循环。
存水的面积的高度h为{min(当前柱子的高度,栈顶元素在其柱子中的高度)【此时的栈顶元素是刚弹出后的下一个了】-刚弹出的栈顶元素}【木桶原理】
宽度则为当前idex-stack[-1]-1;栈存的是索引!!如果栈不存在则往里面添加index。
def trap(self, height: List[int]) -> int:
if len(height)<3:
return 0
res=0
stack=[]
i=0
while i <len(height):
while len(stack)>0 and height[i]>height[stack[-1]]:
top=stack.pop()
if len(stack)==0:
break
h=min(height[i],height[stack[-1]])-height[top]
w = i-stack[-1]-1
res+=(h*w)
stack.append(i)
i+=1
return res
15.搜索旋转排序数组(medium)
题目:假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。你可以假设数组中不存在重复的元素。你的算法时间复杂度必须是 O(log n) 级别。
思路:果然这种就是属于一看就会,一写就废的题【边界问题】你的算法时间复杂度必须是 O(logn) 级别—》进一步暗示我们要用二分查找。
class Solution:
def search(self, nums: List[int], target: int) -> int:
length = len(nums)
if length==0:
return -1
l=0
r=length-1
while l<=r:
mid = l +(r-l)//2
if nums[mid]==target:
return mid
if nums[mid] >= nums[l]:
if nums[l] <=target<nums[mid]:
r = mid-1
else:
l = mid+1
else:
if nums[mid]<target<=nums[r]:
l = mid+1
else:
r = mid-1
return -1
16.数组中数字出现的次数(medium)
题目:一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)
思路:1).将数组进行排序,然后当前和下一个数字一致的话,就将当前数字跳一格。不同的话则将当前数字存入列表中,并采用n标记存入的数的个数,当已经找到这两个只出现一次的数字时就可以提前结束遍历了。
2)也可以采用Counter函数,统计每个数字出现次数,取为一的加入列表中
#1)--------------------------
def singleNumbers(self, nums: List[int]) -> List[int]:
nums =sorted(nums)
res = []
n = 0
i=0
while i < len(nums) -1:
if nums[i]!=nums[i+1]:
res.append(nums[i])
n+=1
i+=1
else:
i+=2
if n<2 and i == len(nums)-1:
res.append(nums[i])
n+=1
if n == 2:
return res
#2)--------------------------
def singleNumbers(self, nums: List[int]) -> List[int]:
ans = Counter(nums)
res = []
for val, c in ans.items():
if c == 1:
res.append(val)
return res
17.最大子序和(easy)
题目:给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
思路:设置两个变量分别表示当当前最大连续子序列和,最大子序和。
当当前最大连续子序列和大于0 时,则说明后序的数值可以直接加入,
反之,当前最大连续子序列和小于0 ,说明是负数则直接以当前层来更新当前最大连续子序列和
每次都更新最大子序和
def maxSubArray(self, nums: List[int]) -> int:
max_num =nums[0] #保存连续数组的最大和
Sum=0 #初始化当前最大连续子序列和为0
if len(nums)==1:
return nums[0]
for i in nums:
if Sum>0: #当前最大连续子序列和大于0
Sum+= i #则直接加上当前层的数值
else:
Sum=i #因为加上前面的数反而越来越小了,所以以当前层开始重新计算最大连续子序列和
max_num =max(Sum,max_num) #更新最大子序和
return max_num
18.和可被 K 整除的子数组(medium)
题目:给定一个整数数组 A,返回其中元素之和可被 K 整除的(连续、非空)子数组的数目。
示例:
输入:A = [4,5,0,-2,-3,1], K = 5
输出:7
解释:
有 7 个子数组满足其元素之和可被 K = 5 整除:
[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]
思路:通常,涉及连续子数组问题的时候,考虑使用前缀和来解决。
采用哈希表+排列组合的思路:用哈希表记录前缀和的数值,以及其出现的次数,由同余定理(两个数除以同个数k得到的余数相同,说明存在被k整除的数。eg,a=3,b=8,k=5,(b-a)==0)可知,出现次数大于1的次数排列组合就是最终结果。
def subarraysDivByK(self, A: List[int], K: int) -> int:
h = dict()
h[0]=1
res=0
Sum=0
for i in A:
Sum +=i #前缀和
rem = Sum%K #求余
if rem not in h:
h[rem]=1
else:
h[rem]+=1
for i,x in h.items():
res += x*(x-1)//2 #根据出现的次数算排列组合
return res
19.合并两个有序数组(easy)
题目:给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
说明:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素
示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
思路:
1.双指针,从后往前遍历:
以p2遍历nums2为基准,将num2合并到nums1中。
设置p1指向nums1的m-1位置,p2 指向nums2尾部即n-1位置,p3为在nums1基础上改动合并nums2形成有序数组,从后往前遍历【数字呈递减】,当p1>p2时,则p3指向的值等于p1指向的值。反之则等于p2,当p1遍历完nums1,则直接将nums2剩下的数添加到nums1
最后返回num1
Do not return anything, modify nums1 in-place instead.
"""
p1 = m-1
p2 = n-1
p3 = m+n-1
while p2>=0:
if p1>=0 and nums1[p1]>nums2[p2]:
nums1[p3] = nums1[p1]
p1-=1
else:
#包括两种情况,一种是p1<0时,一种是p1>=0,并且 nums1[p1]<nums2[p2]
nums1[p3] = nums2[p2]
p2-=1
p3-=1
return nums1