一、数组的基本知识
1.一维数组是存放在连续内存空间上的相同类型数据的集合。数组可以方便的通过下标索引的方式获取到下标下对应的数据。(注意:数组索引下标都是从0开始的)
2. 数组的元素是不能删的,只能覆盖。因为数组存放在连续内存空间,因此在删除或者增添元素的时候,就难免要移动其他元素的地址,所以删除数组中某个元素其实是将它后面的元素全部向前移动,覆盖掉该元素。
3. 二维数组在C++中是连续内存,但在Java中是无序的。
二、数组的常见算法类型
1.二分查找(5.24)
(1)使用前提
数组有序
元素无重复
(2)注意细节
是while left<right 还是while left<=right
是right=mid还是right=mid-1
如果定义 target 是在一个在左闭右闭的区间里,也就是[left, right],则right=mid-1,(因为mid位置已经判断过了),这种情况下while left<=right,因为left==right是有意义的。此时,right=len(nums)-1。
如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right),此时终止判断条件应为while left<right,更新时也应为right=mid,因为right不在下一次搜索范围内。此时,right=len(nums)。
这两种写法中,left始终等于mid+1。
注意:除非是mid还需要再被判断,就让r=mid(例如153),否则,更换边界都是l=mid+1,r=mid-1
(3)相应题目:
最基本的二分法题型
4. 寻找两个正序数组的中位数 - 力扣(LeetCode)
二分法,依次比较两个数组第k/2位元素,较小的数组删去前k/2部分再继续比较。相当于left变成了nums1[k/2],right变成了nums2[k/2],比较left与right。只不过因为
终止条件为k==1时,输出min(nums1[0],nums2[0]),同时要防止外溢,因此每次迭代时的k应为min(len(nums2),k/2),并每次递归前判断以保证nums2为长度较短的那一个数组。
考虑存在奇数和偶数两种情况,偶数需要求第len//2与(len+1)//2的和的平均值
虽然该数组不是完全有序,但一分为二以后总有一部分是有序的,先判断哪部分有序,在有序部分判断左右边界应该如何变化。
81. 搜索旋转排序数组 II - 力扣(LeetCode)
在33的基础上,由于数组中有重复元素,因此多了一个判断nums[l]和nums[r]和nums[mid]间的大小关系,l+1或者r-1以去除重复元素后再去判断哪部分是有序区间。
最基础的二分法问题,不过是加了“如果不存在target,返回应该插入的位置”这一问题,由于左右指针始终将target包含在区间内,当最后到达终止条件时仍未找到target,说明不存在,那么插入位置就应该是left。
34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
相当于两个二分操作分别寻找target对应的最早、最迟出现的位置。
优化时可以只保留寻找左边界的函数,通过寻找target和target+1最早出现的位置,右边界即为target+1出现位置-1的地方。
注意存在三种情况,即target在数组范围之外,在数组范围之内但不存在,存在于数组中。对于情况一容易判断:
if not nums or nums[0]>target or nums[-1]<target:
return [-1,-1]
情况二就需要最后判断一下nums[left]是否等于targe或者l是否已经超出列表范围即nums[l]==len(nums)。
右边界即为x,判断条件是mid的平方是否小于等于x,并维护一个变量保存当前mid(之所以小于和等于放到一起是因为有的数没有刚好相等的只能选择平方和最接近它的)。
153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)
将nums[mid]与区间的右边界元素比较,若当前最小值在mid左侧,则nums[mid]一定小于nums[r](因为数组原来是单调递增的),因此r=mid;否则,nums[mid]>nums[r],即最小值在mid右侧,l=mid+1。这里要注意的是,不论判断条件用的是while left<right,还是while left<=right,都要r=mid,因为判断条件只是判断了最小值可能存在的区间,并没有判断mid是否就是最小值,mid可能就是最小值,所以需要在下一个区间中包含它继续判断。
154. 寻找旋转排序数组中的最小值 II - 力扣(LeetCode)
和153类似,只是由于多了重复元素,因此多加了一个判断nums[mid]=nums[r],此时让r-1即可。因为无论nums[r]是否为最小值,nums[mid]中已经包含了这个数,所以可以放弃nums[r]。和上一题一样,最后返回nums[mid]
根据峰值的定义,假设从左遍历数组,如果出现当前元素大于前一个,说明该处可能是峰值,则继续向右搜索,否则停止。因此,可以用二分法,只需要比较nums[mid]和nums[mid+1]的大小,如果mid大,说明峰值不会在右边,因为mid就挡住了,r=mid;否则,继续向右寻找,l=mid+1。
注意的是,这里由于mid每次都要和mid+1比较,left==right的时候也就没办法比较了,因此这里选择while left<right
数组是[1,n]之间的数字组成,但有重复数。数组当作一个链表来看,数组的下标就是指向元素的指针,把数组的元素也看作指针。如 0 是指针,指向 nums[0]
,而 nums[0]
也是指针,指向 nums[nums[0]]
.
首先明确前提,整数的数组 nums 中的数字范围是 [1,n]。考虑一下两种情况:
如果数组中没有重复的数,以数组 [1,3,4,2]为例,我们将数组下标 n 和数 nums[n] 建立一个映射关系 f(n),其映射关系 n->f(n)为:
0->1
1->3
2->4
3->2
我们从下标为 0 出发,根据 f(n) 计算出一个值,以这个值为新的下标,再用这个函数计算,以此类推,直到下标超界。这样可以产生一个类似链表一样的序列。
0->1->3->2->4->null
如果数组中有重复的数,以数组 [1,3,4,2,2] 为例,我们将数组下标 n 和数 nums[n] 建立一个映射关系 f(n),其映射关系 n->f(n) 为:
0->1
1->3
2->4
3->2
4->2
同样的,我们从下标为 0 出发,根据 f(n) 计算出一个值,以这个值为新的下标,再用这个函数计算,以此类推产生一个类似链表一样的序列。
0->1->3->2->4->2->4->2->……
从理论上讲,数组中如果有重复的数,那么就会产生多对一的映射,这样,形成的链表就一定会有环路了,
综上
1.数组中有一个重复的整数 <==> 链表中存在环
2.找到数组中的重复整数 <==> 找到链表的环入口
167. 两数之和 II - 输入有序数组 - 力扣(LeetCode)
左右指针所指向元素进行求和,与目标和比较大小,从而进行指针的移动
数字在升序数组中出现的次数_牛客题霸_牛客网 (nowcoder.com)
和上一题类似,这里用的是左右指针,而不是二分法