代码随想录算法训练营Day 1 | 二分:LeetCode704二分查找、35搜索插入位置、34查找元素第一个和最后一个位置 | 双指针:27移除元素、977有序数组的平方

代码随想录算法训练营Day 1 | 二分:LeetCode704二分查找、35搜索插入位置、34查找元素第一个和最后一个位置 | 双指针:27移除元素、977有序数组的平方

704.二分查找

模板题
题目链接:https://leetcode.cn/problems/binary-search/代码随想录题解: 704. 二分查找 | 代码随想录
状态:完全掌握三种模板
“一只会code的小金鱼”的分界线思路(左开右开)和卡哥的思路(左闭右开)(左闭右闭)

        这题是模板题,用卡哥的思路很快就写出来了,而且很好记忆;

        我给二分模板定了三要素进行记忆,1.左右初始化;2.while()条件;3.mid是否±1。

        以前自己写二分老是会忘记while()的条件是left<right还是left<=right?什么时候用left=middle+1还是left=middle,right=middle-1还是right=middle?初始化该用left=0还是left=-1,right=nums.size()还是right=nums.size()-1?

        理解二分的边界是最容易记忆二分的,理解方法是区间不变量原则,小金鱼的思路也可以归到区间不变量原则里。为了方便,我现在定义的数组都是从小到大顺序排序并且数组从0开始。

        区间不变量是指将二分的Left和Right当成区间,同时固定为闭区间还是开区间的不变量。简单来说举个例子:(左闭右开)Left是闭区间,Right是开区间,Left的值是可以取到的,但Right的值无法取到。首先看1.左右初始化:左闭left时数组可以取到left值,所以left=0等于原数组的下标下限,右开right取不到,所以right=nums.size()比原数组的下标上限多1个;2.while()条件:左闭left时数组可以取到left值,右开right时数组取值不到,right不能=left值,所以while(left<right);3.mid是否±1:左闭left时数组可以取值,此时mid=left+right>>1,nums[mid]<target,left更新,因为mid此时的值已经不符合target了并且是左闭left是可以取到的,所以left不取mid,而是从mid+1开始,所以左闭mid+1。同理,右开right时数组取不到right的值,nums[mid]>target,right更新,mid此时的值不符合target并且右开right的值是取不到的,所以right是可以取到mid。那左闭右闭则left=mid+1,right=mid-1;以此类推左开右开left=mid,right=mid。

        我做了个表格以此类推我的理解原则:

区间不变量左右初始化while()条件mid是否±1
左闭右开left=0 | right=nums.size()left<rightleft=mid+1
right=mid
左闭右闭left=0 | right=nums.size()-1left<=rightleft=mid+1
right=mid-1
左开右开left=-1 | right=num.size()left+1!=rightleft=mid
right=mid

        最后一行是小金鱼的模板,我记忆为两边都是开的情况,是符合我们的区间不变量原则的,同时没有左开右闭这种情况是因为日常开发中都是用的左闭的多。

        下面用左闭右开手撕模板题,模板题是默认了从小到大排列同时没有重复元素的:

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

35.搜索插入位置

题目链接:35. 搜索插入位置 - 力扣(LeetCode)
【二分查找 | 妈妈再也不怕我写错二分啦 | 五点七边二分视频补充】 https://www.bilibili.com/video/BV1fA411z7ru/?share_source=copy_web&vd_source=7a3599e572d9e56d7bbfb1f44a9b852d

        这题和上面的模板题变化的地方在于找不到target不是输出-1了,而是插入到原数组去;

我这里使用左开右开来写,使用小金鱼的分界线理论,可以看小金鱼的视频当作卡哥的模板的衍生;我的个人详细理解在下一道题。

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

34.在排序数组中查找元素出现第一次的位置和最后一次的位置

题目链接:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

思路:【二分查找 | 妈妈再也不怕我写错二分啦 | 五点七边二分视频补充】 https://www.bilibili.com/video/BV1fA411z7ru/?share_source=copy_web&vd_source=7a3599e572d9e56d7bbfb1f44a9b852d

        我的个人思路,把mid当成分界线,左边left都是小于target,右边right都是大于等于target的,所以分界线右边第一个元素就是target第一次出现的位置,即此时right的下标;

        同理当我的左边都是小于等于target的数,右边都是大于target的数,那分界线左边第一个元素就是target最后一次出现的位置,即此时left的下标;

手撕代码如下,用的左开右开:

class Solution { public: vector<int> searchRange(vector<int>& nums, int target) { if(nums.empty())return{-1,-1};//当数组是空的时候直接结束输出-1,-1 int l1,r1,l2,r2; int ans1,ans2; l1=-1;l2=-1;r1=nums.size();r2=nums.size(); while(l1+1!=r1) { int mid=l1+(r1-l1)/2; if(nums[mid]>=target)r1=mid; else l1=mid; } if(r1<nums.size()&&nums[r1]==target)ans1= r1; else return{-1,-1}; while(l2!=r2-1) { int mid=l2+(r2-l2)/2; if(nums[mid]>target)r2=mid; else l2=mid; } if(nums[l2]==target)ans2=l2; // else ans2= -1; 这行可写可不写,因为第一个出现位置找不到就肯定找不到最后一个出现位置 return {ans1,ans2}; } };

27.移除元素

题目链接:https://leetcode.cn/problems/remove-element/

代码随想录:27. 移除元素 | 代码随想录

思路:双指针(快、慢指针)

        这题在力扣是可以暴力写的,但时间复杂度是O(n*n);

