数组理论基础
数组是存放在连续内存空间上的相同类型数据的集合。
数组可以方便的通过下标索引的方式获取到下标对应的数据。
注意:
- 因为数组在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。
- vector 和 array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。
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();
}
704.二分查找
704 二分查找
给定一个 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
提示:
你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。
第一次尝试
class Solution {
public:
int left = 0;//数组所以第一个为0
int right = nums.size()-1;//最后一个
int search(vector<int>& nums, int target) {
//注意确定左右区间开闭
//这里设置为左闭右开
if(left>right) return -1;
int mid=left+(right-left)/2;
if(target==nums[mid]) return mid;
else if(target>nums[mid]){
left=mid+1;
search(nums,target);
}
else{
right=mid;
search(nums,target);
}
}
};
报错:
Line 4: Char 17: error: use of undeclared identifier ‘nums’
4 | int right = nums.size()-1;//最后一个
| ^
1 error generated.
卡住的原因:
没有想到怎么能在第一次的时候在函数里定义左右区间,然后在接下来的循环中不执行这两个初始化定义
看完carl代码随想录,思考:
因为一定要用nums确定右区间,所以初始化一定是在子函数里的
在这个基础上,要不断循环修改左右区间:
不需要递归,直接while循环,在满足区间中还有可能有目标值的情况下,不断执行while即可
- 循环 - 递归/while循环
第二次尝试
class Solution {
public:
int search(vector<int>& nums, int target) {
//注意确定左右区间开闭
//这里设置为左闭右开
int left = 0;//数组所以第一个为0
int right = nums.size()-1;//最后一个
while(left<right) {
int mid=left+(right-left)/2;
if(target==nums[mid]) return mid;
else if(target>nums[mid]){left=mid+1;}
else{right=mid;}
}
if(nums.size()==1) return target==nums[0]?0:-1;
return -1;
}
};
[2,5] 输出-1
对比carl发现由于我设定的是左闭右开 而int right = nums.size()-1;//最后一个 中没有把最后一个包含进来
修改后通过了
class Solution {
public:
int search(vector<int>& nums, int target) {
//注意确定左右区间开闭
//这里设置为左闭右开
int left = 0;//数组所以第一个为0
int right = nums.size();//最后一个
while(left<right) {
int mid=left+(right-left)/2;
if(target==nums[mid]) return mid;
else if(target>nums[mid]){left=mid+1;}
else{right=mid;}
}
if(nums.size()==1) return target==nums[0]?0:-1;
return -1;
}
};
并且由于初始右区间,我多了一个判断数组长度为1的情况
这个可以注释掉 if(nums.size()1) return targetnums[0]?0:-1;
- Q:为什么在数组长度为1 且该数等于target的时候能顺利运行呢int
mid=left+(right-left)/2;这里的mid不应该是等于1
然后在下面判断if(target==nums[mid])的时候不应该数组越界了吗
为什么mid = 0 + (1-0)/2 = 0
A:因为在整数除法中,结果会向下取整 TUT
再写一个左闭右闭情况
class Solution {
public:
int search(vector<int>& nums, int target) {
//注意确定左右区间开闭
//这里设置为左闭右开
/*int left = 0;//数组所以第一个为0
int right = nums.size();//最后一个
while(left<right) {
int mid=left+(right-left)/2;
if(target==nums[mid]) return mid;
else if(target>nums[mid]){left=mid+1;}
else{right=mid;}
}
return -1;*/
int left=0;
int right=nums.size()-1;
while(left<=right){
int mid=left+(right-left)/2;
if(target==nums[mid]) return mid;
else if(target>nums[mid]) {left=mid+1;}
else{right=mid-1;}
}
return -1;
}
};
错误汇总:
- 循环 - 递归/while循环
- 左闭右开没判断好 nums.size()是最后一个元素的下一个
- 在整数除法中,结果会向下取整
27. 移除元素
27.移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。
假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:
更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
返回 k。
用户评测:
评测机将使用以下代码测试您的解决方案:
int[] nums = […]; // 输入数组
int val = …; // 要移除的值
int[] expectedNums = […]; // 长度正确的预期答案。
// 它以不等于 val 的值排序。
int k = removeElement(nums, val); // 调用你的实现
assert k == expectedNums.length;
sort(nums, 0, k); // 排序 nums 的前 k 个元素
for (int i = 0; i < actualLength; i++) {
assert nums[i] == expectedNums[i];
}
如果所有的断言都通过,你的解决方案将会 通过。
示例 1:
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,,]
解释:你的函数函数应该返回 k = 2, 并且 nums 中的前两个元素均为 2。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。
示例 2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3,,,_]
解释:你的函数应该返回 k = 5,并且 nums 中的前五个元素为 0,0,1,3,4。
注意这五个元素可以任意顺序返回。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。
提示:
0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100
第一次尝试
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int left=0;
int right=0;
int count=0;
while(right<nums.size())
{
if(right<nums.size()&&nums[right]==val) {
while(right<nums.size()&&nums[right]==val){
right++;
}
if(right<nums.size()){
int temp=nums[left];
nums[left]=nums[right];
nums[right]=temp;
left++;
count++;
}
}
else{right++;left++;}
}
return count;
}
};
想到双指针了
但是对于怎么判断循环终止 、如何终止 、怎么不越界
没想明白
(烦)
直接看carl代码代码随想录不想想了
暴力解法:一个for循环遍历数组元素 ,第二个for循环更新数组。
第一个循环找到val就移动元素覆盖 第二遍找val缩短数组长度
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
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;
}
};
感觉这里的i<size 和i–; 都挺难想而且易错(sad)
双指针法
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
定义快慢指针
快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组(这个我想的是对的)
慢指针:指向更新 新数组下标的位置(这个不太对)
很多同学这道题目做的很懵,就是不理解 快慢指针究竟都是什么含义,所以一定要明确含义,后面的思路就更容易理解了。
// 时间复杂度: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;
}
};
注意这些实现方法并没有改变元素的相对位置!(可怕)(可恶(〃>皿<))
看完这个注意也没啥思路(呜呜)
nums = [0,1,2,2,3,0,4,2], val = 2
区别:找到那个非目标的3 把前面的2覆盖 而不是交换
对于非目标的01 做一个无用功 赋给自己
对于目标的2 左边不动 等待被覆盖 右边向右走一
然后再进循环里判断新走到的这个是否是目标值 如果是 就再向右走 如果不是 就把左边的覆盖 左边向右走一
右边也走一(已经把这个非目标值存起来了 所以可以右移)
也就是左边指针左侧的都是已经判断是非目标值的
右边指针右侧的是没判断过的 左右指针中间的是目标值或者是已经保存过的非目标值
是暴力解法的一种缩减 不太理解暴力 所以双指针也很难想
好神奇。。。
感觉一辈子想不出来(悲伤)