一、二分查找
- LeetCode题目链接
- 卡尔老师代码随想录讲解
- 关键点:二分法大家再也熟悉不过了,但是很多人搞不明白二分法中边界设置和中间值的关系。 其实,我们只要确定自己的合法区间范围 就可以很简单的区分了,详情请查看卡尔老师代码随想录讲解。
- 代码:
第一种:设置区间 left 初始值为0, right 为 nums.length-1,即左闭右闭([ ]),因为 nums.length-1 在数组中是合法下标。
var search = function(nums, target) {
let left = 0
let right = nums.length - 1
while (left <= right) {
//左开右闭所以left和right相等也属于合法区间
let middle = Math.floor((left+right)/2)
if (nums[middle] < target) {
//改变左边界
left = middle + 1 //middle已经不相等了所以应该+1
}
else if (nums[middle] > target) {
//改变右边界
right = middle - 1
}
else {
//middle和target相等情况
return middle
}
}
//没找到即返回-1
return -1
};
第二种:设置区间 left 初始值为0, right 为 nums.length,即左闭右开([ )),因为 nums.length 在数组中是非法下标。
var search = function(nums, target) {
let left = 0
let right = nums.length
while (left < right) {
//左闭右开所以left和right相等属于非法区间
let middle = Math.floor((left+right)/2)
if (nums[middle] < target) {
left = middle + 1
}
else if (nums[middle] > target) {
right = middle //因为右开区间,所以right取middle即可
}
else {
return middle
}
}
return -1
};
- 注意:
1、二分法适应于有序数组
2、 Matn.floor((left+right)/2) 为防止溢出可以写成 left + Math.floor((right - left)/2) - 复杂度分析:
时间:O( log n \log_{} {n} logn)
空间:O( 1 1 1)
二、移除元素
- LeetCode题目链接
- 卡尔老师代码随想录讲解
- 关键点: 因为题目强调原地,不开辟新空间,也不强求元素的顺序变化,所以想到双指针的做法。一个指针用来指向下一个元素被保存的位置,一个指针用来不断遍历原数组寻找不被删除的元素
- 代码:
我思考的方法:
想到了一个头指针和尾指针,头指针不断往前用来保存尾指针已经查验过与目标元素不相等的元素,而尾指针用来不断往前将尾部无需删除的元素都置换到前面,找到要删除的元素则不置换。
var removeElement = function(nums, val) {
let start = 0
let end = nums.length-1
while (start <= end) {
if (nums[end] != val) {
let tmp = nums[start]
nums[start] = nums [end]
nums[end] = tmp
start++
}
else {
end --
}
}
return start
};
卡尔老师讲解的:
卡尔老师也讲解了一种双指针的做法,但两个指针都从头开始,一个指针用于遍历数组元素,一个指针用于指向将要保存与目标元素不相等的元素的位置。
var removeElement = function(nums, val) {
let i = 0; //用来保存非目标元素的指针
let j = 0 ; //用来遍历原数组的指针
for (;j < nums.length ; j++ ) {
if (nums[j] == val ) {
//找到要删除的元素,跳过
continue
}
else {
//不是要删除的元素,保存到i所指向的位置
nums[i] = nums [j]
i++
}
}
return i
};
- 注意:
无 - 复杂度分析:
时间:O( n n n)
空间:O( 1 1 1)
三、有序数组的平方
- LeetCode题目链接
- 卡尔老师代码随想录讲解
- 关键点: 按照题目描述是一个非递减顺序排序的整数数组,因为平方组成新数组,考虑负数原因并不能直接平方得到新数组,而数据特征是从数组头和尾部两边往中间,越靠近中间的数其平方越小。
- 代码:
var sortedSquares = function(nums) {
let newarr = []
let head = 0
let end = nums.length - 1
while (head <= end) {
let head_2 = Math.pow(nums[head],2)
let end_2 = Math.pow(nums[end],2)
if (head_2 >= end_2) {
newarr.unshift(head_2)
head++
}
else {
newarr.unshift(end_2)
end--
}
}
return newarr
};
- 注意:
可以先通过 Math.abs() 求得绝对值直接比较,在加入新数组的时候再算平方 - 复杂度分析:
时间:O( n n n),如果先求得平方再进行快排时间复杂度就为O( n n n + + + l o g n log_{}{n} logn)
空间:O( 1 1 1)
四、长度最小的子数组
- LeetCode题目链接
- 卡尔老师代码随想录讲解
- 关键点: 按照题目很容易想到暴力解法,即滑动窗口从第一个元素开始,不断更新滑动窗口末尾位置,但这样的算法会在运行时超时,从而达不到题目要求。根据卡尔老师讲解我们可以确定滑动窗口的末尾,移动滑动窗口的始端,因为要找到长度最小的数组,所以当我们找到更小的长度数组时需要缩小滑动窗口(即将始端前移以找到更小的),不符合 sum > > > target 时需要扩大滑动窗口(即将尾端前移来增加 sum)。
- 代码:
卡尔老师所写的代码简洁明了:
var minSubArrayLen = function(target, nums) {
let start, end
start = end = 0
let sum = 0
let len = nums.length
let ans = Infinity
while(end < len){
sum += nums[end];
while (sum >= target) {
ans = Math.min(ans, end - start + 1);
sum -= nums[start];
start++;
}
end++;
}
return ans === Infinity ? 0 : ans
};
我的写法:代码有问题,但我自查觉得逻辑没问题,在LeetCode两个测试用例没过,希望大家帮我找出问题。
var minSubArrayLen = function(target, nums) {
let res = nums.length+1
for (let i = 0,j = 0; j < nums.length;) {
let sum = 0
for (let k=i;k<=j;k++) {
sum+=nums[k]
}
if (sum >= target) {
newres = j-i+1
console.log(newres)
if (newres <= res) { //这里跟卡尔老师的有所区别用的if
res = newres
i++
}
else {
j++
}
}
else {
j++
}
}
return res > nums.length ? 0 : res
}
- 注意:
卡尔老师的算法的精髓就是在发现比存储的长度小时会让 sum 减去当前 i 的值,并让 i 自减以缩小滑动窗口 - 复杂度分析:
只对卡尔老师参考代码的进行分析
时间:O( n n n),不要以为for里放一个while就以为是O( n 2 n^2 n2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 ∗ n 2*n 2∗n 也就是O( n n n)。
空间:O( 1 1 1)
五、螺旋矩阵II
- LeetCode题目链接
- 卡尔老师代码随想录讲解
- 关键点: 这道题目逻辑不难,因为按螺旋将值填满矩阵即可,我们可以先填充外部矩阵再填充内部矩阵,但关键点是在填充矩阵时什么时候转折,以及转折点在什么时候处理。卡尔老师讲到循环不变量原则,即处理边界时我们不处理边界尾元素,每条边界都按照这样的原则 这样就不会在转折点犯迷糊。
- 代码:
卡尔老师思想代码:
var generateMatrix = function(n) {
let arr = Array.from(new Array(n), () => new Array(n))
let x = 0 //每次填充的开始x坐标
let y = 0 //每次填充的开始y坐标
let offset = 1 //缩小边界
let count = 1 //需要填充的值从1开始
let loop = Math.floor(n/2) //需要几圈螺旋循环
while (loop--) {
let i,j = 0
//处理上边界
for (j = y;j < n-offset; j++) {
arr [x] [j] = count++ //这里开始位置要看x,不能取i
}
//处理右边界
for (i = x;i < n-offset; i++ ) {
arr [i] [j] = count++
}
//处理下边界
for ( ;j > y; j-- ) {
arr [i] [j] = count++
}
//处理左边界
for ( ;i > x; i-- ) {
arr [i] [j] = count++
}
//更新开始坐标位置
x++
y++
offset++
}
if ( n % 2 == 1) {
arr [x] [y] = count //奇数圈要填充最后一个元素
}
return arr
}
不遵循循环不变量 原则:这里用了四个点分别是左上点、右上点、右下点、左下点 从代码简洁读上来看上面明显优于下面。
var generateMatrix = function(n) {
//这里我用了正方形的四个角点来获取边界循环条件
let topleft = [0,0]
let topright = [0,n-1]
let bottom_left = [n-1,0]
let bottom_right = [n-1,n-1]
let i = 1
while (i<=n*n) {
//左上点和右上点重合说明是最后一个元素了
if(topleft[1] == topright[1]){
arr[topleft[0]][topleft[1]] = n*n
return arr
}
for (let j = topleft[1];j <= topright[1]; j++) {
arr[topleft[0]][j]=i
i++
}
for (let j = topright[0]+1;j <= bottom_right[0]; j++) {
arr[j][topright[1]]=i
i++
}
for (let j = bottom_right[1]-1;j >= bottom_left[1]; j--) {
arr[bottom_right[0]][j]=i
i++
}
for (let j = bottom_left[0]-1;j > topleft[0]; j--) {
arr[j][bottom_left[1]]=i
i++
}
topleft[0]++,topleft[1]++
topright[0]++,topright[1]--
bottom_left[0]--,bottom_left[1]++
bottom_right[0]--,bottom_right[1]--
}
return arr
}
- 注意:
无 - 复杂度分析:
两种写法时间和空间复杂度一致
时间:O( n 2 n^2 n2)
空间:O( 1 1 1)