数组理论基础,704. 二分查找,27. 移除元素
二分查找
有序无重复元素数组中查找某个元素。 难点:
- 注意left,right更新时是middle还是middle+/-1,对于左闭的,不需要+/-1,因为/2向下取整对应就是区间靠左的元素,是一致的。
- 循环的终止条件是left<=right还是left==right。
- right = middle - 1,这是反直觉的,应为边界在向左收缩时要更新右边界值。
前两个边界条件都直接跟搜索区间的定义有关,搜索区间是一个不变量,在查找过程中定义不能出现歧义,O(logn),因为可以抽象为二叉树节点,节点数量是N = 2^n(n是树的高度),每次搜索只走一个分支复杂度就是Log2(N)。
左闭右闭写法
- left = right是合法区间,所以left <= right,初始搜索区间是[0,nums.size() - 1]。
- 更新区间边界索引不能包含middle(上次搜索已经确定nums[middle] != target,更新时边界再包含middle,由于左闭右闭,就把不是搜索区间的值放入,出现问题),left = middle + 1,right = middle - 1。
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while(left <= right) {
int middle = left + (right - left) / 2;
if(nums[middle] > target) {
right = middle - 1;
} else if(nums[middle] < target) {
left = middle + 1;
} else {
return middle;
}
}
return -1;
}
};
middle = left + (right - left) / 2可以防止越界, 等同于 middle = (left + right)/2,对于左闭右闭middle =(left + right + 1)/2 也可以。
左闭右开写法
- left = right不合法,left < right,初始搜索区间[0,nums.size())。
- 更新区间边界索引时,right = middle,因为右开正好表示不包含middle,left = middle + 1。
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size();
while(left < right) {
int middle = left + (right - left) / 2;
if(nums[middle] > target) {
right = middle;
} else if(nums[middle] < target) {
left = middle + 1;
} else {
return middle;
}
}
return -1;
}
};
左开右闭
不常见,left < right,初始区间(-1,nums.size() -1]。right = middle - 1, left = middle。这里注意mid值要+1向上取整,因为左开右闭,如果还向下取整,只有两个元素的时候left值一直等于mid没办法等于right死循环(mid向下取整等于left,left又等于middle,死循环),而左闭右开就不会有这问题。
- 根据边界收缩情况写对应的 mid 计算公式,看见 left = mid + 1 (左闭) 就写 mid 下取整:mid=(left+right)/2、看见 left = mid (左开)就写 mid 上取整:mid=(left+right+1)/2;
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = -1;
int right = nums.size() - 1;
while(left < right) {
int middle = left + (right - left + 1) / 2;
if(nums[middle] > target) {
right = middle - 1;
} else if(nums[middle] < target) {
left = middle;
} else {
return middle;
}
}
return -1;
}
};
左开右开
不推荐,容易死循环,left < right - 1 (注意(1,2)区间是没意义的),初始区间(-1,nums.size())。right = middle, left = middle。
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = -1;
int right = nums.size();
while(left < right - 1) {
int middle = left + (right - left) / 2;
if(nums[middle] > target) {
right = middle;
} else if(nums[middle] < target) {
left = middle;
} else {
return middle;
}
}
return -1;
}
};
移除元素
暴力
第一次循环找要移除的元素,第二次循环移动后面的元素,注意下标和size更新-1,O(n^2)。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int size = nums.size();
for(int i = 0; i < size; i++) {
if(nums[i] == val) {
for(int j = i + 1; j < size; j++) {
nums[j - 1] = nums[j];
}
i--;
size--;
}
}
return size;
}
};
双指针(快慢指针)
快指针先扫一遍,遇到不需要删除的值就更新慢指针的索引和数组的值,这样一遍for循环就能完成删除。总结来说,快指针寻找新数组需要的元素,满指针指向新数组的索引,O(n)。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int size = nums.size();
int slowIndex = 0;
int fastIndex = 0;
for(fastIndex; fastIndex < size; fastIndex++) {
if(nums[fastIndex] != val) {
nums[slowIndex ++] = nums[fastIndex];
}
}
return slowIndex;
}
};
C#代码
二分查找:
public class Solution {
public int Search(int[] nums, int target) {
int left = -1;
int right = nums.Length - 1;
while(left < right) {
int mid = left + (right - left + 1) / 2;
if(nums[mid] > target) {
right = mid - 1;
} else if(nums[mid] < target) {
left = mid;
} else {
return mid;
}
}
return -1;
}
}
移除元素:
public class Solution {
public int RemoveElement(int[] nums, int val) {
int slowIndex = 0;
int fastIndex = 0;
for(fastIndex = 0; fastIndex < nums.Length; fastIndex++) {
if(nums[fastIndex] != val) {
nums[slowIndex ++] = nums[fastIndex];
}
}
return slowIndex;
}
}
和C+的区别在于传参不需要&取数组地址,数组长度是xx.Length而不是vector.size()。
二三刷
704 二分查找
#include<iostream>
#include<vector>
using namespace std;
class Solution {
public:
//左闭右闭
int search1(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target == nums[mid]) return mid;
if (target > nums[mid]) {
left = mid + 1;
}
if (target < nums[mid]) {
right = mid - 1;
}
}
return -1;
}
int search2(vector<int>& nums, int target) {
int left = 0;
int right = nums.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (target == nums[mid]) return mid;
if (target > nums[mid]) {
left = mid + 1;
}
if (target < nums[mid]) {
right = mid;
}
}
return -1;
}
};
int main() {
while (1) {
vector<int> test;
int n = -1;
int temp = -1;
int target = -1;
cin >> n;
Solution* s1 = new Solution;
for (int i = 1; i <= n; i++) {
cin >> temp;
test.push_back(temp);
}
cin >> target;
cout << s1->search2(test, target) << endl;
}
return 0;
}
27 移除元素
#include<iostream>
#include<vector>
using namespace std;
class Solution{
public:
int doit1(vector<int>& nums, int val) {
int size = nums.size();
for (int i = 0; i < size; i++) {
if (nums[i] == val) {
for (int j = i; j < size - 1; j++) {
nums[j] = nums[j + 1];
}
size--;
i--;
}
}
return size;
}
int doit2(vector<int>& nums, int val) {
int slow = 0;
int fast = 0;
while (fast < nums.size()) {
if (nums[fast] != val) {
nums[slow] = nums[fast];
slow++;
}
fast++;
}
return slow;
}
};
int main() {
while(1) {
int n = 0;
vector<int> s{};
int temp = 0;
int val;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> temp;
s.push_back(temp);
}
cin >> val;
Solution* s1 = new Solution();
int size = s1->doit2(s, val);
cout << size << endl;
for (int i = 0; i < size; i++) {
cout << s[i] << " ";
}
cout << endl;
delete s1;
}
return 0;
}
35.搜索插入位置(二分)
当没搜到时,left的位置恰好就是要插入的位置,当target>第一个元素时,left最终迭代结束后会再加1,就是要插入的位置,当target<第一个元素时,right会收敛到-1,而left维持0不动,还是插入位置。
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int resIndex = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
}
if (nums[mid] < target) {
left = mid + 1;
}
if (nums[mid] > target) {
right = mid - 1;
}
}
return left;
}
};
使用ACM控制输入输出时注意,while(cin>>x)后EOF标志会残留在缓冲区内,为了不影响下次输入,需要清空缓冲区。
int main() {
Solution s1;
vector<int> s;
int i;
while (cin >> i) {
s.push_back(i);
}
//清空缓冲区
cin.clear();
cin.ignore();
int target;
cin >> target;
cout << s1.searchInsert(s, target);
return 0;
}
34 在排序数组中查找元素的第一个和最后一个位置(二分)
两次二分分别记录第一个和最后一个位置,当找到元素更新记录并向右收敛(left = mid + 1)得到最后一个位置,第一个位置以此类推。
class Solution {
public:
vector<int> res{-1, -1};
vector<int> searchRange(vector<int>& nums, int target) {
int first = -1;
int last = -1;
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target == nums[mid]) {
first = mid;
right = mid - 1;
}
if (target < nums[mid]) {
right = mid - 1;
}
if (target > nums[mid]) {
left = mid + 1;
}
}
left = 0;
right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target == nums[mid]) {
last = mid;
left = mid + 1;
}
if (target < nums[mid]) {
right = mid - 1;
}
if (target > nums[mid]) {
left = mid + 1;
}
}
res[0] = first;
res[1] = last;
return res;
}
};
int main() {
Solution s1;
vector<int> s;
int i;
while (cin >> i) {
s.push_back(i);
}
cin.clear();
cin.ignore();
int target;
cin >> target;
vector<int> ans = s1.searchRange(s, target);
for (int item : ans) {
cout << item << " ";
}
return 0;
}
26删除有序数组中的重复项(快慢指针)
注意题目是非严格递增也就是说虽然有序但有重复元素,根据和前一个元素是否相等判断一下重复即可,如果不是有序的用哈希表扫一遍记录重复次数也可以。
- 注意,这题的最好解思路应该是fast指针不断和slow指针比较,而不是fast自己一个劲跑slow只负责记录,对应两种写法,当重复元素多的时候fast一个劲跑就很难解。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int slow = 0;
int fast = 0;
for (fast; fast < nums.size(); fast++) {
//fast一个劲跑,检测重复的
if (fast == 0 || fast > 0 && nums[fast] != nums[fast - 1]) {
nums[slow++] = nums[fast];
}
// fast遍历不断和slow比,注意slow-1才是上一个应该被保留的元素所移动到的指定位置
//if (slow == 0 || nums[fast] != nums[slow - 1]) {
//nums[slow++] = nums[fast];
//}
}
return slow;
}
};
80删除有序数组中的重复项II(快慢指针)
相比26,如果继续只考虑fast判断重复会有很多种情况,因为fast可能和slow更新后记录的元素冲突,不如原来fast前面没有2,slow更新完有2就会干扰重复判断,因此最优解是直接判断fast和slow-2的元素是否重复,因为是有序数组不可能存在1 2 1的情况,元素肯定是连续的。自己做的时候花了10min把所有条件都想出来了,实际上两种方式条件约化后是一样的。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
unordered_map<int, int> data;
if (nums.size() <= 2) return nums.size();
int slow = 2;
int fast = 2;
for (fast; fast < nums.size(); fast++) {
//if (nums[fast] != nums[fast - 1] || nums[fast] != nums[fast - 2] || nums[fast] == nums[fast - 2] && slow == fast - 1 && nums[slow - 1] != nums[slow - 2]) {
// nums[slow++] = nums[fast];
//}
if (nums[fast] != nums[slow - 2]) {
nums[slow++] = nums[fast];
}
}
return slow;
}
};