目录
第一天做题,忘记了不少C++ 的语法,抽空一定要多看看书。
数组理论基础
- 数组在内存中的存储方式:数组是存放在连续内存空间上的相同类型数据的集合。
- 数组可以方便的通过下标索引的方式获取到下标下对应的数据。
- 数组下标都是从0开始的。
- 数组内存空间的地址是连续的。C++中二维数组在地址空间上亦是连续的。
- 因为数组的在内存空间的地址是连续的,所以我们在
“删除或者增添”元素的时候,就难免要移动其他元素的地址。 - vector 和 array 的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。
- 数组的元素是不能删的,只能覆盖。
- 当
“删除或增添元素”时,数组真正的内存空间不会改变,但对于一些编程语言中,对数组进行了一层包装,如C++中的vector提供了各式各样使用数组的接口,删除(erase)了一个元素后,后面的元素会自动前移,长度(size)会自动减1。
摘自:代码随想录 (programmercarl.com)-数组理论基础
704. 二分查找
题目链接:704. 二分查找
给定一个 n 个元素有序的(升序)整型数组
nums
和一个目标值target
,写一个函数搜索nums
中的target
,如果目标值存在返回下标,否则返回-1
。
题目建议
熟悉根据 左闭右开,左闭右闭 两种区间规则 写出来的二分法
使用二分法的前提:1. 数组为有序数组;2. 数组中无重复元素;
左闭右闭法 [left, right]:
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = nums.size();
int left = 0;
int right = n-1;
while(left <= right){ //设定合法区间
int mid = (left + right) / 2; //可能有溢出风险!!
if(nums[mid] > target){
right = mid - 1;
}
else if(nums[mid] < target){
left = mid + 1;
}
else{
return mid;
}
}
return -1;
}
};
左闭右开法 [left, right):
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = nums.size();
int left = 0;
int right = n;
while(left < right){ //设定合法区间
int mid = (left + right) / 2; //可能有溢出风险!!
if(nums[mid] > target){
right = mid;
}
else if(nums[mid] < target){
left = mid + 1;
}
else{
return mid;
}
}
return -1;
}
};
PS: 整数溢出与回绕
两个int型相加可能会溢出 [1]。
int mid = (left + right) / 2;
替换为
int mid = left + (right - left) / 2;
[1] 参考链接:整数溢出与回绕
#include <iostream>
int main() {
unsigned int a = 0xffffffff; //a = 4294967295;
unsigned int b = 1;
unsigned int c = 0;
c = (a + b)/2; //出现越界:c = 0;
c = a + (b - a)/2; //出现越界:c = 0;
//防止越界
c = b + (a - b)/2; // c = 2147483648;
std::cout << a << std::endl;
std::cout << c << std::endl;
return 0;
}
27. 移除元素
题目链接:27.移除元素
给你一个数组
nums
和一个值val
,你需要原地移除所有数值等于val
的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用
O(1)
额外空间并原地修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 3:(很典型的输入)
输入:nums = [1], val = 1
输出:0, nums = []
题目建议
暴力的解法,可以锻炼一下我们的代码实现能力,建议先把暴力写法写一遍。 双指针法 是本题的精髓,今日需要掌握,至于拓展题目可以先不看。
暴力求解法:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int len = nums.size();
int new_len = len;
for(int i = 0; i < new_len;){
if(nums[i] == val){
for(int j =i; j < new_len-1; ++j ){
nums[j] = nums[j+1];
}
new_len--;
}
if(nums[i] != val) i++; //把判断条件放到了程序块中
}
return new_len;
}
};
双指针法:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
// 双指针解法
int len = nums.size();
int fast = 0; //快指针,存储数组索引
int slow = 0; //慢指针,存储数组索引
for(int fast = 0; fast < len; fast++){
if(nums[fast] != val){
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
};
双向指针法:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
// 双向指针解法
int left_index = 0;
int right_index = nums.size() - 1;
while (left_index <= right_index){
while(left_index <= right_index && nums[left_index] != val){
++left_index;
}
while(left_index <= right_index && nums[right_index] == val ){
//注意:nums[right_index]的判断一定要放在后面
--right_index;
}
if(left_index < right_index){ //这一行的理解在末尾
nums[left_index] = nums[right_index];
++left_index;
--right_index;
}
}
return left_index; //一定指向最终数组末尾的下一个元素,也就是新数组的长度
}
};
/*上面while 和 if 括号中的 left_index <= right_index
或者 left_index < right_index
都是针对当 left_index = right_index 时,
如果等于 val ,则 left_index 直接+1,不用在此进入判断赋值*/
PS: while循环中注意数组的索引范围
在双向指针法力扣网站的调试中,遇到while循环中 nums[left_index] != val 的判断放到 left_index <= right_index 前后时,提交代码具有不同的效果:
正确的:
while(left_index <= right_index && nums[left_index] != val){
++left_index;
}
while(left_index <= right_index && nums[right_index] == val ){
--right_index;
}
报错的:
while(nums[left_index] != val && left_index <= right_index){
++left_index;
}
while(nums[right_index] == val && left_index <= right_index ){
--right_index;
}
针对上述语句报错:
执行出错信息:
Line 1034: Char 34: runtime error: addition of unsigned offset to 0x602000000310 overflowed to 0x60200000030c (stl_vector.h)
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/stl_vector.h:1043:34
最后执行输入:
[1]
1
最后发现因为left_index 或 right_index 做 ++ 或 -- 运算时,有超出数组索引范围 (nums().size 或-1) 的可能,在内while循环中进行判断时就会产生报错。(奇怪的时,相同的代码,在CLion中不会报错)
一般的,对于数组的索引会使用 for循环 来控制索引范围,while循环中要特别注意数组的索引范围。