テーマ: 凝縮夜紫
ハイライト: a11y-dark
仲間ノート作成活動「第5期青少年合宿」に参加して16日目です。
1. アレイ
配列は、連続したメモリ空間に格納された同じタイプのデータのコレクションです。
配列は、添え字インデックスを通じて添え字の下にある対応するデータを簡単に取得できます。
二分探索
配列は 順序付き配列であり、配列内に繰り返される要素はありません。要素が繰り返されると、バイナリ検索メソッドによって返される要素の添え字が一意でなくなる可能性があるためです。これがバイナリ検索メソッドを使用するための前提条件です。
左閉 右閉 [左、右] * * **
- while (left <= right) * * <= を使用するには、left == right が意味をなすため、<= を使用します。
- if (nums[middle] > target) * right には middle - 1* を割り当てる必要があります。現在の nums[middle] が target であってはいけないため、次に検索される左区間の終了添え字位置は middle - 1 になります。
左閉 右開 [左、右) * *
- while (left < right) では、区間 [left, right) では left == right は無意味であるため、ここでは < を使用します。
- (nums[middle] > target) right が middle に更新された場合、現在の nums[middle] が target と等しくないため、左の区間に移動して検索を続行します。検索区間は左閉右開区間です。 、つまり、右が中央に更新されます。つまり、次のクエリ間隔は nums[middle] と比較されません。
左 + ((右 -左) >> 1) == (左 + 右) /2
>>
: バイナリ右シフト
例: 1010 >> 1 == 0101
1010 10進数の10
0101 10進数5
要約すると>> 1 は 2 で割ることと同じです
所以 left + ((right -left) >> 1) ==> left + ((right -left)/2)
==> 左 + 右/2 -左/2 ==> 左/2 + 右/2 ==> (左 + 右) /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/
問題解決のアイデア:
配列内にターゲットがある場合、添字 3 で左右の境界を見つけます。左右の境界は [4, 5] です。
境界を見つけます。ターゲット レコードは左側にあり、さらに左側に移動します。
ターゲットレコードは右に左にあり、さらに右に進みます
帰りには、
- 只要有一个边界为-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 }; ```