数组专题
数组特点在内存地址中连续,不能单独删除,只能覆盖。
对于数组问题,注意是否有重复,是否有序。
方法上可以考虑二分法、双指针法、暴力解法等。
易错点是数组的边界范围,数组的index是从0开始。注意index的整数运算。
704 二分查找 (二分法)
Example:
Input: nums = [-1,0,3,5,9,12], target = 9 Output: 4 Explanation: 9 exists in nums and its index is 4
思路:
数组有序且无重复,可以考虑二分法
Solution:
方法一:二分法(recursive binary search implementation)
1)注意!新的搜索区间选择。我喜欢用“左闭右闭”的区间,即区间内最左或者最右的元素都可能是mid被判断到。每次算mid的时候用整除,这样相当于是向下取整,即floor运算,永远”四舍“。
E,g. [0,1,2,3,4,5]
mid=(0+5) // 2 = 2, 则取出第三个元素和target比较,
如果小于target,下一次应该从 (mid,end], 即 [mid+1,end]去搜索。因为mid这个元素已经判断过不是,下一次的搜索范围要跳过,记得对mid+1。
如果大于target,下一次应该从 [start,mid-1] 去搜索。同上,记得对mid-1。
2)注意!循环截止条件。
start>end
因为新的区间左右边界的值都是有可能被考虑的,所以在start==end的时候,也是可以再算一次mid来判断的,如果还不对,下一次mid+1或者mid-1就start>end了,也就该跳出循环了。
具体来说,每次算mid的时候用整除,这样相当于是向下取整,即floor运算,永远”四舍“。在start与end相邻的时候(start=end-1),start会作为mid被取到。此时如果start<target的话,新的start=mid+1,也就是start=start+1,就和end相等了,即下一个搜索区间start == end,再执行一次搜索操作。这一次start==end==mid。如果start>target的话,end=mid-1,也就是end = start-1,即 end<mid,符合递归终止条件,新的搜索范围不再有效,跳出循环,终止程序。
class Solution(object):
def search(self, nums, target):
return self.binsearch(nums, 0, len(nums)-1, target)
def binsearch(self, nums, start, end, target):
# Step1: 当前区间是否合理,判断是否满足跳出循环的条件
if start > end:
return -1 # 按照题目要求,return -1
# Step2: 当前区间合理,判断中值,看是否要继续二分。
mid = (start + end) // 2 # 注意整数除法用 //, 浮点型除法用 /
# Step2.1:找到啦,return返回。
if nums[mid] == target:
return mid
# Step2.2:要找新的区间继续搜索
elif nums[mid] > target:
return self.binsearch(nums, start, mid-1, target)
else:
return self.binsearch(nums, mid+1, end, target) # return 不要忘记啊
时间复杂度: O(logn)。因为每一步因为每一步算法都会将搜索空间分成两半。
空间复杂度: 由于递归调用堆栈,O(logn)。否则的话是 O(1)。
27 移除元素 (双指针法)
Example:
Input: nums = [0,1,2,2,3,0,4,2], val = 2 Output: 5, nums = [0,1,4,0,3,_,_,_]
分析:
数组在存储地址上是连续的,删除掉数组的一个元素,后面的元素都需要递补。
Solution:
方法一:暴力解法
两层循环嵌套,一个for循环遍历数组,一个for循环把被删元素之后的元素更新。
算法复杂度:O(n^2)。两次for循环,最坏的情况是要移动元素1+2+3+...+n = n*(n+1)/2 次。
方法二:快慢指针
快指针遍历每个值,慢指针记录有效值。有效值记下来;没用的无效值,覆盖掉就好。
快慢指针是同向双指针,也可以用异向双指针,右侧指针始终指向一个有效值,但凡左指针指向了无效值,就把右侧的指针指向的值替换给左指针对应的位置。
def removeElement(self, nums: List[int], val: int) -> int:
cnt = 0 # 用cnt来记录有效的位数
for i in range(len(nums)):
if nums[i] != val: #当前快指针所指向的数据有效,记录下来!
nums[cnt] = nums[i]
cnt += 1
return cnt
时间复杂度:O(n),每一个元素被处理常数项次数。
空间复杂度:额外O(1),存储cnt,原地修改原数组。
#############################################################################
74. Search a 2D Matrix
You are given an m x n
integer matrix matrix
with the following two properties:
- Each row is sorted in non-decreasing order.
- The first integer of each row is greater than the last integer of the previous row.
Given an integer target
, return true
if target
is in matrix
or false
otherwise.
You must write a solution in O(log(m * n))
time complexity.
思路:此题类似,只不过是从2Dmatrix中搜索,重复两遍二分法罢了。先按照第一列的值找到合适的行,再在那行按列搜索。关键点在于行搜索完后,如果第一列就已经找到target值,自然return True,如果没有的话,就是不满足while start <= end的条件,也就是end<start。这时开启第二个二分法,要对end行进行搜索,而不是start。仔细想想看,是因为这时候已经end<start了,其实意思是要在end到start的区间继续搜索(不过这对第一个二分法不再成立了,因为两个数之间没有更多数可以搜索了),这也就是第二个二分法需要搜索的区间,也就是end所在的行。
这道题还有一个点是,求2D list的size,要用len(matrix), len(matrix[0])。list这个object是没有shape() attribute的。
另外就是,时隔一个月再写这道题,一不小心把end的起始直接写成了m而不是m-1,index的问题虽小,但实在要注意。
class Solution(object):
def searchMatrix(self, matrix, target):
"""
:type matrix: List[List[int]]
:type target: int
:rtype: bool
"""
m,n = len(matrix), len(matrix[0])
start = 0
end = m-1
if start == end:
pass
else:
while start <= end:
mid = (start+end)//2
mid_val = matrix[mid][0]
if mid_val == target:
return True
elif mid_val < target:
start = mid + 1
else:
end = mid - 1
start_row = end
#print(start)
start, end = 0, n-1
if start == end:
if target == matrix[start_row][0]:
return True
else:
return False
while start <= end:
mid = (start+end)//2
mid_val = matrix[start_row][mid]
if mid_val == target:
return True
elif mid_val < target:
start = mid + 1
else:
end = mid - 1
return False
There is a long table with a line of plates and candles arranged on top of it. You are given a 0-indexed string s
consisting of characters '*'
and '|'
only, where a '*'
represents a plate and a '|'
represents a candle.
You are also given a 0-indexed 2D integer array queries
where queries[i] = [lefti, righti]
denotes the substring s[lefti...righti]
(inclusive). For each query, you need to find the number of plates between candles that are in the substring. A plate is considered between candles if there is at least one candle to its left and at least one candle to its right in the substring.
- For example,
s = "||**||**|*"
, and a query[3, 8]
denotes the substring"*||**|"
. The number of plates between candles in this substring is2
, each of the two plates has at least one candle in the substring to its left and right.
Return an integer array answer
where answer[i]
is the answer to the ith
query.
思路:用二分法找出要求的query中的蜡烛之间夹着的盘子。把蜡烛的index记录下来,然后在这个蜡烛index的list中找出query中的index,如果找不到(也就是query中的index其实指向了盘子),那就找到相邻的index(二分法时right和left每次只+/-1最后肯定会指向一个和target index相邻的蜡烛index),然后求这两个最后找出来的index之间的距离,然后减去这两个index在蜡烛index的list中的距离,得到的就是所求盘子的个数。
注意,这道题目里不是直接用二分法去找原list的值,而是把原list的值转成index,然后再用二分法去找。
如果找不到蜡烛的index而要用相邻的,这里是return left还是right最好举例子想清楚。
class Solution:
def just_larger_than(self, candle_idx, target):
left, right = 0, len(candle_idx)-1
while left<=right:
m = (left+right)//2
if candle_idx[m] == target:
return m
if candle_idx[m] > target:
right = m-1
else:
left = m+1
return left
def just_smaller_than(self, candle_idx, target):
left, right = 0, len(candle_idx)-1
while left<=right:
m = (left+right)//2
if candle_idx[m] == target:
return m
if candle_idx[m] > target:
right = m-1
else:
left = m+1
return right
def platesBetweenCandles(self, s, queries):
#brute force
n = len(s)
candles = []
out = []
for i in range(n):
if s[i]=='|':
candles.append(i)
for left,right in queries:
#find the index of the first candle of the given interval, in the candles array
firstIndx = self.just_larger_than(candles,left)
#find the index of the last candle of the given interval, in the candles array
lastIndx = self.just_smaller_than(candles,right)
#if no candles in the given interval
if firstIndx>=len(candles) or candles[lastIndx]>right or candles[firstIndx]<left:
out.append(0)
continue
#number of candles
num_candles = lastIndx-firstIndx-1
# print("num candles in range",num_candles)
#number of items between first and last candle - this interval would contain both plates and candles
items = candles[lastIndx]-candles[firstIndx]-1
#Remove the candles from the above interval to get the plates
num_plates = max(0,items-num_candles)
out.append(num_plates)
return out