数组
数组理论基础
概念:数组是存放在连续内存空间上的相同类型数据的集合,如:
数组特点:数组的内存空间的地址连续,元素是不能删的,只能覆盖。如果要添加或删除元素,就要移动别的元素。
内存地址特点:通常以“0x”开头,表示随后的数字是十六进制格式,如0x7ffee4065820。计算机中字节(byte)是最基本的数据单位,每个字节包含8位(bit)。一般int类型的大小是4个字节,意味着相邻的int数组元素在内存中相隔4个字节。
二分查找
特点:有序元素数组是二分查找的基础(无重复更好)。
原题:
题目描述:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
思路:采用二分法,在初始区间中取中间索引的值,将其与目标值比较,并根据大小关系不断缩短搜索区间。
注意:为了防止溢出,在计算区间的中点时,我们采用middle = left + (right - left)// 2
(//为floor division,返回商的整数部分)
代码
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums)-1
while left <= right:
middle = left + (right-left)//2
if nums[middle] < target:
right = middle-1
elif nums[middle] > target:
left = middle+1
else:
return middle
return -1
注意:上面更新方式为right = middle-1
,left = middle+1
这样可以确保每次迭代都会缩小搜索区间,并且避免无限循环的可能性。
相关题目
35.搜索插入位置
题目表述:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
代码:
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums)-1
while left <= right:
middle = left + (right-left)//2
if nums[middle] < target:
right = middle-1
elif nums[middle] > target:
left = middle+1
else:
return middle
return right+1
解析为什么返回right+1
:不满足while left <= right:
的上一轮必然是left==right==middle
,而由于nums[middle]>target
,使得left = middle+1 >right
。又因为在缩为一个值之前,target值必然包含在[left,right]的区间内的。所以可以判断,right 指向了 target 应该被插入的位置的前一个索引。
34. 在排序数组中查找元素的第一个和最后一个位置
题目表述:给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]。
思路:寻找target在数组的左右边界。采用两个二分法分别查找左边界和右边界。值得注意的是,题目说升序,但没说无重复元素,
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def getRightBorder(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
rightBoder = -2 # 记录一下rightBorder没有被赋值的情况
while left <= right:
middle = left + (right-left) // 2
if nums[middle] > target:
right = middle - 1
else: # 寻找右边界,nums[middle] == target的时候更新left
rightBoder = middle
left = middle + 1
return rightBoder
def getLeftBorder(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
leftBoder = -2 # 记录一下leftBorder没有被赋值的情况
while left <= right:
middle = left + (right-left) // 2
if nums[middle] >= target: # 寻找左边界,nums[middle] == target的时候更新right,right往左走
leftBoder = middle
right = middle - 1
else:
left = middle + 1
return leftBoder
leftBoder = getLeftBorder(nums, target)
rightBoder = getRightBorder(nums, target)
# 情况一
if leftBoder == -2 or rightBoder == -2: return [-1, -1]
# 情况三
if rightBoder -leftBoder >=0: return [leftBoder, rightBoder]
# 情况二
return [-1, -1]
注意:在寻找右边界时,当nums[middle]==target
时,左边界会往右移动,所以可能会出现,区间收敛到目标区间的右侧一位(因为left = middle+1
)。下一步由于target在左侧,right会更新导致区间长度<0而结束循环。所以在left更新前更新右边界rightBoder = middle
。左边界同理
69. x的平方根
题目表述:给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5。
思路:
在这里插入代码片
367. 有效的完全平方数
题目表述:给你一个正整数 num 。如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
完全平方数 是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。
不能使用任何内置的库函数,如 sqrt 。
思路:
移除元素
题目描述:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
思路:由于数组不能单独删除某个元素,只能覆盖。可以采用快慢指针法,快指针遍历数组,遇到不等于目标值的元素,将其赋值给慢指针,慢指针移动一位;否则快指针一位,但慢指针不移动。
def removeElement(nums, val):
fast, slow = 0, 0
# 遍历数组
while i<len(nums):
# 如果当前元素不等于val,加其赋值到索引j,并向右移动
if num[fast] != val:
num[slow] = num[i]
slow +=1
fast += 1
return slow
相关题目
283.移动零
题目描述:给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
思路:注意要原地操作。采用快慢指针都从索引为0开始往右走,当快指针遇到不为0的元素,则赋值给慢指针。最后将慢指针往后的元素赋零。
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
slow,fast = 0,0
while fast <= len(nums)-1:
if nums[fast] != 0:
nums[slow] = nums[fast]
slow += 1
fast += 1
while slow < len(nums):
nums[slow] = 0
slow += 1
# for i in range(slow, len(nums)):
# nums[i] = 0
# nums[slow:] = [0]*(len(nums)-slow)
return nums
26.删除排序数组中的重复项
题目描述:给你一个 非严格递增排列 的数组 nums ,请你原地删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。
思路:注意要原地操作。采用快慢指针,从索引为1开始往右走,若快指针指向的元素与上一个元素值不相等,则将该元素赋值给slow,并且不重复元素计数+1(注意从1开始计数)。
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
count = 1 # 至少有一个元素是正确的
slow, fast = 1, 1
while fast < len(nums):
if nums[fast] != nums[fast-1]:
nums[slow] = nums[fast]
slow += 1
count += 1
fast += 1
return count
977. 有序数组的平方
题目描述:给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
思路一:暴力解法(用python的sort函数)
class Solution:
def sortedSquares(self, nums):
squared_nums = [x**2 for x in nums]
# 对平方后的数组进行降序排序
squared_nums.sort()
return squared_nums
上面如果采用squared_nums.sort(reverse=True),则倒序
思路二:双指针解法。由于nums是非递减排序的数组,平方以后的最大值肯定出现在两侧,不是左边就是右边(负数的平方为正数)。
class Solution:
def sortedSquares(self, nums):
left, right = 0, len(nums)-1
# 存储结果数组,从数组结尾开始
res = [-1]*len(nums)
site = len(nums)-1
while left <= right: # 相等的时候也能运行,
if nums[left]*nums[left] < nums[right]*nums[right]:
res[site] = nums[right]*nums[right]
right -= 1
else:
res[site] = nums[left]*nums[left]
left += 1
site -= 1
return res
209.长度最小的子数组
题目描述:给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其总和大于等于 target 的长度最小的子数组[numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
思路一:暴力法。
思路二:采用滑动窗口法,实质是用快慢两个指针构造一个区间,通过不断调节子序列起始位置和终止位置,得出我们想要的结果。假设最小长度为inf,快指针先移动,且计算累加值。若累加值大于target,进入循环,移动slow指针以减小区间。注意的是,先在窗口中减去了慢指针的值cel -= nums[slow]
,再移动慢指针slow += 1
。
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
minlen = float('inf') # 初始化为无穷大
cel = 0 # 当前窗口的和
slow, fast = 0, 0 # 初始化两个指针
while fast < len(nums): # 移动 fast 指针扩展窗口
cel += nums[fast] # 将 fast 指针的值加到当前窗口的和上
while cel >= target: # 当前窗口的和大于等于 target 时
minlen = min(minlen, fast - slow + 1) # 更新最小长度
cel -= nums[slow] # 从当前窗口的和中减去 slow 指针的值
slow += 1 # 移动 slow 指针向前,缩小窗口
fast += 1 # 移动 fast 指针继续扩展窗口
# 如果 minlen 仍然是无穷大,说明没有找到满足条件的子数组
return 0 if minlen == float('inf') else minlen
59.螺旋矩阵二
题目描述:给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
思路:首先判断有多少轮(圈),用n//2求取,注意处理n=1的特殊情况。对于每一圈,需要按照四条边填补数据,在python中,x in range(a, b)是取不到右边界b的,所以需要对右边界+1处理。对于n=奇数,还需要天目中间元素。
class Solution:
def generateMatrix(self, n: int) -> List[List[int]]:
count = 1
loop = n//2 # 如果是偶数,则为n/2,如果是奇数,则为(n-1)/2
# 注意,2和1在这里得到的结果都会是1,所以需要做出区分
matrix = [[0 for _ in range(n)] for _ in range(n)]
if n == 1:
matrix = [[1]]
else:
for i in range(loop): # 索引从0开始
for j in range(i,(n-1-i)):
matrix[i][j] = count
count += 1
for j in range(i,(n-1-i)):
matrix[j][n-1-i] = count
count += 1
for j in range((n-1-i),i, -1):
matrix[n-1-i][j] = count
count += 1
for j in range(n-1-i,i,-1):
matrix[j][i] = count
count += 1
if n%2 != 0: # 说明是奇数
matrix[n//2][n//2] = n**2
return matrix
58. 区间和
题目描述:给定一个整数数组 Array,请计算该数组在每个指定区间内元素的总和。
输入描述:第一行输入为整数数组 Array 的长度 n,接下来 n 行,每行一个整数,表示数组的元素。随后的输入为需要计算总和的区间,直至文件结束。
思路:采用前缀和的方式,创建一个数组cel,其中第i个位置用来记录前i个元素的累积和。要求某段区间[a,b]的和,只需要求cel[b]-cel[a-1],注意a==0的情况。
import sys
input=sys.stdin.read
data = input().split()
index = 0
n = int(data[index])# 行数
index += 1
vec = []
for i in range(n):
vec.append(int(data[index+i]))
cel = [0]*n
cel_num = 0
for i in range(n):
cel_num += vec[i]
cel[i] = cel_num
results = []
index += n
while index < len(data):
a = int(data[index])
b = int(data[index+1])
index += 2
if a == 0:
sum = cel[b]
else:
sum = cel[b]-cel[a-1]
results.append(sum)
for result in results:
print(result)
44. 开发商购买土地
题目描述:在一个城市区域内,被划分成了n * m个连续的区块,每个区块都拥有不同的权值,代表着其土地价值。目前,有两家开发公司,A 公司和 B 公司,希望购买这个城市区域的土地。现在,需要将这个城市区域的所有区块分配给 A 公司和 B 公司。然而,由于城市规划的限制,只允许将区域按横向或纵向划分成两个子区域,而且每个子区域都必须包含一个或多个区块。为了确保公平竞争,你需要找到一种分配方式,使得 A 公司和 B 公司各自的子区域内的土地总价值之差最小。
注意:区块不可再分。
思路:采用前缀和的方式。首先记录每个输入元素并构成数组,然后分别按行和按列求出行累加和以及列累加和。然后再分行和列找到使两个子块相差最小的分割方式。
import sys
input = sys.stdin.read
data = input().split()
index = 0
n = int(data[index])
index += 1
m = int(data[index])
sum = 0
vec = []
index += 1
for i in range(n):
row = []
for j in range(m):
num = int(data[index])
index += 1
row.append(num)
sum += num # 用来求整个矩阵所有元素之和
vec.append(row)
# 统计横向
h = [0]*n
for i in range(n):
for j in range(m):
h[i] += vec[i][j]
# 统计纵向
v = [0]*m
for j in range(m):
for i in range(n):
v[j] += vec[i][j]
result = float('inf')
# 看看是否要横向切割
hCut = 0
for i in range(n):
hCut += h[i]
result = min(result,abs((sum-hCut)-hCut))
# 纵向切割
vCut = 0
for i in range(m):
vCut += v[i]
result = min(result,abs((sum-vCut)-vCut))
print(result)
# 统计纵向
参考资料
[1] https://programmercarl.com/%E6%95%B0%E7%BB%84%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html