今天一共学习三道数组习题,分别是LeetCode704.二分查找,LeetCode27.移除元素,LeetCode977.有序数组的平方。
704.二分查找

梦开始的地方,我感觉每一个讲数据结构和算法的课程,第一个算法题都是这道二分查找。这道题单看题目,本是不难,可以利用遍历来找到是否存在,以及存在时的数组下标,但是遍历较为耗时,LeetCode会显示超时,所以我们需要像一个更高效的方法来完成查找。
在高中的数学课上其实就学过二分法求方程的根的范围,这道题就是利用类似的思想。题目规定数组是有序的,我们从数组的中间元素开始比较,如果目标值比中间元素大,就在后半段重复操作,反之则在前半段重复。
public static int search(int[] nums,int target){
int left=0;
int right=nums.length-1;
while(left<=right){
int mid=(left+right)>>1;
if(nums[mid]<target){
left=mid+1;
}else if(target<nums[mid]){
right=mid-1;
}else {
return mid;
}
}
return -1;
}
首先,在这个方法中,我们先定义查找的范围,刚开始是从数组下标0位置开始到数组最后一个元素结束。根据二分查找的思想,该范围在后续要被不断更新。在这个方法中,一般有一点需要注意。
循环条件
具体来说是循环条件中是否可以取到等号,我们知道,无论范围怎么更新,都要满足左边界小于右边界,但是这里左边界是否可以等于有边界呢?
这里要通过数组的下标取值来思考判断,这里题目没有明确说明,但是我们可以通过题目和示例分析得到,数组范围是一个左右全闭的闭区间。那么左边界=右边界这种情况就可以满足,因为数组范围左右全闭,比如[3,3],那么3就是可以取到的。反之则不行,比如[3,3),左边可以取到3,但右边不能取3,所以3取不到,所以左右不能相等。
27.移除元素

第二道题是移除元素,我在自己做这道题时,选择的是使用集合进行存储,最后在存回数组中,较为复杂和冗余,我在自己写的时候,就感觉解得很难受,隐隐约约好像有一种思路,但是又抓不住。讲解中使用了双指针法,以前听过这种方法,但是一般都可以通过冗余的遍历解决,所以一直没有了解过。听完讲解,恍然大悟,原来我隐隐约约能想到类似的思路,原来双指针就是这样的,又一次觉得算法的神奇和有趣。
回归正题,所谓双指针,就是指在遍历对象的过程中,使用两个指针进行遍历,从而达到相应的目的。正常来说我们可以通过两层循环来完成这个题目,第一层循环来遍历数组,第二层循环用来把需要移除的元素的后续元素都向前移动一格,来达到删除目的。而使用双指针,就可以用一层循环来完成两层循环的任务。
public static int ipRemoveElement(int[] nums,int val){
int k=0;
for (int i = 0; i < nums.length; i++) {
if (nums[i]!=val){
nums[k]=nums[i];
k++;
}
}
return k;
}
双指针的作用是将两个for循环用一次for循环来完成,用原来的nums数组即作为旧数组遍历,也作为新数组存储,i为快指针,遍历旧数组nums,k为慢指针,指向新数组的下标,用来覆盖旧数组的元素,起到存储的作用。
碰到和目标值不同的元素,则存入新数组下标j指向的位置,j向后移动一次,
碰到和目标值相同的元素,则不存入数组,如此必有i>=j,所以称为快慢指针,慢指针覆盖旧数组成为新的数组,一定在i遍历完的范围内,不会影响到旧数组的遍历。
最后,我自己的解法也放在这里,留作参考。
public static int removeElement(int[] nums,int val){
ArrayList<Integer> list=new ArrayList<>();
int k=0;
for (int i = 0; i < nums.length; i++) {
if (nums[i]!=val){
list.add(nums[i]);
k++;
}
}
for (int i = 0; i <k; i++) {
nums[i]= list.get(i);
}
return k;
}
977.有序数组的平方

这道题第一眼看,非常简单,把数组中的每一个元素都平方即可,但是稍微仔细思考,再看一眼示例,就大概能明白难点:负数的平方也是正数,会改变顺序。
那么最简单的方法是,先平方,再排序,简单易懂。
public static int[] sortedSquares(int[] nums){
for (int i = 0; i < nums.length; i++) {
nums[i]=nums[i]*nums[i];
}
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < nums.length-1; j++) {
if (nums[j]>nums[j+1]){
int temp=nums[j];
nums[j]=nums[j+1];
nums[j+1]=temp;
}
}
}
return nums;
}
这里的代码时先平方存入新数组,然后我使用冒泡排序对新数组进行排序,也可以使用快排。
但是下方又进阶问题,该问题也可以使用双指针法。双指针法一般有快慢指针和对撞指针,** 27.移除元素** 使用了快慢指针,这道题可以使用对撞指针。对撞指针就是从两个相反方向进行遍历。这道题通过观察和思考,可以发现,平方后越小的值由越靠原数组中间位置的元素得到,越大的元素越靠两边。所以我们可以从数组头尾分别出发进行遍历,注意,这样遍历平方先得到较大的元素,所以可以存入新数组的尾部,向头部移动。
比较两个指针指向位置的元素平方后的大小,先存入较大的,然后指针向自己方向的下一位移动。这里注意,等于和大于小于中的任意一个都可以合并。如果相等,则会将两个元素全部存入,但合并之后,可以理解为,先存入一个元素,剩下一个相等的元素会留到下一轮循环存入。
public static int[] ipSortedSquares(int[] nums){
int i=0;
int j=nums.length-1;
int[] result=new int[nums.length];
int k=nums.length-1;
while(i<=j){
int a=nums[i]*nums[i];
int b=nums[j]*nums[j];
if (a>b){
result[k]=a;
k--;
i++;
} else {
result[k]=b;
k--;
j--;
}
}
return result;
}
2327

被折叠的 条评论
为什么被折叠?



