代码随想录算法训练营第一天|704.二分查找、27.移除元素
一. 数组理论基础
看代码随想录整理
- 数组在内存中的存储方式
java中是不连续的, 另外 java中内存是没有暴露的,寻址操作由JVM完成
- 数组中的元素能否删除?
只能覆盖
二. 数组相关算法题练习
704.二分查找
这道题短断断续续刷了n遍了,每次重新做都是搞不清边界处理,今天重新认真看了下代码随想录的题解,整理了以下两个痛点希望以后能刻在脑子里
- 题目中出现有序且没有重复元素时就可以考虑是不是能用二分法了
- 因为边界比较难确认,要时刻遵循“循环不变量原则”(具体的实现可以看下面代码注释)
左闭右闭
- 时间复杂度: O(logn)
- 空间复杂度: O(1)
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length-1;
int reult = -1;
//左闭又闭
while (left<=right){ //因为是左闭右闭所以当左右相等是也要进入循环判断
int mid = (right-left)/2+left;
if (nums[mid]>target){
right = mid-1; //注1
}else if (nums[mid]<target){
left = mid+1; //注2 - 注1与注二两个地方可以理解为左右边界在区间内被判断如果没有命中应移到下一节点
}else {
return mid;
}
}
return reult;
}
}
左闭右开,理解了循环不变量原则的区间控制左闭右开的思路就迎刃而解了
- 时间复杂度: O(logn)
- 空间复杂度: O(1)
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length-1;
int reult = -1;
//左闭右开
while (left<right){ //因为是左闭右开所以当左右相等是无意义的
int mid = (right-left)/2+left;
if (nums[mid]>target){
right = mid; //注1
}else if (nums[mid]<target){
left = mid+1; //注2 - 注1与注二两个地方可以理解为左边的数比较过了右边的数要纳入下次循环中
}else {
return mid;
}
}
return reult;
}
}
27.移除元素
菜鸡不看题解首先想到的时暴力双层遍历逐个删除…不过在代码实现上还是有些地方卡住了,并不是一下就考虑到的
双层遍历暴力解法
- 时间复杂都 O(n^2)
- 空间复杂都O(1)
- 因为要删除控制循环遍历的终点,要把长度保存出来每次删除时减1
- 循环遍历删除元素时没有卡哥的写法妙
- 要注意删除元素后第一层循环下标抵消
class Solution {
public int removeElement(int[] nums, int val) {
//暴力解法
int size = nums.length; //这里先把长度取出来,因为在遍历删除时,长度会逐渐缩短的
for (int i = 0; i < size; i++) {
if (val==nums[i]){
/*for (int j = i; j < size-1; j++) {
nums[j] =nums[j+1];
}*/
for (int j = i+1; j < size; j++) { //这里看卡哥的题解:使j = i+1 可以不用考虑到最后一位J+1越界的问题,好妙!!!---上面是跟着自己的思路写的要控制j不能遍历到最后一位逻辑上还是有点绕
nums[j-1] = nums[j];
}
i--; //因为删除了元素,i当前索引要跟循环里的i++抵消---即下拨循环访问当前下标的新元素
size--; //因为"删除元素"数组"长度"缩短
}
}
return size;
}
}
双指针法
- 时间复杂度: O(n)
- 空间复杂度: O(1)
这里直接看的卡哥的题解,自己是没有想到的
主要有两点:
- 没有遇到目标值时双指针同步前移
- 遇到目标值慢指针停滞等待下个非目标值出现直到最后快指针遍历完整个数组慢指针就是最新数组长度
class Solution {
public int removeElement(int[] nums, int val) {
//快慢指针实现
int slow= 0;
for (int fast = 0; fast < nums.length; fast++) {
if (val != nums[fast]) { //1 将不等于目标值的元素前移; 2 没有遇到目标值前双执政同步移动, 遇到目标值不进判断慢指针停止移动,等待下个非目标值来,以此类推到最后慢指针就是新数组长度;
nums[slow++] = nums[fast]; //slow++ 到最后一位时正好也是数组长度
}
}
return slow;
}
}
双向双指针法
- 时间复杂度O(n)
- 空间复杂度O(1)
将右边不等于目标值的元素 覆盖 左边等于目标值的元素
class Solution {
public int removeElement(int[] nums, int val) {
//双向指针
int right= nums.length-1;
int left= 0;
while (left <= right){
//找左边等于目标值的, 一定要把目标值判断放在后面因为可能出现下标小于0的情况
while (left<=right && nums[left] != val){
++left;
}
// 找右边不等于目标值的元素, 一定要把目标值判断放在后面因为可能出现下标小于0的情况
while (right>=left && nums[right] == val){
--right;
}
if (left<right){ //这层判断是因为上面查找下标时左右相等时还会加或者减
nums[left++]=nums[right--]; //这里覆盖后还要前移下标,到最后一位时下标++正好也就是数组长度
}
}
return left;
}
}
三. 每日附加题
35.搜索插入的位置
这道题有两个条件有序和不重复,可以使用二分法解决也符合时间复杂度O(logn)
注意点
- 同二分法循环不变量原则
- 如果没有命中要考虑需要插入的位置:
- 如果最后一遍循环中间值大于目标值,那就只需要目标值插入中间值位置,中间值以后元素后移
- 如果最后一遍循环中间值小于目标值,那就需要目标值插入中间值之后
- 如果等于返回下标
只实现了左闭右闭
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length-1;
int result = 0;
while (left<=right){
int mid = left + (right-left)/2;
// System.out.println(left + "--" + right + "--" + mid + "--" + result );
if (nums[mid]>target){
right = mid-1;
result = mid;
}else if (nums[mid] <target){
left = mid +1;
result = left;
} else if(nums[mid] == target) {
result = mid;
break;
}
}
return result;
}
}