704.二分查找
- 初步代码
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = nums.size() - 1;
int i = n;
while (i >= 0) {
if (nums[i] < target) {
i = (i + n) / 2;
} else if (nums[i] > target) {
i = (i + 1) / 2;
} else {
return i;
}
}
return -1;
}
};
- 修改后代码
class Solution {
public:
int search(vector<int>& nums, int target)
{
int n=nums.size()-1;
int i=0;
int j=n;
while(i<=j)
{
int left=i;
int right=j;
int mid=(left+(right-left)/2);
if(nums[mid]<target)
{
i=mid+1;
}
else
if(nums[mid]>target)
{
j=mid-1;
}
else
{
return mid;
}
}
return -1;
}
};
- 我的问题
- 首先是while语句后面加不加逗号的问题
- 其次是二分法边界的问题
- 为什么mid取中值,用mid=L+(R-L)/2而不是mid=(L+R)/2
- 代码随想录算法
- 二分查找
- 二分法前提条件
- 数组为有序数组
- 数组中无重复元素
- 循环不变量规则:二分法区间定义就是不变量,每一次边界的处理都要坚持根据区间的定义来操作
- 区间定义
- 左闭右闭
- 循环条件:while(left<=right),因为闭区间中两个相等是有意义的(即是可以选到值的)
- 选择条件:if(nums[mid]>target),那么right=mid-1。因为nums(mid)已经被遍历过了,一定不是target,那么就要把范围缩小到mid-1。
- 左闭右开
- 循环条件:while(left<right),因为开区间是不可能大于和小于的是同一个值得。然后再在开区间里面寻找的时候
- 选择条件:if(nums[mid]>target)那么就让right=mid,因为右开,而mid被遍历过,而mid-1没有被遍历过。
- 左闭右闭
- 二分法前提条件
- 二分查找
- 时间复杂度计算
- 假设一个数组长度为n,
- 第一次查找后数组长度变成n/2;
- 第二次查找后数组长度变成n/2^2
- 第k次查找后数组长度变成n/2^k
- 最坏情况下,数组长度变成1
- n/2^k'=1=>k'=log2(n)。即查找n次
- 假设一个数组长度为n,
- 问题回顾
- while语句后面加逗号的话,while循环就会陷入死循环
- 好了,现在第二个问题可以回答了,二分法划分是都可以的,关键是你要把所有的数据都包括进去,这里面要注意全部遍历,mid的赋值,然后循环条件中=是否要加的这两个问题。
- 为什么mid取中值的原因是因为溢出
- 新的底层问题
- 计算机溢出原理
- 代码问题
- vector <int>&nums 创建指向int类型的指针
vector<int>& nums 是一个函数参数,它表示一个传入的整数向量(也就是动态数组)的引用。
让我来解释一下:
- vector<int>:这是 C++ 标准库中的一个容器,用于存储整数类型的数据。它类似于数组,但具有更多的功能和灵活性。
- &:这是引用运算符,表示传递的是变量的引用,而不是变量的副本。通过引用传递可以避免复制大量数据,提高性能。
- nums:这是变量的名称,通常用于表示传入的整数向量。
- 代码语法问题
- 变量的引用&,为什么是引用呢,感觉好奇怪的样子。
- 容器是什么
- &传递变量的引用在哪一章
- 动态数组在哪一章
27.移除元素
- 看到这道题的时候我的思路
- 首先对数组所有元素排序
- CHATGPT:对数组所有元素排序: 这一步骤实际上是不必要的,题目并没有要求对数组进行排序,而且排序会带来额外的时间复杂度。题目只是要求移除特定元素,并不需要改变其他元素的顺序。
- 我的理解:就是题目没有要求排序仅仅要求移除特定元素就不用排序。因为排序算法的时间复杂度是nlogn.特别是在移动元素的情况下。可能在某些情况下,先排序可能会让后续的算法更高效,因为排序后可以利用有序数组的性质来进行更快的操作。但是在这个特定问题中,双指针法可能更直接高效。综合考虑,直接解决问题的方法往往更为优雅和高效。
- 用二分法找到VAL
- CHATGPT:用二分法找到VAL: 二分法在有序数组中查找特定元素是一个不错的方法,但前提是数组必须是有序的。在这个题目中,并没有要求数组有序,所以使用二分法查找并不适用。
- 前面否定了排序的必要性,二分法要求数组有序,所以这里也不能用二分法了
- 删除val,val后面元素往前移
- CHATGPT:删除 val,val 后面元素往前移: 删除元素并将后面的元素往前移动确实是一种处理方法,但这样的操作需要移动数组中的元素,导致时间复杂度较高。题目要求的是移除特定元素,可以通过更高效的方式来实现。
- 检查元素是否等于val
- CHATGPT:检查元素是否等于 val 并进行操作: 这一步骤在实际操作中可能会导致逻辑复杂化。如果你在遍历中不断检查元素是否等于 val 并进行相应操作,可能会增加代码的复杂性,降低代码的可读性。
- 如果不等于,则指针往前移动,检查元素是否等于val
- 如果等于,统计数组长度
- 首先对数组所有元素排序
- 双指针算法:
-
在移除数组元素这道题中,更简洁且高效的方法是使用双指针法。具体做法是,用一个指针遍历数组,另一个指针指向当前有效元素应该放置的位置。如果当前遍历的元素不等于目标值,就将它放在第二个指针的位置,然后两个指针都向前移动。如果当前遍历的元素等于目标值,就只移动第一个指针。最终,第二个指针之前的元素都是有效的,可以得到最终的数组长度。
错误代码
-
class Solution { public: int removeElement(vector<int>& nums, int val) { int i=0; for(int j=0,j<nums.size,j++) //错误1:for语句中,分号;用来隔断初始化条件,循环语句,和迭代语句。 //错误2:nums.size()是函数,用来得到nums数组的长度 { if(nums[j]!=val) //错误3:if条件语句后面的语句应该用花括号括起来,这样保证它们一起在满足选择条件时一起被执行。 nums[i]=nums[j]; i++; } return i; } };
代码随想录
-
- 元素在内存地址中式连续的,不能单独删除数组中的某个元素,只能覆盖。
- 双指针法(快慢指针法):通过一个快指针和一个慢指针在一个for循环下完成两个for循环的工作
- 快指针(遍历):寻找新数组的元素,新数组就是不包含目标元素的数组。
- 慢指针:指向更新新数组(有效数组)下标的位置
- 时间复杂度:O(n)
只有一个循环体,循环迭代次数为nums.size(),也就是n。所以是O(n)
疑问:那这个题目for循环的循环体里面有三条语句if(nums[j]!=val) { nums[i]=nums[j]; i++; }。为什么时间复杂度不是O(3n)呢
Chatgpt:因为在大O符号表示法中,我们通常只关注主导项,而忽略常数项,所以O(3N)和O(N)实际上是等价的,因为它们都表示线性时间复杂度。当我们分析算法的时间复杂度时,常数因子是不重要的,因为它不会随着问题规模的增长,算法的性能如何扩展,在大规模数据下,o(3n)和O(n)的性能几乎忽略不计。因此通常只关注主要的操作数量随输入规模的增长情况。
-
空间复杂度是衡量算法在执行过程中所需要的额外内存空间的度量。对于移除数组元素的问题,你提供的双指针法确实是 O(1) 的空间复杂度。
-
这是因为你的算法并没有使用额外的数据结构来存储元素,而是直接在原始数组上进行操作。算法只需要几个额外的变量,比如指针 i 和 j 以及可能的临时变量。这些变量在算法的执行过程中只占用常数级别的额外空间,不会随着输入规模的增加而改变。
无论输入数组的大小如何,算法所需的额外内存空间都保持不变,因此空间复杂度是 O(1),即常数级别的空间使用。这就是为什么你的双指针法的空间复杂度为 O(1)。