        用双指针可以只用一层for循环,时间复杂度到O(n);

        由于数组在内存上是一段连续的地址,所以是不存在删除的,只能用后面的元素覆盖掉实现“删除”。所以我们可以用两个指针进行更新数组,快指针fast用于指向不删除的元素,慢指针slow指向我们的新数组,将fast指向的值给slow即可完成更新。具体代码如下:

class Solution { public: int removeElement(vector<int>& nums, int val) { int size = nums.size(); for(int fast=0,slow=0;fast<nums.size();fast++) { if(nums[fast]!=val) { nums[slow++]=nums[fast]; } else size--; } return size; } };

Tips:力扣上的超过百分之多少的人当玩具就行了,多交几次一样的代码也可超过百分百;

977.有序数组的平方

题目链接:977. 有序数组的平方 - 力扣(LeetCode)

代码随想录题解:977.有序数组的平方 | 代码随想录

思路:双指针(头、尾指针)

        暴力写法可以直接全部平方之后再sort排序,但因为sort复杂度是O(nlogn)的,还能再优化。

        用空间换时间,再开一个新数组,因为存在负数的平方是大于正数的,而且原数组是有序排列的,所以可以头尾各设计一个指针,平方值大的更新在新数组的末尾,这样只用一次循环,代码时间复杂度O(n),代码如下:

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

太忙了,第二天才完成第一天的内容,二分还是很建议大家琢磨透再写的,很重要的算法。

Read more

【C++】C++异常

【C++】C++异常

🎬 个人主页:MSTcheng · ZEEKLOG 🌱 代码仓库 :MSTcheng · Gitee 🔥 精选专栏: 《C语言》 《数据结构》 《算法学习》 《C++由浅入深》 💬座右铭:路虽远行则将至,事虽难做则必成! 在前面的文章中,我们已经介绍了C++11的一些新特性。本文将和下一篇一起为大家讲解C++的最后两个重要主题:异常处理和智能指针。 文章目录 * 一、异常的概念及使用 * 1.1异常的概念 * 1.2异常的分类 * 1.3异常的抛出与捕获 * 1.4栈展开 * 1.5 查找匹配的处理代码 * 1.6异常重新抛出 * 1.7异常的安全问题 * 1.8异常规范 * 二、总结 一、异常的概念及使用 1.1异常的概念 异常(Exception)是指在程序执行过程中发生的意外或错误情况,

By Ne0inhk
【 C++ 入门】Cyber骇客的 流式文本序列处理器 —— 【 string 类】万字大文带你从0学好C++的string类!

【 C++ 入门】Cyber骇客的 流式文本序列处理器 —— 【 string 类】万字大文带你从0学好C++的string类!

⚡ CYBER_PROFILE ⚡ /// SYSTEM READY /// [WARNING]: DETECTING HIGH ENERGY 🌊 🌉 🌊 心手合一 · 水到渠成 >>> ACCESS TERMINAL <<<[ 🦾 作者主页 ][ 🔥 C语言核心 ][ 💾 编程百度 ][ 📡 代码仓库 ] --------------------------------------- Running Process: 100% | Latency: 0ms 索引与导读 * 一、为什么学习 string类 ? * 二、C++ 标准库中的 string 类 * 2.1)auto和范围for * 2.2)string类的常用接口 * 🚩1)string类的常用构造 * 🚩2)string类对象的容量操作 * ❗注意事项 * 1)size(

By Ne0inhk
【算法通关指南:数据结构和算法篇】算法里的 “排队系统”:队列的数组模拟 + STL queue 实战

【算法通关指南:数据结构和算法篇】算法里的 “排队系统”:队列的数组模拟 + STL queue 实战

🔥小龙报:个人主页 🎬作者简介:C++研发,嵌入式,机器人方向学习者 ❄️个人专栏:《算法通关指南》 ✨ 永远相信美好的事情即将发生 文章目录 * 前言 * 一、队列的概念 * 二、队列的模拟实现 * 2.1创建 * 2.2 入队 * 2.3出队 * 2.4队头 * 2.5队尾 * 2.6判空 * 2.7有效元素个数 * 2.8 所有测试代码 * 三、queue * 3.1 如何创建 * 3.2容器相关接口 * 3.2.1 size / empty * 3.2.2 push

By Ne0inhk
华为OD技术面八股文真题_C++_3

华为OD技术面八股文真题_C++_3

文章目录 * 变量的声明和定义的区别 * 内存泄露是什么意思?怎么避免内存泄露 * 怎么排查内存泄漏,遇到内存泄漏情况,一般怎么解决 * 说一下define和const的区别 * define和typedef的区别 * 宏函数和内联函数的区别 * 类和结构体的区别 * 结构体(struct)和联合体(union)差别 * 静态库和动态库区别 * 介绍一下C++的编译过程 变量的声明和定义的区别 * 变量的声明是告诉编译器变量的名称和类型,不分配存储空间; * 变量的定义会为变量分配存储空间并建立实体。 * 一个变量可以在多个地方声明,但只能在一个地方定义。 使用 extern 修饰的变量通常是声明,表示该变量在其它文件中定义,但 如果 extern 变量带初始化,则该语句仍然属于定义。 内存泄露是什么意思?怎么避免内存泄露 内存泄漏是指程序在动态申请内存后,后续失去对该内存的控制,导致这块内存无法被释放,从而造成内存资源浪费的现象。内存被申请了,却释放不了。 内存泄漏的危害如下: 1. 程序内存占用不断增大,导致系统可用内存减少,性能下

By Ne0inhk