数组理论基础
数组是非常基础的数据结构,它是存放在连续内存空间上的相同类型数据的集合。需要注意的两点是:
数组的下标都是从0开始
数组的内存空间的地址是连续的
在C++中,二维数组的内存空间的地址也是连续的,Java则不然。
正是因为数组的内存空间的地址是连续的,所以在删除或增添元素时,就难免要移动其他元素的地址。所以说:数组的元素是不能删的,只能覆盖。
如果使用C++的话,要注意vector 和 array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。
leetcode.704 二分查找
代码实现
// 左闭右闭
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 定义target在[left, right]区间内
while(left <= right){ // 左闭右闭区间可以取到<=
int middle = left + ((right - left) >> 1); // 防溢出
if(nums[middle] > target){
right = middle - 1; // nums[middle]不在target区间内,故取middle-1
}
else if(nums[middle] < target){
left = middle + 1;
}
else
return middle;
}
return -1;
}
};
// 左闭右开
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); // 定义target在[left, right)区间内
while(left < right){ // 左闭右开区间不能取到<=
int middle = left + ((right - left) >> 1); // 防溢出
if(nums[middle] > target){
right = middle; // nums[middle]不在target区间内,同时右边为开区间,故取middle
}
else if(nums[middle] < target){
left = middle + 1;
}
else
return middle;
}
return -1;
}
};
时间复杂度O()
空间复杂度O(1)
细节处理
如果一个整型数据经过除运算后的结果是一个浮点数,那么它不会默认向下取整。这在计算middle值时可能会发生溢出的问题,为了取到正确的 middle 值,需要使用更为精确的计算方法,例如使用位运算。
在本题中使用了右移运算符>>,它的规则是这样:
各二进制位全部右移若干位,正数高位补0,负数高位补1,低位丢弃
十进制数进行 >> 1的位运算就相当于一个除以2的十进制运算。
为了防止 (left + right) 出现溢出,同时用右移操作替代除法提升性能,所以此题中的写法如下:
(left + right) / 2 = left / 2 + right / 2
= left + right / 2 - left / 2
= left + ((right - left) / 2)
= left + ((right - left) >> 1)
下面看一下二分法的时间复杂度和空间复杂度:
时间复杂度:时间复杂度的计算并不是计算程序具体运行的时间,而是算法执行语句的次数。
假设总共有n个元素,每次查找的区间大小就是n,n/2,n/4,…,其中k就是循环的次数,
>= 1(1最坏的情况,即还剩一个元素),
令 = 1,
可得k=(以2为底,n的对数),
所以时间复杂度可以表示O()。
空间复杂度:空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。
因为变量只创建一次,所以空间复杂度为O(1)。
总结
记住 循环不变量 规则(在while寻找中每一次边界的处理都要坚持根据区间的定义来操作),区间的定义就是不变量。
leetcode.27 移除元素
代码实现
暴力解法
// 暴力解法
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--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
size--; // 此时数组的大小-1
}
}
return size;
}
};
时间复杂度O()
空间复杂度O(1)
双指针解法
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
定义快慢指针
快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
慢指针:指向更新新数组下标的位置
相当于一个取元素,一个取下标。
// 双指针法
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0; // 慢指针
for(int fast = 0; fast < nums.size(); fast++){ // 快指针
if(nums[fast] != val){
nums[slow++] = nums[fast];
}
}
return slow;
}
};
时间复杂度O(n)
空间复杂度O(1)
双指针算法是C++中erase()函数的底层原理,该库函数是一个时间复杂度为O(n)的操作