专题:数组
题目:二分查找
题目要求:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。(升序意味着没有重复的元素)
题目理解:
因为题目满足 有序数组 并且 无重复元素 两个特点,所以可以对他使用二分查找。
如果不是有序数组不能使用二分查找;
如果数组有重复元素,返回的下标可能就不唯一了,所以也不能使用二分查找;
大体思想:
用左下标left指向数组的第一个元素 ,右下标right指向数组的最后一个元素。在左下标 小于 右小标的前提下,循环进行下面的操作。
取当前左下标 和 右下标 的中间下标对应的值 和 目标值比较,如果目标值小于中间下标对应的值,那么说明目标值在当前左右下标区间的左半部分,此时我们更新右下标,让右下标指向中间下标。然后左右下标区间就缩小了一半,下一次查找的范围也缩小了一半。
取当前左下标 和 右下标 的中间下标的值 和 目标值比较,如果目标值大于中间下标值,那么说明目标值在当前左右下标区间的右半部分,此时我们更新左下标,让左下标指向中间下标。然后左右下标区间就缩小了一半,下一次查找的范围也缩小了一半。
取当前左下标 和 右下标 的中间下标的值 和 目标值比较,如果目标值等于中间下标值,那么找到了目标值,直接返回中间下标。
如果直到 左右下标不满足条件,退出循环。那说明数组中没有目标值,返回-1;
细节注意
左闭右闭:nums.size()求出的是数组的有效元素的个数。而根据数组的特性,元素下标从0开始,即第一个元素的下标是0,第二个元素的下标是1...最后一个有效元素的下标是 nums.size() -1,那么nums.size()对应的元素,就是数组后面的第一个无效元素。
所以左闭右闭要求:左下标从第一个元素开始 left = 0;右下标指向最后一个有效元素 right = nums.size()-1; 由于左下标右下标指向的都是有效元素,所以在while循环时,条件为(left <= right);
在更新左边界的时候,由于原来的中间指针指向的元素,已经参加了比较,所以下一个比较可以直接越过它。并且不管是左下标指向中间下标的后一个元素,还是右下标指向中间下标的前一个元素,左右下标都可以拿到这个值,参与到这次的循环比较中来。不会遗漏。所以,更新左下标的时候 left = mid+1; 更新右下标的时候 right = mid -1;
左闭右开:nums.size()求出的是数组的有效元素的个数。而根据数组的特性,元素下标从0开始,即第一个元素的下标是0,第二个元素的下标是1...最后一个有效元素的下标是 nums.size() -1,那么nums.size()对应的元素,就是数组后的第一个无效元素。
所以左闭右开要求:左下标从第一个元素开始 left = 0;右下标指向数组后第一个无效元素 right = nums.size(); 由于右下标指向的是无效元素,所以在while循环时,不能取等于条件。 写为:while(left < right);
在更新左边界的时候,由于原来的中间指针指向的元素,已经参加了比较,所以下一个比较可以直接越过它。让左下标直接指向中间下标的后一个位置的下标。但是更新右下标时,因为右下标指向的空间不能访问,所以此时,就不能让右下标指向中间下标的前一个,如果指向中间下标的前一个,那么中间下标的前一个的元素,就不能参与到下一个次循环比较当中,就会造成数据丢失。所以,更新左下标的时候 left = mid+1; 更新右下标的时候 right = mid;
代码实现:
题目:移除元素
题目要求:
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
题目理解:
数组,原地移除所有数值等于val的值,返回数组新长度。 不能使用新空间,只能在原来的数组上进行操作,根据数组的特性,移除元素,不能删除而是依靠覆盖的方式。可以采用暴力移除法,或者快慢指针法去实现。
大体思想
暴力循环法:第一层for循环,遍历数组。一旦发现当前元素等于val,那么就从这个元素处,再加一次for循环,把后面的元素都往前依次移动一个位置,覆盖当前的这个值等于val的元素。这样大循环完成,那么数组中值等于val的元素都会被移除。
细节注意
暴力循环法,小循环每执行一次,覆盖一个元素,控制访问数组大小的size就要减1.还有就是我们要人为的给大的for循环的循环变量减1.因为后面的元素都往前移动了一位。如果让大循环的变量直接走的话,会把本来下次应该访问的元素,结果因为后面的元素全部往前移一位,导致错过访问。所以每次小循环结束,我们要人为的给大循环的循环变量-1;
代码实现
为啥不正确???
这是因为,我们没有控制访问数组元素的大小。
知识点补充:数组无论你是前移动数组元素,还是后移动数组元素。被移动那个数组元素还存在,并且那个数组元素的值也不会改变。只是其他元素的值被赋值改变了。并且数组的大小也没有改变。nums.size()还是和原来一样。
例如:数组[3,2,2,3],移除val=3的元素。
大循环是for(i = 0; i < 4; i++) 然后经过第一个大循环就会变成[2,2,3,3],然后i--.又变成了for(i = 0; i < 4; i++);现在遇到[2,2]之后,变成for(i = 2; i < 4; i++);遇到了nums[2] = val;然后把后面的3往前赋值一个之后得到[2,2,3,3].此时i- - 又回到了for(i = 2; i < 4; i++);又遇到了nums[2] = val;然后又是一直循环。就会出现超时现象。
我们要控制的,不是数组真正的大小nums.size(),而是去控制遍历数组的大小size。
所以使用一个size先记录下原来数组的大小,然后在移除的时候通过改变size的大小,来控制访问数组的大小。
例如:数组[3,2,2,3],移除val=3的元素。
size= 4;大循环是for(i = 0; i < 4; i++) 然后经过第一个大循环就会变成[2,2,3,3], 此时size= 3; 然后i--.又变成了for(i = 0; i < 3; i++);现在遇到[2,2]之后,变成for(i = 2; i < 3; i++);遇到了nums[2] = val; i--,size--; size=2; i=2; 现在for(i = 2; i < 2;i++)退出循环了。数组的大小size就是2。
代码实现
快慢指针法:因为可以通过下标访问数组元素,所以我们可以使用两个下标,fast,slow。让fast先走,slow跟在fast后面。fast遍历数组,如果fast当前指向的元素不等于val,那么将fast指向的元素赋给slow,并且slow往下走一步。如果fast指向的元素等于val,那么啥也不干,fast继续执行循环。这样fast的循环完成之后,slow就是新数组的元素个数,并且当前数组的前slow个,就是要得到的数组。
细节注意
赋值是注意slow是先赋值后++。即 nums[slow++] = nums[fast];
代码实现
学到的知识:深刻理解了数组元素的移动,以及数组元素的下标访问。
标准:根据自己的理解,独立写出来,并且一把运行通过。
否则:等于没学习!!!