赞
踩
非科班学习算法day1 | LeetCode704: 二分查找,Leetcode27: 移除元素
文章目录
包含LC的两道题目,还有相应概念的补充。
相关图解和更多版本:
代码随想录 (programmercarl.com)https://programmercarl.com/#%E6%9C%AC%E7%AB%99%E8%83%8C%E6%99%AF
1)一个数组中所有元素类型相同
2)数组在内存中的地址是连续存放的,所以删除数组中间的某一个元素,就要将后面的元素都向前移动去覆盖。
1)vector是STL中的一种容器,vector可以理解为单端数组,只在vector的尾部提供一个接口
2)vector支持随机访问的迭代器
3)vector支持动态拓展,所以相对于数组更优秀
4)vector支持查询size和capacticy
5)vector支持用 [ ] 或者at查询
时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数. 时间复杂度常用大O表述,不包括这个函数的低阶项和首项系数。(参考高等数学无穷小的概念就好)
几种常见的时间复杂度:
常数阶O(1)
对数阶O(logN)
线性阶O(n)
线性对数阶O(nlogN)
平方阶O(n^2)
立方阶O(n^3)
K次方阶O(n^k)
指数阶(2^n)
基本操作产生的时间复杂度为常数阶,多个顺序结构的复杂度按照加法计算,多个嵌套循环的复杂度按照乘法计算,一般在计算时间复杂度时,按照最坏时间复杂度进行计算。
题目链接:. - 力扣(LeetCode)
题目解析
作为一个没有学过算法,基础也薄弱的人(我),自然会想到可以遍历vector,检查数组元素是否等于被查找值。若相等,返回当前下标;否则直至循环结束,在循环体外设置指定返回值 :-1
C++代码如下:
- class Solution {
- public:
- int search(vector<int>& nums, int target) {
-
- //循环遍历查找target
- for (int i = 0;i < nums.size();i++)
- {
- if(nums[i] == target)
- {
- return i;
- }
- }
- return -1;
- }
- };
时间复杂度分析:进行了双层的循环嵌套,按照最坏复杂度为O(n^2)
优化思路
借助于二分查找可以有效降低查找次数(对数阶O(logN)),但是二分查找代码实现阶段最重要的就是边界设置,这里采用的是左闭右开的区间设置。
有两个值要额外注意,首先是右边界--决定了循环条件的设置(while里面可否取等于号),其次是mid,调整区间之后(二分比对之后),要注意之后的区间是否包含了右边界,在整个循环过程中,要坚持一个不变量原则,就是区间形式不能变(左闭右开/左闭右闭)。
调整之后C++代码如下:
- class Solution {
- public:
- int search(vector<int>& nums, int target) {
-
- //定义循环不变量为左闭右开
- int left = 0;
- int right = nums.size(); //这里右端不包含,所以可以取数组长度
-
- //定义循环条件,进入循环
- //循环条件为单向不包含等号,因为左右区间端点必定不可以相等
- while(left < right)
- {
- //定义和target比较的中值的下标
- int mid = left + (right - left) / 2;
-
- //进入条件判断
- //中值偏大
- if(target < nums[mid])
- {
- //右区间减小
- right = mid; //不需要调整,因为右区间端点不包含
- }
- //中值偏小
- else if(target > nums[mid])
- {
- left = mid + 1; //需要调整,左区间为包含
- }
- //中值匹配,返回中值下标
- else
- {
- return mid;
- }
- }
-
- //不符合循环条件,返回-1
- return -1;
- }
- };

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

时间复杂度分析:按照最坏复杂度为O(log n)
题目链接:. - 力扣(LeetCode)
题目解析
在数组基础中描述过,数组是不可以直接删除中间元素的,必须要对后面的元素也进行操作。所以这里就借用基本的思路:遍历数组,寻找目标值,将目标值后的所有元素遍历,向前覆盖。
这种算法实现起来并不困难,需要注意的是:
在后面的元素移动之后,我们要再次检查此处的元素是否是目标值,所以外层采用了while循环,然后控制了i的自加条件。
C++代码如下:
- class Solution {
- public:
- int removeElement(vector<int>& nums, int val) {
- //遍历数组,寻找nums中符合val的值,删除该值
- //遍历该值后的部分,依据数组内存规则,将其向前覆盖
-
- int size = nums.size();
- int i = 0;
-
- //这里采用外层while
- while(i < size)
- {
- if(nums[i] == val)
- {
- for(int j = i + 1; j < size; j++)
- {
- //实行覆盖
- nums[j-1] = nums[j];
- }
- //调整数组长度,相对减少一点遍历
- size--;
- }
- //这里才调整i,因为覆盖之后,要检查原位置的元素
- else
- {
- i++;
- }
- }
- return size;
- }
- };

时间复杂度分析:按照最坏复杂度为O(n^2)
优化思路
借助于双指针,这里采用的是快慢指针。主要的想法是:
快慢指针同时指向数组的头部;
移动快指针对数组进行遍历;
比对快指针指向的结果和目标值;
慢指针更新数组信息
我的理解叫做慢指针就是因为,可能存在需要舍弃的值,这里的“慢”是相对快指针来说的,由于慢指针记录了新的数组的信息,所以最后返回慢指针也就代表了有效的数组长度(k),关于详细的图解可以参考:
调整之后C++代码如下:
- class Solution {
- public:
- int removeElement(vector<int>& nums, int val) {
- //采用快慢指针的方法来解决
- //定义快指针来遍历数组
- int fast = 0;
-
- //定义慢指针调整数组
- int slow = 0;
-
- //移动快指针
- for(;fast < nums.size(); fast++)
- {
- if(nums[fast] != val)
- {
- //不等则不变
- nums[slow] = nums[fast];
- slow++; //调整慢指针的位置
- }
- }
-
- //返回有效数组长度
- return slow;
- }
- };

时间复杂度分析:按照最坏复杂度为O(n)
打卡第一天也是养成写博客习惯的第一天,坚持!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。