今日学んだ記事とビデオへのリンク
704 記事リンク:リンク
704 動画解説リンク:リンク
27 記事リンク:リンク
27 動画解説リンク:リンク
704 二分探索
最初にタイトルを見てください
トピックは順序付けされた配列であり、配列内に繰り返される要素がないため、ターゲット値を探すときに最初に思い浮かぶ方法は二分法です。
コードカプリスを読んだ感想
二分法はコードのランダムな思考でも使用されます
二分法を使用するための条件
このトピックの前提条件は、配列が順序付けされた配列であることです。同時に、このトピックでは、配列内に繰り返される要素がないことも強調します。これは、要素が繰り返されると、要素の添字が二分探索によって返されるからです。これらはバイナリ メソッドを使用するための前提条件であり、タイトルの説明が上記の条件を満たしていることがわかったら、二分法を使用できるかどうかを考えてください。
二分探索の鍵
-
区間の定義 二分
法の考え方が明確でない主な理由は、区間の定義が明確ではなく、区間の定義が不変であることです。二分探索の過程では不変量が保たれなければなりません。つまり、while 探索では各境界処理が区間の定義に従って動作しなければなりません。これがループ不変の法則です。間隔には 2 つの一般的な定義があります。
- 左閉 右閉 [左、右]
- 左閉右開[左、右)
-
境界条件
二分探索には多くの境界条件が含まれるため、選択した区間定義に従って境界条件を設定する必要があります。
例:
while(left < right)
またはwhile(left <= right)
right = middle
right = middle - 1
導入中に遭遇した困難
1. 最初の書き方、区間を定義する[左、右] 区間を
定義した後、境界条件を設定するにはどうすればよいですか?
- while (left <= right) <= を使用するには、left == right が意味をなすため、<= を使用します。
- (nums[middle] > target) right に middle - 1 を割り当てる必要がある場合、現在の nums[middle] が target であってはいけないため、次に検索される左区間の終了添字位置は middle - 1 になります。
2. 2 番目の書き方、区間を定義する [左、右)
区間を定義した後、境界条件を設定するにはどうすればよいですか?
- while (left < right)、区間 [left, right) では left == right は無意味であるため、ここでは < を使用します。
- (nums[middle] > target) right が middle に更新される場合、現在の nums[middle] が target と等しくないため、左の区間に移動して検索を続行します。検索区間は左閉右開区間です。 、つまり、右が中央に更新されます。つまり、次のクエリ間隔は nums[middle] と比較されません。
コード
最初の書き方【左、右】
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
while (left <= right) {
// 当left==right,区间[left, right]依然有效,所以用 <=
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
} else {
// nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
2 番目の書き方 [左、右)
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
while (left < right) {
// 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
int middle = left + ((right - left) >> 1);
if (nums[middle] > target) {
right = middle; // target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在[middle + 1, right)中
} else {
// nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
27 要素を削除する
最初にタイトルを見てください
このトピックでは、値が val に等しいすべての要素を削除し、削除された配列の新しい長さを返す必要があることがわかると、頭に浮かぶ解決策は、高速ポインタと低速ポインタを介して対応する要素を削除するダブル ポインタ メソッドです。 。
コードカプリスを読んだ感想
Code Caprice では、ダブルポインター方式も推奨しています。
ダブルポインタ方式 (高速ポインタ方式と低速ポインタ方式): 高速ポインタと低速ポインタを介して、1 つの for ループの下で 2 つの for ループの作業を完了します。
高速ポインタと低速ポインタを定義する
- 高速ポインタ: 新しい配列の要素を検索します。新しい配列はターゲット要素を含まない配列です。
- スローポインター: 新しい配列の添え字が更新される場所を指します。
削除の手順は次のとおりです。
導入中に遭遇した困難
ダブルポインタ方式(ファストポインタ方式とスローポインタ方式)の解き方はマスターしましたが、その逆のダブルポインタ方式はちょっとわかりにくいです。
/**
* 相向双指针方法,基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*/
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
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一定指向了最终数组末尾的下一个元素
}
};
コード
ダブルポインタ方式(ファストポインタ方式とスローポインタ方式)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
if (val != nums[fastIndex]) {
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
};
今日は収穫
1. 二分法をより深く理解するには、ループ内の検索間隔の定義、つまりループ不変ルールに従って境界処理を実行することを主張することが重要です。
2. ダブルポインタ法におけるファストポインタとスローポインタの定義について理解を深める
今日はよく3時間勉強します。