数组是什么?
数组是存放在连续内存空间上的相同类型数据的集合。
因为数组的连续存储使得我们在有序的情况下可以快速的查找自己需要的数据,但是同时在数组中间部分插入或者删除数据会变得非常的麻烦。
704. 二分查找
刚看到这题的时候,首先脑海里想到的肯定是既然是升序数组,那么就可以直接遍历一遍然后通过比较大小,一旦发现于自己的相等的数就立即返回下标。
比较粗糙的代码:
class Solution {
public int search(int[] nums, int target) {
//暴力搜索
for (int i = 0; i < nums.length; i++) {
if(nums[i] == target){
return i;
}else if(nums[i] > target){
return -1;
}
}
return -1;
}
}
当数组里的数和目标值一致时可直接返回,若数组当前下标的数已经比目标数大,又因为数组是连续的,那么数组里面就不存在对应的数据,可以直接返回-1。如果碰巧这个数在最末尾会使得我们遍历整个数组。所以我们需要使用二分法查找目标数。
二分法粗糙的实现
二分法首先需要取得有序数组的中间值,然后用中间值与目标值作比较,把目标值所在区间作为下一个二分的区间。
class Solution {
public int search(int[] nums, int target) {
//二分法
if(nums.length ==1){
if(nums[0]==target){
return 0;
}else {
return -1;
}
}
return searchTarget(0, nums.length - 1, nums, target);
}
int searchTarget(int left, int right, int[] nums, int target) {
if (left >= right) {
return -1;
}
int mid = (right + left) / 2;
if (nums[mid] == target) {
return mid;
}else if(left + 1 == right){
if(nums[right]==target){
return right;
}
left++;
return searchTarget(left, right, nums, target);
} else if (nums[mid] > target) {
right = mid;
return searchTarget(left, right, nums, target);
} else if (nums[mid] < target) {
left = mid;
return searchTarget(left, right, nums, target);
}
return -1;
}
}
上面的代码没能深入思考,使用了问题很大的结构实现了所谓的二分,一旦出现了问题就特殊处理,这使得代码非常臃肿。最大的问题在于,直接进行粗暴的二分时,left与right相差1,则会陷入死循环,为了避免这种情况对这种情况做了特殊处理。
教程观后思考
首先还是感叹代码的简洁美。我觉得上面所示代码的核心问题在于没有考虑老师提到的合法性的问题。因为包入了不属于这个区间的值,才会使得判断卡在连续的两个值之间(更严重的问题其实是我没有考虑到左闭右闭或者左闭右开的条件限制,这会使得题目变化后的特殊判断更加臃肿)。当涉及左右是否闭合时,需要对左右比较的代码进行修改,因为一旦有一侧为开,不管另一侧为开还是闭,都会必然引起两边不等(取不到)同时确定与中间值比较完以后,新的区间范围是否包含中间值。
修改后代码
class Solution {
public int search(int[] nums, int target) {
int l = 0;
int r = nums.length - 1;
//需要注意防止超过int的范围值
int mid = (r-l)/2+l;
//二分法
while(l<=r){
if(nums[mid]>target){
r = mid -1;
} else if(nums[mid]<target){
l = mid + 1;
}else{
return mid;
}
mid = (r-l)/2+l;
}
return -1;
}
}
27.移除元素
暴力破解
因为数组是连续的,所以当其中一个数字需要移除的时候,我们要把完整的数组从移除的这一位全部向前移动。
class Solution {
public int removeElement(int[] nums, int val) {
//暴力破解
int size = nums.length;
for (int i = 0; i < size; i++) {
if (nums[i] == val) {
for (int j = i + 1; j < size; j++) {
nums[j - 1] = nums[j];
}
i--;
size--;
}
}
return size;
}
}
即使暴力破解我也遇到了一些问题,一开始循环嵌套我把两个变量的结束条件写成了固定数组nums的长度,结果无限循环,让我很诧异。怎么使得整个方法更加简单,我没能想到更好的办法。
教程观后思考
本质上快慢指针对原数组进行更新就是利用快指针一定在慢指针前面的特点,让慢指针去对原数组进行更新。快指针走到头的时候,慢指针所在的位置就是新数组的重点。这种感觉就有点像是用正字计票数一样,快指针报票,慢指针就是在记录票数(感觉不是很好的比喻,还是代码比较清晰)
修改后代码
class Solution {
public int removeElement(int[] nums, int val) {
//快慢指针
int slow =0;
for (int fast = 0; fast < nums.length; fast++) {
if(nums[fast]!=val){
nums[slow]=nums[fast];
slow++;
}
}
return slow;
}
}
逻辑很容易理解,在思考的时候很难往这方面想,但是做出来以后又觉得很简单易懂。这种朴素又高效的方式让我感觉很佩服。