二分查找
leetcode:704
写代码时的重点或者难点:
- 判断while结束的条件 left<right或left≤right,while内在我看来可以当作是搜索域。
- 当nums[middle]大于target时,需更新右区间,此时的right = middle还是
right = middle – 1
区间定义的把握(循环不变量原则):主要为2种,左闭右闭([left, right])或是左闭右开([left,right)),需提前确认,以确定上述重难点的分歧。
以左闭右闭情况为例,在搜索开始的区域,left = 0, right = nums.size()-1,需考虑while循环条件的合法性,当区间为[1,1]时,区间中仅存在[1]一个元素,此时while(left<right)不合法,但while(left<=right)合法。因此在左闭右闭的情况下,考虑while(left<=right)更合理
当进入循环时,middle = (left + right)/2(由于left和right均为int型,有时需考虑int类型相加的越界问题),若nums[middle]大于target,更新右区间,由于区间为闭区间,middle在前已经判断过,所以需将middle剔除之后的判断搜索域,right = middle – 1,若仍选用right = middle,则将已判断的参数放入下一个搜索域,会影响边界判断或者说性能,而当nums[middle]小于target,更新左区间,left= middle + 1,思路同上。当nums[middle] == target,
找到了target对应nums中的下标middle,return middle。若在while中找不到结果,返回-1,可以考虑用一个result = -1,return result.
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0,right = nums.size()-1;
int middle = 0,result = -1;
while(left <= right){
middle = (left + right)/2;
if(target == nums[middle])
return middle;
if(target<nums[middle]){
right = middle - 1;
}
else{
left = middle + 1;
}
}
return result;
}
};
再以左闭右开为例,此时,开始的搜索域中left = 0,right = num.size()(考虑数组从0开始,num.size()从1开始,左闭右开的情况下,包含右区间+1才能包含所有的值)同样以while循环条件作为起点,由于[1,2)仅包含1一个元素,而[1,1)不包含元素,所以此时考虑while(left<right),当进入循环时,若nums[middle]大于target,对右区间的更新中,虽然在之前判断了middle所处位置的元素,但考虑到区间为左闭右开,right = middle并不会将middle元素放入下个循环的搜索域。再考虑nums[middle]小于target的情况,更新左区间,left = middle + 1。
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0,right = nums.size();
int middle = 0,result = -1;
while(left < right){
middle = (left + right)/2;
if(target == nums[middle])
return middle;
if(target<nums[middle]){
right = middle ;
}
else{
left = middle + 1;
}
}
return result;
}
};
此外,该题中二分查找的时间复杂度为O(logn),空间复杂度为O(1),可以认为大部分二分查找的时间复杂度和空间复杂度都是这样,但二分查找有一个前提条件是数组有序,若数组是无序的,则需考虑将数组先进行排序,那么时间复杂度和空间复杂度就有可能发生改变。
移除元素
leetcode:27
首先考虑暴力解法,遍历整个数组,将值为val的数值用其后面的数据替代(数组中一个元素的删除需要将所有该元素后续位置前移),每次删除后,数组实际长度为nums.size()-1,这里需要注意的几个点,1是在每次删除时,需将当前位置的后一位放置到当前位置上,并依次向后进行(删除的步骤),2是对一个位置删除元素后,实际的长度需要自减1。3是若用for循环,需要考虑数组的边界问题。该算法的时间复杂度为O(n^2),空间复杂度为O(1)。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int count = nums.size();
for(int i = 0; i < count;i++){
if(nums[i] == val){
for(int j = i;j < count - 1;j++){
nums[j] = nums[j + 1];
}
i--;
count--;
}
}
return count;
}
};
考虑到0<=val<=100,使用一种自认为取巧的方法,将值为val的数值用101取代,每取代一次,数组长度--,最后对数组使用STL库的排序,由于不需要考虑最后输出数组中的元素顺序,而所有的val值已经被置于后方,也实现了题目中的要求,考虑到STL库中sort在递归深度较小时为快速排序,而当超过一定阈值时改为堆排序,该算法的时间复杂度为O(nlogn),空间复杂度为O(logn)。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int i = nums.size();
for(int &x:nums)
{
if(x == val){
x = 101;
i--;}
}
sort(nums.begin(),nums.end());
return i;
}
};
再考虑双指针法,定义slow和fast两个指针,slow指针代表当前键入的数组位置,也是最后输出的数组无val值的长度,fast指针用于遍历整个数组查找需要放进新数组中的元素,也是while循环的判断条件,当fast == num.size()时,即遍历结束,循环终止,fast和slow都从0开始,当nums[fast] == val时,fast跳过当前元素(自加一),向数组的下一个元素寻找,当nums[fast]!=val时,将nums[fast]的值赋给nums[slow],同时,slow和fast都自加一,fast寻找下一个位置,slow也指向下一个应键入的数组位置,由于数组从0开始,slow的值也为无val数组的长度。
该算法的时间复杂度为O(n),空间复杂度为O(1)。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0,fast = 0;
while(fast<nums.size()){
if(nums[fast] != val){
nums[slow++] = nums[fast++];
}
else{
fast++;
}
}
return slow;
}
};
1554

被折叠的 条评论
为什么被折叠?



