链接
文章
https://programmercarl.com/%E6%95%B0%E7%BB%84%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html
题目
https://leetcode.com/problems/binary-search/
https://leetcode.com/problems/remove-element/
知识
数组理论基础
数组是存放在连续内存空间上的相同类型数据的集合。
注意:
数组下标都是从0开始的
数组内存空间的地址是连续的
正是因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址
C++中,要注意vector和array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组
数组的元素是不能删的,只能覆盖
C++中二维数组在地址空间上是连续的(在现代系统上,二维数组中的每个int占用4个字节)
Java是没有指针的,同时也不对程序员暴露其元素的地址,寻址操作完全交给虚拟机。输出的值不是真正的地址,而是经过处理的数值
时间复杂度
时间复杂度是一个函数,它定性描述该算法的运行时间。
大O用来表示上界的,当用它作为算法的最坏情况运行时间的上界,就是对任意数据输入的运行时间的上界。有时业界也默认O代表的就是一般情况,而不是严格的上界。面试中说道算法的时间复杂度是多少指的都是一般情况。
数据用例的不一样,时间复杂度也是不同的。
我们说的时间复杂度都是省略常数项系数的,是因为一般情况下都是默认数据规模足够的大。
我们统一说 logn,也就是忽略底数的描述。
空间复杂度
是对一个算法在运行过程中占用内存空间大小的量度。
来看一下例子,什么时候的空间复杂度是$O(1)$呢,C++代码如下:1
2
3
4int j = 0;
for (int i = 0; i < n; i++) {
j++;
}
第一段代码可以看出,随着n的变化,所需开辟的内存空间并不会随着n的变化而变化。即此算法空间复杂度为一个常量,所以表示为大O(1)。
当消耗空间和输入参数n保持线性增长,这样的空间复杂度为O(n),来看一下这段C++代码1
2
3
4int* a = new int(n);
for (int i = 0; i < n; i++) {
a[i] = i;
}
随着n的增大,开辟的内存大小呈线性增长,即 O(n)。
递归的时候,会出现空间复杂度为logn的情况。
思路
第一想法
Leetcode 704 二分查找
这题应该是整数二分,虽然我在yxc的算法基础课里学过这题,但时隔几个月我已经彻底忘了(不管是原理还是实现),从头开始吧。
Leetcode 27 移除元素
试试暴力做法吧,双指针做法想不出来。根据yxc的经验,暴力做法成功后再想办法去优化。
看完代码随想录后的想法
Leetcode 704 二分查找
二分法的使用前提:数组为有序数组,且数组中无重复元素。满足这两个性质的题目可尝试二分法。
二分法中区间的定义有两种:左闭右闭和左闭右开。每一次边界的处理都要坚持根据区间的定义来操作。
Leetcode 27 移除元素
暴力做法:遍历数组->找到需要移除的元素->将该元素后面的所有元素都前移一位->索引前移一位,数组长度减1
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
实现
Leetcode 704 二分查找
cpp中的vector中求数组的长度没有len函数,应该用size函数。
cpp中两个整数的做除法如果得到的结果变量类型为整数,则是向下取整的。
cpp中的vector是动态数组,要先向用push_back向其中添加元素,然后才能通过索引来访问元素。
左闭右闭和左闭右开的写法中,需要注意right初始值的选取的不同(由于一种写法的右边界可以取到,另一种写法的右边界取不到)。还需要注意分成三类讨论,即target > nums[mid], target < nums[mid]和target == nums[mid]。这样就可以避免处理大于等于和小于等于的情况。
返回总是返回mid,不要尝试返回l或者r,可能会遇到边界问题。
用(l + r) >> 1比(l + r) / 2要更快一点。
建议采用l + (r - l) / 2代替(r + l) / 2,前者可以防止(r + l)溢出整数的范围。
时间复杂度:O(log n)
空间复杂度:O(1)
1 | // 左闭右闭写法 |
1 | // 左闭右开写法 |
Leetcode 27 移除元素
暴力做法
若不前移i,则若数组中出现连续的两个val时,结果会发生错误,不能完全移除数组中所有的val。
1 | class Solution { |
时间复杂度:O(n^2)
空间复杂度:O(1)
快慢双指针做法
1 | class Solution { |
时间复杂度:O(n)
空间复杂度:O(1)
相向双指针做法
1 | // 相向双指针方法 |
时间复杂度:O(n)
空间复杂度:O(1)
注意:while (l <= r && nums[l] != val)和while (l <= r && nums[r] == val)中的两个判断条件不可以写反,否则会出现Runtime Error。这是因为短路原则,最好先进行边界检查,再访问数组。
逻辑:数组的左边放等于val的元素,因此需要跳过所有不等于val的元素;数组的右边放不等于val的元素,因此需要跳过所有等于val的元素。交换数组的左右两边,让数组的左边放不等于val的元素,数组的右边放等于val的元素,然后输出数组左边的最后一个值的索引。
相向双指针方法的基本过程我大致理解了,但还不理解其的细节和应用。
心得与备忘录
Leetcode 704 二分查找
我认为代码随想录的做法比yxc的讲解更加清晰。清楚地归纳总结出了左闭右闭和左闭右开的写法,并根据选择区间的开闭性质清晰地写出了代码。同时,分成三类讨论,避免了处理大于等于和小于等于的情况。
还没有做35和34,等到二刷来做。
Leetcode 27 移除元素
相向双指针方法的理解有待加深。