theme: condensed-night-purple
highlight: a11y-dark
这是我参与「第五届青训营 」伴学笔记创作活动的第 16 天
1. 数组
数组是存放在连续内存空间上的相同类型数据的集合。
数组可以方便的通过下标索引的方式获取到下标下对应的数据。
二分查找
数组为 有序数组,同时数组中无重复元素, 因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件
左闭右闭 [left, right]****
- while (left <= right) **要使用 <= ,因为left == right是有意义的,所以使用 <=
- if (nums[middle] > target) *right 要赋值为 middle - 1*,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
左闭右开 [left, right)**
- while (left < right) ,这里使用 < ,因为left == right在区间[left, right)是没有意义的
- if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
left + ((right -left) >> 1) == (left + right) /2
>>
: 二进制右移
举个例子: 1010 >> 1 == 0101
1010 十进制 10
0101 十进制 5
综上 >> 1 作用相当于除二
所以 left + ((right -left) >> 1) ==> left + ((right -left)/2)
==> left + right/2 -left/2 ==> left/2 + right/2 ==> (left + right) /2
问题 :为什么不直接用(left + right) /2 而用left + ((right -left) >> 1)
答: 是因为left + right 在某种情况下可能会超过基本类型所能容纳的最大值,而且 >> (位运算) 比 / 运算要快一点
在排序数组中查找元素的第一个和最后一个位置
https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/submissions/
解题思路:
找左右边界,如果在数组中有一个target,在下标3,左右边界为【4,5】
找边界:target在左边记录right,会越往左走
target在右边记录left,会越往右走
在return时,
- 只要有一个边界为-2, target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1} if(leftBorder === -2 || rightBorder === -2 )
- target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
if (rightBorder - leftBorder > 1) return [leftBorder + 1, rightBorder - 1]
- target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1} return [-1, -1]
移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
暴力解法
这个题目暴力的解法就是两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。
双指针法
双指针法(快慢指针法) : 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
定义快慢指针
- 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
- 慢指针:指向更新 新数组下标的位置
注意这些实现方法并没有改变元素的相对位置
int slowIndex = 0; for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) { if (val != nums[fastIndex]) { nums[slowIndex++] = nums[fastIndex]; } } return slowIndex;
题目:外面有宝,赶紧捡回来按序放好,不能重样哟 有点像小夫妻俩,老公q在外面淘宝,找到后运回来,找到一个新的宝,老婆p在家里就给挖个新坑放好,最后外面没宝了,就结束咯
中间对话
老公:老婆,这个家里有没?(if) 老婆:有了。(nums[p] == nums[q])你再找找(q++)
老公:老婆,这个家里有没?(if) 老婆:有了。(nums[p] == nums[q])你再找找(q++)
老公:老婆,这个家里有没?(if) 老婆:这个没有,拿回来吧 (nums[p] != nums[q]) 放好了,我到下一个位置等你(p++) 你再继续找吧(q++)
貌似双指针都可以这么理解
相向双指针方法,基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素
int leftIndex = 0; int rightIndex = nums.size() - 1; while (leftIndex <= rightIndex) { // 找左边等于val的元素 while (leftIndex <= rightIndex && nums[leftIndex] != val){ ++leftIndex; } // 找右边不等于val的元素 while (leftIndex <= rightIndex && nums[rightIndex] == val) { -- rightIndex; } // 将右边不等于val的元素覆盖左边等于val的元素 if (leftIndex < rightIndex) { nums[leftIndex++] = nums[rightIndex--]; } } return leftIndex; // leftIndex一定指向了最终数组末尾的下一个元素
https://www.programmercarl.com/0027.%E7%A7%BB%E9%99%A4%E5%85%83%E7%B4%A0.html#%E6%80%9D%E8%B7%AF
题目
- 删除有序数组中的重复项https://leetcode.cn/problems/remove-duplicates-from-sorted-array/
- 844.比较含退格的字符串 没看懂双指针
- 977.有序数组的平方
双指针法,i指向起始位置,j指向终止位置。
定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置。
如果A[i] * A[i] < A[j] * A[j] 那么result[k--] = A[j] * A[j]; 。
如果A[i] * A[i] >= A[j] * A[j] 那么result[k--] = A[i] * A[i]; 。
滑动窗口
滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
在暴力解法中,是一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程。
那么滑动窗口如何用一个for循环来完成这个操作呢。
如果用一个for循环,那么应该表示 滑动窗口的起始位置,还是终止位置。
- 如果只用一个for循环来表示 滑动窗口的起始位置,遍历剩下的终止位置难免再次陷入 暴力解法的怪圈。
- 所以 只用一个for循环,那么这个循环的索引,一定是表示 滑动窗口的终止位置。
实现滑动窗口,主要确定如下三点:
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的结束位置?
题目:长度最小的子数组
在本题中
- 窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
- 窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
- 窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
var minSubArrayLen = function(target, nums) { let sum = 0, result = nums.length , j = 0, i = 0, subL = 0; for (; j < nums.length; j ++) { sum += nums[j] while (sum >= target) { subL = j - i + 1; result = result < subL ? result : subL; sum -= nums[i ++]; } } return result };
题目
/** * @param {number[]} fruits * @return {number} */ var totalFruit = function(fruits) { let l = 0;//起始指针 let maxLen = 0;//窗口的最大长度 其中最多包涵两种水果 let n = 0//前一类水果的结束位置 let arr = [fruits[l]]//水果的种类数组 console.log(arr) for(let r = 0; r < fruits.length; r++){//窗口的右指针不断前进 if(!arr.includes(fruits[r])){//如果窗口中不包含 进窗口的水果 if(arr.length <= 1){//如果只有一种水果 arr[1] = fruits[r]//将这种水果加入arr数组 }else{//如果有两种水果 l = n//更新窗口的左边界 arr[0] = fruits[r-1]//更新arr中水果的种类 arr[1] = fruits[r] } } if(fruits[r] !== fruits[n]){//如果进来了一种新的类型的水果 更新前一种水果的位置 n = r } maxLen = Math.max(maxLen,r-l+1)//更新滑动窗口的最大值 } return maxLen };
螺旋矩阵https://leetcode.cn/problems/spiral-matrix-ii/submissions/
给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
坚持循环不变量原则
模拟顺时针画矩阵的过程:
- 填充上行从左到右
- 填充右列从上到下
- 填充下行从右到左
- 填充左列从下到上
由外向内一圈一圈这么画下去
每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来
math函数库中的一个函数,math.floor (x) 返回小于参数x的最大整数,即对浮点数向下取整。x[]的取值。
Array.fill
arr.fill(value[, start[, end]])
fill() 方法用一个固定值填充一个数组中,
从起始索引到终止索引内的全部元素,
不包括终止索引,
返回被修改后的数组。
value:用来填充数组元素的值。
start:起始索引,默认值为0。
end:终止索引,默认值为 this.length。
``` /** * @param {number} n * @return {number[][]} */ var generateMatrix = function(n) { let loop = Math.floor(n / 2); let mid = Math.floor(n / 2); let count = 1 let row = col = 0 let startX = startY = 0 let offset = 1 let res = new Array(n).fill(0).map(() => new Array(n).fill(0));
while(loop --) { row = startX col = startY for(; col < n - offset ; col ++) res[row][col] = count ++
for (; row < n - offset ; row ++) res[row][col] = count ++
for (; col > startY; col --) res[row][col] = count ++
for (; row > startX; row --) res[row][col] = count ++ startX ++ startY ++ offset += 1 } if(n % 2 === 1) res[mid][mid] = count return res }; ```