代码随想录算法训练营第一天| 704. 二分查找、27. 移除元素

第一天开始刷题,先看了视频,然后默写,有很多细节的地方出错。之后看题解前,重点关注变量和符号。

数组基础

C++中,数组的地址是连续的,包括二维数组,首地址之间差一个定义长度,比如int差4。地址是十六进制显示。(像Java是没有指针的,同时也不对程序员暴露其元素的地址,寻址操作完全交给虚拟机。所以看不到每个元素的地址情况)

数组的元素不能删除,只能覆盖。

C++中,要注意vector 和 array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。

704.二分查找:

题目链接:704.二分查找

视频链接

通过对半缩小区间来实现目标的快速查找。

思路:

先对边界值之外的进行排除,然后对之内的进行筛选

看完代随想录之后的思路:

循环通过左右区间判别来实现。

查找target通过值的大小判断来实现,判断后立即缩小区间。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left=0;
        int right=nums.size()-1;
        
        while(left<=right)
        {
            int middle=left+(right-left)/2;
            if(target<nums[middle])
                right=middle-1;
            else if(target>nums[middle])
                left=middle+1;
            else
                return middle;
        }
        return -1;
    }
};

遇到的困难:

  1. 语法不熟,size方法的括号没加上,数组的序号用成了方法点上去了
  2. 变量的变化不敏感,将middle放在了循环外,这样区间就没法变化了;middle应该是左区间加上一半的区间长度,但是却定义成了左加右除以2,这样不变化
  3. left只是下标值,不是地址,不能写nums.0
  4. 查找时是用target和middle比较,不是区间边界和middle比较

收获:

第一次刷题,做完、找到错误、执行正确,有了开头,继续坚持下去。

最原始办法的用时和资源消耗不是最顶级,二刷时再看看别的题解。

middle的初始值不能写成(left+right)/2,因为如果right是int最大值,加起来直接就溢出了,所以一定是用left+(right-left)/2。也可以写成((r - l) >> 1) + l

迭代时保证不重不漏。如果left=middle,不是middle+1,那么当target在范围之外时,循环永远出不去。因为到最后right=left+1,根据计算机除2必舍的原则,middle永远等于left,之后一直在这一步卡死。所以迭代时一定保证不重不漏,搜过的一定不能再在下一次的范围中。

重点:

区间开闭的影响

变量变化的影响(是否在循环内、谁去比较)

27.移除元素:

在一个数组中找到目标元素,并将元素剔除出这个数组。

题目链接:27 移除元素

视频讲解

思路

直接删除

看完随想录之后的思路

暴力解法:两个循环,第一个循环查找与target相同的元素,第二个循环将该元素后的所有元素向前移位

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--;
                size--;
            }           
        }
        return size;
    }
};

双指针解法:只用一个循环,当遇到不同的元素,快指针和慢指针同步更新地址;当遇到相同的元素时,快指针更新,慢指针不变。最后慢指针的数据即为更新数据

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;
    }
};

左右双指针解法:两层循环,外层循环保证内层覆盖一次结束后,继续下一次覆盖任务。内层左循环查找与target相同的元素,内层右循环查找不等于target的元素,然后将右边非剔除值塞到左边剔除值的空位上。最后输出左边序列的下标值就是新数组的长度。

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一定指向了最终数组末尾的下一个元素
    }
};

遇到的困难

1. for循环的第一个变量需要定义:

for(int i=0;;)

2. 数组中下标的自增运算:

第一种:正常定义的数组,其数组名存储的是数组第一个元素的地址,是一个常量指针,赋值或者自增自减运算不合法;

第二种,把一个定义好的数组作为函数的形参时,此时数组名在编译时就会被当作指针变量来处理(形参肯定是一个指针变量,只有指针变量才能存放地址),此时这个形参的赋值或者自增自减运算是合法的;

3. 自增代表的意义模糊不清:

p++:运算规则:先取 p 的值作为表达式的值,然后将 p 后移指向下一个内存单元。

在这里插入图片描述

++p:运算规则:将 p 后移指向下一个内存单元,然后取 p 的值作为表达式的值。

在这里插入图片描述

这里可能看不出来有什么区别,因为是单独作为语句,放在一起输出对比一下先后++的顺序就知道了

在这里插入图片描述

收获

for循环的定义

变量前后自增的效果

重点

数组不能删,只能移位

双指针减少时间复杂度的巧妙思路

指针的自增运算,输出长度得到数组

额外拓展

1. 移位

二分的中间界限写法:

1> mid = l + (r - l) / 2

避免溢出

        mid=(l+r)/2中,(l+r)可能会溢出

确保正负数取整方向一致,正负数都是向下取整而不是向零取整

  • 区间[2, 5]的中点求下界是mid = (2 + 5) / 2 = 3;mid = 2 + (5 - 2) / 2 = 3
  • 区间[-5, 2]的中点求下界是mid = (-5 + 2) / 2 = -1(有问题,正负数取整方向不一致);mid = -5+(2 -(-5)) / 2 = -2

2> mid = (l + r) >> 1

向右移位约等于除以二

  • -5 / 2 = -(int)2.5 = -2,这里是把绝对值变小了,加个负号,结果就变大了。
  • -5 >> 1 = (1011) >> 1 = (1101) = -3,假设用4-bit表示一个整数,补码表示。发现结果变小了。
  • -5 / 2 = -25 / 2 = 2。这表明除二是向零取整
  • -5 >> 1 = -35 >> 1 = 2。这表明右移一位是向下取整

2. C++函数:输出数组长度,得到数组

  • 数组不用返回:函数的形参使用了引用,类似于指针的效果,直接在内存上对变量进行修改。外部调用变量时,直接从内存拿值,拿到的值就是修改过的。
  • 调用该函数的语句类似于(从右上角playground调试中查看):len=removeElement(nums, val); cout<<nums<<endl; 所以输出数组长度,得到的是数组

3. 引用:

给变量起别名:

//数据类型 &别名 = 原名
int a = 10;
int &b = a;

引用定义时,必须使用一个变量来初始化

初始化后不能再重新用其他变量来定义

	int a = 10;
	int b = 20;
	//int &c; //错误,引用必须初始化
	int &c = a; //一旦初始化后,就不可以更改
    //int &c=b; //错误,初始化后不能用其他变量来定义
	c = b; //这是赋值操作,不是更改引用

引用在函数的形参中作用与指针相同,而且语法更加简单

//1. 值传递
void mySwap01(int a, int b) {
	int temp = a;
	a = b;
	b = temp;
}

//2. 地址传递
void mySwap02(int* a, int* b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

//3. 引用传递
void mySwap03(int& a, int& b) {
	int temp = a;
	a = b;
	b = temp;
}

int main() {

	int a = 10;
	int b = 20;

	mySwap01(a, b);
	cout << "a:" << a << " b:" << b << endl;

	mySwap02(&a, &b);
	cout << "a:" << a << " b:" << b << endl;

	mySwap03(a, b);
	cout << "a:" << a << " b:" << b << endl;

	system("pause");

	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值