数组是存放在连续内存空间上的相同类型数据的集合。
- 数组下标都是从0开始的。
- 数组内存空间的地址是连续的。
正因为数组在内存空间的地址是连续的,所以在删除或者增加元素时,就难免要移动其他元素的位置。
不同编程语言的内存管理不一样。以 C++ 为例,在 C++ 中二维数组是连续分布的。
void test_arr() {
int array[2][3] = {
{0, 1, 2},
{3, 4, 5}
};
cout << &array[0][0] << " " << &array[0][1] << " " << &array[0][2] << endl;
cout << &array[1][0] << " " << &array[1][1] << " " << &array[1][2] << endl;
}
int main() {
test_arr();
}
// 0x7ffee4065820 0x7ffee4065824 0x7ffee4065828
// 0x7ffee406582c 0x7ffee4065830 0x7ffee4065834
可以看出二维数组地址是连续的。
像 Java 是没有指针的,同时也不对程序员暴露其元素的地址,寻址操作完全交给虚拟机。 因此看不到每个元素的地址情况。
public static void test_arr() {
int[][] arr = {{1, 2, 3}, {3, 4, 5}, {6, 7, 8}, {9,9,9}};
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
System.out.println(arr[3]);
}
// [I@7852e922
// [I@4e25154f
// [I@70dea4e
// [I@5c647e05
这里输出的不是真正的地址,而是经过处理的数值。 从这里也可以看出,二维数组的每一行头结点的地址是没有规则的,更谈不上连续。
区间定义可以分为两种:
- 左闭右闭:
$[left, right]$ - 左闭右开:
$[left, right)$
middle
可以取值 right
,因此 left == right
是有意义的,取
while (left <= right)
。
middle
不可以取值 right
,因此 left == right
在区间
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) / 2);
if (nums[middle] > target) {
right = middle; // target 在左区间,右边界是开边界
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,左边界是闭边界
} else {
return middle;
}
}
return -1;
}
};
- 快指针:寻找新数组的元素,即不含有目标元素的数组
- 慢指针:指向更新新数组下标的位置
// 时间复杂度:O(n)
// 空间复杂度:O(1)
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;
}
};
基于元素顺序可以改变的题目描述改变了元素相对位置,确保移动最少元素。
/**
* 时间复杂度: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一定指向了最终数组末尾的下一个元素
}
};
- 时间复杂度:
$O(n)$
滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调整子序列的起始位置。
从而将