1.数组理论基础
数组是存放在连续内存空间上的相同类型数据的集合。其中,一定要注意数组的在计算机中所占用的内存是连续的。这就导致了一个优点和一个缺点:优点是当我们需要查找数组中的某个元素时,可以直接通过下标访问,因此查找的时间复杂度较低,为O(1)。而缺点则是当我们需要插入或者删除某个元素时,需要遍历和移动整个数组,时间复杂度为O(n)。还有一点,数组中其实并没有“删除数组元素”这一说法。只有覆盖数组元素这一说法。因此,人们常说的删除数组中的某元素,就是将数组中的某个元素用其它值覆盖掉,这里一定要搞清楚!
以此类推,二维数组的内存地址也是连续的。
2.二分法查找元素
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
这道题的方法则是使用二分法,而二分法中又有很多小细节需要注意。其中,大致分为左闭右闭写法和左闭右开两种写法。无论是使用左闭右闭还是使用左闭右开来写,在更新left和right时,都需要遵循左闭右闭/左闭右开的原则,以免出现不必要的错误。
代码如下:本题的时间复杂度为O(log n)
class Solution {
public:
int search(vector<int>& nums, int target){ //总共有两种方法。左闭右闭写法和左闭右开的写法
// 方法一:左闭右闭
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] > target) {
right = mid - 1; //更新右边界
} else if (nums[mid] < target) {
left = mid + 1; //更新左边界
} else {
return mid; //如果找到,则返回
}
}
return -1;
}
};
class Solution {
public:
int search(vector<int>& nums, int target){ //总共有两种方法。左闭右闭写法和左闭右开的写法
// 方法二:左闭右开
int left = 0;
int right = nums.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > target) {
right = mid; //更新右边界,由于右边界是开区间,因此需要遵循开区间的原则,使得right = mid,而不是right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1; //更新左边界
} else {
return mid; //如果找到,则返回
}
}
return -1;
}
};
本题完整的可在本地IDE运行的代码如下:
// This is binary search algorithm
#include <iostream>
#include <vector>
using namespace std;
// class Solution {
// public:
// int search(vector<int>& nums, int target){ //总共有两种方法。左闭右闭写法和左闭右开的写法
// // 方法一:左闭右闭
// int left = 0;
// int right = nums.size() - 1;
// while (left <= right) {
// int mid = left + (right - left) / 2;
// if (nums[mid] > target) {
// right = mid - 1; //更新右边界
// } else if (nums[mid] < target) {
// left = mid + 1; //更新左边界
// } else {
// return mid; //如果找到,则返回
// }
// }
// return -1;
// }
// };
class Solution {
public:
int search(vector<int>& nums, int target){ //总共有两种方法。左闭右闭写法和左闭右开的写法
// 方法二:左闭右开
int left = 0;
int right = nums.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > target) {
right = mid; //更新右边界,由于右边界是开区间,因此需要遵循开区间的原则,使得right = mid,而不是right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1; //更新左边界
} else {
return mid; //如果找到,则返回
}
}
return -1;
}
};
int main()
{
vector<int> arr;
arr.push_back(-1);
arr.push_back(0);
arr.push_back(3);
arr.push_back(5);
arr.push_back(9);
arr.push_back(12);
Solution sol;
int target = 0;
int index = sol.search(arr, target);
cout << index << endl;
return 0;
}
3.移除元素
这道题需要额外注意遵循数组的内存连续原则。前面提到过:数组增删操作的时间复杂度至少为O(n)。因此,本题中最理想的解法则是找到一个时间复杂度为O(n)的算法。
本题有两种解法,一种为暴力解法,时间复杂度为O(n^2)。使用两次for循环找出所有需要移除的元素,并逐一替换,代码如下:
class Solution { // 方法一:暴力解法
public:
int removeElement(vector<int>& nums, int val){
int i,j;
int size = nums.size();
for (i = 0; i < size; i++) {
if (nums[i] == val) {
for (j = i; j < size - 1; j++) {
nums[j] = nums[j+1];
}
size--;
i--;
}
}
return size;
}
};
还有一种解法为双指针法,时间复杂度为O(n)。即定义fast和slow两个指针,fast指针负责一马当先,在前面遍历数组,将所有不需要移除的元素赋值给slow指针。当fast指针遍历到需要删除的元素时,则避开需要删除的元素,遍历下一个元素,直到遍历完整个数组,并将整个数组中不需要移除的值全部通过slow指针赋值给数组。代码如下:
class Solution { // 方法二:双指针
public:
int removeElement(vector<int>& nums, int val){
int fast, slow = 0; //定义快指针和慢指针
for (fast = 0; fast < nums.size(); fast++) {
if (nums[fast] != val) { //快指针在前面遍历数组,将数组中不等于val的值通过慢指针赋值
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
};
可以在本地IDE运行的完整代码如下:
#include <iostream>
#include <vector>
using namespace std;
// class Solution { // 方法一:暴力解法
// public:
// int removeElement(vector<int>& nums, int val){
// int i,j;
// int size = nums.size();
// for (i = 0; i < size; i++) {
// if (nums[i] == val) {
// for (j = i; j < size - 1; j++) {
// nums[j] = nums[j+1];
// }
// size--;
// i--;
// }
// }
// return size;
// }
// };
class Solution { // 方法二:双指针
public:
int removeElement(vector<int>& nums, int val){
int fast, slow = 0; //定义快指针和慢指针
for (fast = 0; fast < nums.size(); fast++) {
if (nums[fast] != val) { //快指针在前面遍历数组,将数组中不等于val的值通过慢指针赋值
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
};
int main(int argc, char** argv){
vector<int> nums;
nums.push_back(3);
nums.push_back(2);
nums.push_back(2);
nums.push_back(3);
nums.push_back(3);
int val = 2;
Solution sol;
int slow = sol.removeElement(nums, val);
cout << slow << endl;
return 0;
}
今毕。