这是我参与「第四届青训营 」笔记创作活动的第9天
前言
正值8.9月份,各互联网大厂的秋招季在火爆开展,非常希望能进个中大厂,于是现在就用蹒跚的步伐在此开展数据结构算法的学习记录啦
一、走进数组
1、数组下标默认总是从0开始
2、因为内存空间地址是连续的,所以数组元素不能删,只能覆盖。即不能释放单一元素,如果要释放,就是全释放
3、数组的length属性
length属性不计算非整数的键,length值取最大的正整数键值+1,而不是键值对个数
举个栗子:
解释:a.length取最大的正整数键值‘5’+1 = 6,所以length值为6
下面是重头菜:关于数组经典算法题的个人见解和记录啦
二、二分查找
题目:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
提示:
- 你可以假设 nums 中的所有元素是不重复的。
- n 将在 [1, 10000]之间。
- nums 的每个元素都将在 [-9999, 9999]之间。
本题可以使用二分法的前提:
①有序数组(如果是无序的,可以先排序,但要注意排序的成本)
②无重复数组(一旦有重复元素,二分查找法返回的元素下标可能不唯一)
使用二分法的关键点:确定区间!!
①左闭右闭 [left , right]
②左闭右开 [left , right)
①左闭右闭[ left,right ]:
// 二分法--左闭右闭
var search = function(nums, target) {
//初始
let left = 0,
right = nums.length-1
//进入循环
while(left <= right){
let mid = left + Math.floor( (right - left)/2 )
if(nums[mid] < target){
//向右查
left = mid + 1
}
else if(nums[mid] > target){
//向左查
right = mid - 1
}
else{
//nums[mid] == target!! 找到当前值下标,返回
return mid
}
}
//退出循环
return -1
}
解释:
1、初始: left = 0 //因为数组的存储方式,下标从0开始
2、进入循环条件:while( left <= right ) // 因为区间[ left ,right ]代表了left == right时有意义,所以当 left == right 时也要进入循环。
3、向右查:left = mid + 1 // 为什么要加一?因为进入if的条件就是target大于nums[mid],而不是大于等于,mid这个下标的元素不会等于target,所以left要等于mid+1
4、向左查:right = mid -1 //为什么要减一?同理“向右查”,target是小于nums[mid],不是小于等于
5、返回当前值下标:return mid //进入到最后的else,代表nums[mid] == target啦
6、退出循环:return -1 //如果进入到循环的最后一次遍历,是‘left == right’,即只有一个值需要与target对比,如果target仍然没有进入最后的else‘return mid’,代表target未在数组nums中存在,所以返回-1
②左闭右开[ left ,right ):
//二分法--左闭右开
var search = function(nums, target) {
//初始
let left = 0,
right = nums.length
//进入循环
while(left < right){
let mid = left + Math.floor( (right - left)/2 )
if(nums[mid] < target){
//向右查
left = mid + 1
}
else if(nums[mid] > target){
//向左查
right = mid
}
else{
//nums[mid] == target!! 找到当前值下标,返回
return mid
}
}
//退出循环
return -1
}
一个小疑问:区别于上一个左闭右闭[ left,right ]的区间,左闭右开[ left ,right )有什么区别呢?为什么要这么设置?
带着小疑问往下寻找答案吧~
1、初始:right = nums.length // 因为左闭右开[ left ,right )中,right等于left没有意义,所以right的取值就是“最大可取的下标 + 1”。最开始的初始化,最大可取下标是nums.length -1,所以right = ( nums.length -1 ) + 1 = nums.length
2、进入循环:while(left < right) // 左闭右开[ left ,right ),right等于left没有意义
3、向左查:right = mid //为什么不是mid-1了呢?可以回看第一点‘right的取值就是“最大可取的下标 + 1”’。当进入nums[mid] > target条件语句中,表示mid不会是target的下标了,最大可取下标是mid-1,所以right = (mid - 1)+ 1 = mid
整理一下:
[ left,right ]:
初始条件:left = 0,right = arr.length -1
终止循环:left >= right
向左查:right = mid - 1
向右查:left = mid + 1
[ left ,right ):
初始条件:left = 0,right = arr.length
终止循环:left > right
向左查:right = mid
向右查:left = mid + 1
三、移除元素
题目:给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。
提示:
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
①本题使用“双指针”(又叫快慢指针)可以实现‘不借助额外的数组空间,实现原地移除’
//移除元素--双指针
var removeElement = (nums, val) => {
let left = 0
for(let right = 0; right< nums.length; right++){
//判断nums[right]
if(nums[right] != val){
nums[left++] = nums[right]
}
}
return left
}
解释:
1、通过一个快指针和一个慢指针在一个for循环下完成两个for循环的工作!!
2、先初始化:左右指针最开始指向0下标
3、进入循环:判断nums[right] // 如果右指针指向的元素不等于val,那么它就一定是输出数组中的一个元素,①于是将右指针指向的值赋给左指针,②左右指针同时右移。
如果nums[right] == val,那么这个值需要去除,right右指针右移,left左指针不动,表示[ 0,left )区间内没有该值
4、区间[ 0,left) 中的元素都不等于val。当左右指针遍历完输入的数组后,left的值就是输出数组的长度,[ 0,left )就是即将输出的数组。
来个小疑问:为什么输出的数组区间不是[ 0, left ]呢?
因为当right右指针进入最后一次if循环内,右指针的值赋给了左指针后,左指针自加一,即,此时左指针指向的下标是将要输出数组的最后一个下标加一,所以真正输出的是[ 0,left )数组,长度是left
复杂度分析:
时间复杂度O(n):n为序列长度,我们只需要遍历该序列至多两次
空间复杂度O(1):我们只需要常数的空间保存若干变量
②“优化方案”:同样利用双指针
//移除元素--双指针优化
var removeElement2 = (nums, val) => {
let left = 0,right = nums.length-1
while(left<=right){
//判断nums[left]
if(nums[left] == val){
nums[left] = nums[right--]
}else{
left++
}
}
return left
}
与上面的双指针解题区别在于:
1、初始化时,左指针同样指向0下标,右指针指向了最后一个下标。
2、进入循环的次数好像减半了,上面题解是最坏的情况是左右指针分别遍历了该序列一次,即最多遍历两次,而本题解的左右指针分别从数组的首尾出发向中间靠拢,所以至多是遍历一次序列。
3、进入while循环的条件是left<=right,为什么不是left<right? 因为left 等于right时也有效,即该值也需要if判断
4、上面题解与val判断的是右指针right,本题与val判断的是左指针left。即,直接由左指针判断,无须经过右指针判断后再赋值给左指针
其他:
1、If...else逻辑:如果左指针left指向的值 == val,那么将右指针指向的值覆盖左指针的值(下一次循环就是判断该值,即右指针赋给左指针的值),然后右指针左移 -- ,否则左指针left指向的值 !=val ,那么左指针指向的值保留(不用让右指针的值覆盖左指针的值) ,左指针右移 ++。
2、与上题解同,输出的数组是[ 0,left ),输出的数组长度是left
未完待续...