数组是存放在连续内存空间上相同类型数据的集合。
数组可以使用下标索引实现对目标元素的访问。如下图所示:
需要注意的是:
正因为其在内存空间存放的连续性,所以我们在删除或增添数组的元素时,就难免的要大量移动其他元素的地址,虽然我们看不出来,但在底层中,确实是这样,举个例子:
删除下标为3的元素,需要对下标为3的元素后面的所有元素都要做移动操作,如图所示:
而且大家如果使用C++的话,要注意vector 和 array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。
数组的元素是不能删的,只能覆盖。
接下来看看二维数组,如下图所示:
简单的理解,二维数组在内存中采用优先顺序进行存储,即先存储数组的第一行,再依次存储其他行。
当列表为升序不重复时,推荐使用二分查找。
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
① 首先确定整个查找区间的中间位置 mid = (left + right) /2 。
② 用待查关键字值与中间位置的关键字值进行比较;若相等,则查找成功;若大于,则在后(右)半个区域继续进行折半查找;若小于,则在前(左)半个区域继续进行折半查找。
③ 对确定的缩小区域再按折半公式,重复上述步骤。最后,得到结果:要么查找成功, 要么查找失败。
第一种写法,我们定义 target 是在一个在左闭右闭的区间里,也就是 [left, right] (这个很重要非常重要)。
区间的定义这就决定了折半法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:
下面给出具体实现代码:
nums = [1,2,3,4,5,6,7,8,9,10,11,22,33,44,65,546,5432]
target = int(input("请输入目标数字"))
left = 0
right = len(nums) - 1
while (left <= right):
middle = (left + right ) // 2
if (nums[middle] > target):
right = middle - 1
elif (nums[middle] < target):
left = middle + 1
elif(nums[middle] == target):
print(middle)
break // 很重要,否则陷入无限循环
else:
print(-1) #未找到该元素
这种方法将 target 定义在一个在左闭右开的区间里,也就是[left, right)。需要注意,此时右边界是取不到的。需要注意下面两点:
下面给出具体实现代码:
nums = [1,2,3,4,5,6,7,8,9,10,11,22,33,44,65,546,5432]
target = int(input("请输入目标数字"))
left = 0
right = len(nums)
while left < right:
middle = (left+right) // 2
num = nums[middle]
if num < target:
left = middle + 1 #因为左侧是闭的,所以nums[left] 已经在上一轮被取到,所以下一轮循环需要将左侧标志位+1.
elif num > target:
right = middle
else:
print(middle)
break // 很重要,否则陷入无限循环
else:
print(-1) #未找到该元素
区间的定义就是不变量,那么在循环中坚持根据查找区间的定义来做边界处理,就是循环不变量规则。折半查找中最重要的就是区间的选择和边界的确定。记住一点。考察区间是否存在,是否有实际意义,这样在写代码的时候就不会模糊。
双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
本题要求不能新开辟空间,只能在原数组里进行修改,思路是定义两个指针指向数组的头部,快指针Fast 负责迅速遍历数组,慢指针Slow 负责将数组中与val不等的值原地覆盖,Slow每赋值一次就要加一,指向下一位置。
下面给出具体实现代码:
nums = [1,2,2,2,3,4,5,2,2]
val = int(input("请输入目标值:"))
Fast,Slow = 0,0
for Fast in range(len(nums)):
if nums[Fast] != val:
nums[Slow] = nums[Fast]
Slow += 1
print(Slow)
print(nums)
#Output
#请输入目标值:2
#4
#[1, 3, 4, 5, 3, 4, 5, 2, 2]
双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组和链表操作的面试题,都使用双指针法.它独特的结构使得数组不用开辟新的空间,直接在原数组上对数据进行修改,极大节省了内存,提高了运行效率。
滑动窗口法,我更愿意称之为动态队列法。其思路就是:不断维护一个动态的队列,满足条件后,通过删除队头元素的方式不断调整子队列的起始位置和终止位置,从而得到我们想要的结果。
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
滑动窗口演示
窗口是满足其和 ≥ s 的长度最小的连续子数组。
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。(滑动窗口法使用的前提是该问题可以使用双循环暴力求解)
下面给出具体实现代码:
'''方法一:双循环暴力求解 ,无技巧~
nums = [2,3,1,2,4,3]
target = 7
n = len(nums)
sum = 0
min_lenth = [0] * n
res = []
for i in range(n):
count = 1
sum = nums[i]
if sum >= target:
min_lenth[i] = count
break
else:
for j in range(i+1,n):
sum += nums[j]
count += 1
if sum >= target:
min_lenth[i] = count
break
for i in range(len(min_lenth)):
if int(min_lenth[i]) > 0:
res.append(min_lenth[i])
if len(res) != 0:
print(min(res))
else:print(0)
'''
'''方法二:滑动窗口,只一次循环
nums = [2,3,1,2,4,3]
target = 7
n = len(nums)
res = float("inf") # 定义一个无限大的数
print(res)
sum = 0
j = 0
for i in range(n):
sum += nums[i]
while(sum >= target):
temp = i - j + 1
res = min(temp,res)
sum = sum - nums[j] #滑动窗口实现的关键:j为每次初始的值,当条件满足时,类似队列的结构,将队列的头元素删除,进行下一步判断。
j += 1
print(0) if res == float("inf") else print(res)
'''
模拟类的题目在数组中很常见,不涉及到什么算法,就是单纯的模拟,主要考察对代码的掌控能力。
主要要用到的是循环不变量原则,换句话说,就是在每一次子循环的处理中, 处理的子序列的长度和开闭区间的选择上都应该与前面保持一致。
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
求解本题依然是要坚持循环不变量原则。
模拟顺时针画矩阵的过程:
n = 3
res = [[0] * n for i in range(n)]
start_x, start_y = 0, 0
loop = n // 2
mid_point = n // 2 #n为奇数时,矩阵的中心点坐标值(中心的点坐标值横纵一样 )
count = 1
offset = 1 #每行的最后一个元素算入下一轮循环的首元素,且每循环完一整轮,值加一。
while loop :
for j in range(start_y,n - offset):
res[start_x][j] = count
count += 1
for i in range(start_x,n - offset):
res[i][n - offset] = count
count +=1
for j in range(n - offset,start_y,-1):
res[n - offset][j] = count
count += 1
for i in range(n- offset,start_x,-1):
res[i][start_y] = count
count +=1
start_x += 1
start_y += 1
offset += 1
loop -= 1
if n % 2 != 0:
res[mid_point][mid_point] = count
print(res)
#Outout
# [[1, 2, 3], [8, 9, 4], [7, 6, 5]]