数组是存放在连续内存空间的具有相同类型的数据的集合
704. 二分查找
**题目:**给定一个 n
个元素有序的(升序)整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1
。
在做二分查找的时候,起初并没有深入想到太多注意事项,直接使用了二分法的思想进行搜索,代码实现如下(当时还用了ceil这个奇怪的单词)
class Solution {
public int search(int[] nums, int target) {
int ceil = nums.length - 1;
int floor = 0;
int position = -1;
int mid = 0 ;
boolean loopFlag = true;
while(loopFlag){
mid = (ceil + floor) /2;
if(nums[mid] == target){
position = mid;
loopFlag = false;
}
else if(nums[mid] < target){
floor = mid;
}else if(nums[mid] > target){
ceil = mid;
}
if(mid == (floor + ceil) / 2){
loopFlag = false;
}
}
if(position == -1){
return -1;
}else{
return position;
}
}
}
仔细观察所写的代码,发现了一些问题:
- ceil + floor两个整型变量相加可能会存在内存溢出的问题
- 程序陷入无限循环(应该也是边界问题)
- 边界问题没有考虑到
重新思考这个问题,将边界、内存等因素考虑进去,便可以得到较为完善的二分法
左闭右闭 二分法
四个注意点:
- [left, right],这个右边界是有意义的,为避免数组下标越界异常,right = nums.length - 1
- 同样在闭区间的定义下,在while循环中,判断条件应为**(left <= right)**,因为 left = right 是有意义的
- 如果nums[middle] > target,更新搜索范围右下标应更新为 right = middle - 1,因为middle位置上的数值一定不等于target,同理,更新left时,应更新为left = middle + 1
- 考虑到内存溢出的问题 定义middle为left + (right - left)/2
代码实现如下
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int middle = 0;
while (left <= right) {
middle = left + (right - left) / 2;
if (nums[middle] == target) {
return middle;
} else if (nums[middle] < target) {
left = middle + 1;
} else if (nums[middle] > target) {
right = middle - 1;
}
}
return -1;
}
}
左闭右开 二分法
四个注意点
- [left, right),这个右边界是无意义的,**right = nums.length **
- 同样在闭区间的定义下,在while循环中,判断条件应为**(left < right)**,因为 left = right 是无意义的
- 如果nums[middle] > target,更新搜索范围右下标应更新为 right = middle ,但应注意,更新left时,应更新为left = middle + 1(似乎闭区间就要加1或减1)
- 考虑到内存溢出的问题 定义middle为left + (right - left)/2
代码实现如下:
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length;
int middle = 0;
while (left < right) {
middle = left + (right - left) / 2;
if (nums[middle] == target) {
return middle;
} else if (nums[middle] < target) {
left = middle + 1;
} else if (nums[middle] > target) {
right = middle;
}
}
return -1;
}
}
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
end_point = 0
start_point = 0
target_tag = 0
for i in range(len(nums)):
end_point = end_point + 1
end_point = end_point - 1
loop_flag = True
while loop_flag:
target_tag = int((end_point + start_point) / 2)
if nums[target_tag] < target:
start_point = target_tag + 1
if nums[target_tag] > target:
end_point = target_tag - 1
if nums[target_tag] == target:
return target_tag
if start_point > end_point:
return -1
总结:
- 两个整型相加注意内存溢出问题
- 注意边界问题的处理
27.移除数组中的元素
题目:给你一个数组 nums
和一个值 val
,你需要原地数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
开始想的时候想法比较简单,想到了使用循环对数组中元素进行覆盖,使用暴力方式解决,但是在具体的代码实现过程中,做法就真的暴力了。在使用暴力实现的过程中,遇到了几个问题,括号里是一些不成熟的做法:
移除数组中的元素,刚开始想的时候想法比较简单,想到了使用循环对数组中元素进行覆盖,使用暴力方式解决,但是在具体的代码实现过程中,做法就真的暴力了。在使用暴力实现的过程中,遇到了几个问题,括号里是一些不成熟的做法:
- 最后一个元素如果等于目标值的话,会陷入无限循环中(如果这种情况就把最后一个元素赋值为-1)。
- 如果数组为空的话,会发生数组下标越界异常(如果这种情况就直接返回0)
- 在覆盖之后数组下标就移动到下一位了,不能检测移动过来的数组元素是不是也等于目标值(把for循环中的控制语句放到了选择结构中)
于是暴力解法代码实现如下
public int removeElement(int[] nums, int val) {
int num = 0;
if(nums.length == 0){//上文第二种情况
return 0;
}else{
if (nums[nums.length - 1] == val){//上文第一种情况
nums[nums.length - 1] = -1;
num--;
}
for(int i = 0;i<nums.length;){
if(nums[i] == val){
for(int j = i;j<nums.length - 1;j++){
nums[j] = nums[j+1];
}
num--;
}else {//上文第三种情况
i++;
}
}
return nums.length + num;
}
}
class Solution(object):
def removeElement(self, nums, val):
"""
:type nums: List[int]
:type val: int
:rtype: int
"""
start_point = 0
end_point = 0
loop_flag = True
while loop_flag:
if start_point == len(nums):
loop_flag = False
break
if nums[start_point] != val:
start_point = start_point + 1
continue
else:
end_point = start_point + 1
while end_point < len(nums) and nums[end_point] != None:
nums[start_point] = nums[end_point]
start_point += 1
end_point = start_point + 1
del nums[start_point]
start_point = 0
return len(nums)
之后重新学习了一下暴力解法, 得到了一些新想法:
- 把数组的长度赋值给一个变量, 能够解决无限循环的问题和数组下标越界的问题(覆盖之后, 这个变量数值减小)(如果数组为空,循环很快就能退出来)
- 循环结构中的控制语句每覆盖一次数值-1,就能保证在覆盖这个位置在一次检测
优化后的暴力解法如下
public int removeElement(int[] nums, int val) {
int num = nums.length;
for(int i = 0; i < num; i++){
if(nums[i] == val){
for(int j = i;j<nums.length - 1;j++){
nums[j] = nums[j+1];
}
num--;
i--;
}
}
return num;
暴力解法时间复杂度 O ( n 2 ) , 空间复杂度为 O ( 1 ) 暴力解法时间复杂度O(n^2),空间复杂度为O(1) 暴力解法时间复杂度O(n2),空间复杂度为O(1)
下面是使用双指针思想解决移除数组中元素问题
思路:分别定义一个快指针和慢指针,快指针用于获得数组中所需要的元素(就是数组中不等于目标值的元素);慢指针用于接收数组中所需要的元素值,并将其赋值给所指向的数组位置。如果快指针遇到了等于目标值的数组元素,慢指针停下,快指针继续向前走,直到遇见不等于目标值的元素,把这个元素的值传递给慢指针,慢指针赋值给相应的位置,实现覆盖。循环结束之后,慢指针所在的位置就是所要求返回的数组长度。
代码实现:
public int removeElement(int[] nums, int val){
int fastIndex = 0;
int slowIndex = 0;
for (fastIndex = 0; fastIndex < nums.length; fastIndex++) {
if (nums[fastIndex] != val){//如果发现等与目标值的数组元素,慢指针停下,快指针继续向前走
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
双指针思想移除数组中元素时间复杂度 O ( n ) , 空间复杂度 O ( 1 ) 双指针思想移除数组中元素时间复杂度O(n),空间复杂度O(1) 双指针思想移除数组中元素时间复杂度O(n),空间复杂度O(1)