数组基础知识心得
- 数组在计算机当中是一片连续的存储空间,也就是说假如数组0号元素的地址是a的话,那么后面数组元素的地址=a+数组下标*元素字节数。
- 数组的起始下标是0,终止下标是数组长度-1,这与我们“第一个元素、第二个元素……第n个元素”的直觉不一样。遍历数组的时候可以使用
for(int i=0;i<array.length;i++){}
,最普遍。 - 数组的大小是初始化之后就不能变的,在Java编程中如果不明确需要的数组大小,就可以用ArrayList。
- 数组的查询非常迅速,时间复杂度是O(1);插入和删除操作相对较慢,时间复杂度是O(n)。
算法题目
二分查找
704、二分查找
这个是二分查找梦开始的地方了。回想了一下二分查找的思路大致就是,数组必须是排好序的。用数组正中间的元素去和目标元素比大小,如果前者比后者大,就到前者左边那一半数组去找,否则到右边那一半去找。然后在新的一半数组中继续这个过程。大致流程应该是这样:
二分查找需要注意的主要就是边界的问题,左闭右闭还是左闭右开。我选择左闭右闭,看着舒服。
其中在计算mid的时候,还是要注意会不会溢出,这跟题目描述的数组元素的大小范围有关系。最好就是固定用a+(b-a)/2代替(a+b)/2,计算a+b的时候超出整型范围。
public int search(int[] nums, int target) {
int low = 0, high = nums.length - 1;
while (low <= high) {
int mid = ((high - low) >> 1) + low;
if (target < nums[mid]) {
high = mid - 1;
} else if (target > nums[mid]) {
low = mid + 1;
} else {
return mid;
}
}
return -1;
}
思维是很好理解的。但是写代码的时候细节需要把握好,就是在于循环终止的判断条件和边界的更新。
二分查找的时间复杂度是O(logn),空间复杂度是O(1),相当高效!
移除元素
27、移除元素
在看到这个题目的时候,直觉就告诉我暴力解题肯定报用,如果放在企业面试在线笔试之类的,肯定会给很长的数组,导致超出时间限制。
暴力法就是用一个for循环遍历数组,在碰到需要删除的元素时,就再用一个for循环将其后面所有的元素挨个向前挪一位。这个算法的时间复杂度将时O(n^2)。
双指针法最先想到的,是用一个指针从前面遍历数组,用另一个指针指向数组的尾巴,当碰到需要删除的元素的时候,把后面的指针的元素填到这个位置来(因为最终的数组不要求顺序)。但是后面指针所指的元素,要考虑会不会恰好就等于需要删除的元素,如果是的话就需要进行处理。想法都很简单明了,但是写起来却是另一回事。
//前后指针
public int removeElement(int[] nums, int val) {
int len = nums.length;
for (int i = 0; i < len; i++) {
if (nums[i] == val) {
while (nums[len - 1] == val) {
if (len - 1 == i) return len - 1;
len--;
}
nums[i] = nums[len - 1];
len--;
}
}
return len;
}
在调出这段代码之后,我不禁沉思,难道代码非要这么复杂么。仔细思考了一下,看了一下代码随想录提供的思路,原来两个指针都从前面开始遍历也是可以的,这就是快慢指针法。
经过反复思考,反复调试,终于写出以下代码,竟然和卡哥给出的代码一模一样!
快指针正常遍历数组,当碰到的是要删除的元素的时候,慢指针就留在原地,表示这个位置的元素已经删掉了,当快指针再往后遍历到非删除元素的时候,就将快指针的元素补到慢指针的位置。
看似明了的想法,如果不是反复摸索,真的难以想到!
public int removeElement(int[] nums, int val) {
int j = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != val) {
nums[j++] = nums[i];
}
}
return j;
}
这就是典型的,思维上面很精妙,代码表现出来很简单简洁的算法,希望以后都能写出这种代码。
小结
第一天就上了这么两盘大菜,真的学渣哭泣。但是本学渣相信,只要脚踏实地地,用自己的双手去敲够足够的代码,那学渣也能逐渐成为,能解决别的学渣解决不了的问题的学渣!