数据结构与算法(LeetCode)第一节:认识复杂度,对数器,二分法与异或运算
一、认识复杂度
1.评估算法优劣的核心指标:
时间复杂度:当完成了表达式的建立,只要把最高阶项留下即可。低阶项都去掉,高阶项的系数也去掉,记为O(去掉系数的高阶项);
时间复杂度是衡量算法流程的复杂度的一种指标,该指标只与数据量有关,与过程之外的优化无关
常见的时间复杂度(从好到坏)
O(1)
O(logN)
O(N)
O(N+logN)
O(N2),O(N3)....O(N^K)
O(2^N) O(#N)...O(KN)
O(N!)
空间复杂度
常数项时间
说明:如何确定算法流程的总操作数与样本数量之间的表达式关系?
(1)想象该算法流程所处理的数据状况,要按照最差情况来
(2)把整个流程彻底拆分为一个个基本动作,保证每个动作都是常数时间的操作
(3)如果数据量为N,看看基本动作的数量和N是什么关系
2.通过排序,实现时间复杂度的估算
2.1 选择排序
- 基本思想
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
- 实现逻辑
① 第一轮从下标为 1 到下标为 n-1 的元素中选取最小值,若小于第一个数,则交换
② 第二轮从下标为 2 到下标为 n-1 的元素中选取最小值,若小于第二个数,则交换
③ 依次类推下去……
//将数组中的数从下到大排序
public static void selectionSort(int[] arr){
//如果数值中只有一个数,返回
if(arr == null || arr.lenght<2){
return;
}
//遍历数组按从小到大排序
for(int i=0;i<arr.length-1;i++){
//最小值的位置索引
int minIndex=i;
for(int j=i+1;j<arr.length;j++){
mindex=arr[j]<arr[minIndex]?j:minIndex;
}
//交换两个数的位置
swap(arr,i,minindex);
}
}
public static void swap(int[] arr,int i,int j){
arr[i]=arr[i]^arr[j];
arr[j]=arr[i]^arr[j];
arr[i]=arr[i]^arr[j];
}
- 复杂度分析
平均时间复杂度:O(N^2)
最佳时间复杂度:O(N^2)
最差时间复杂度:O(N^2)
空间复杂度:O(1)
排序方式:In-place
稳定性:不稳定
2.2 冒泡排序
- 基本思想
冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。
- 算法步骤
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较 。
//将数组中的数从下到大排序
public static void selectionSort(int[] arr){
//如果数值中只有一个数,返回
if(arr == null || arr.lenght<2){
return;
}
//两两交换
for(int e=arr.length-1;e>0;e++){
for(int i=0;i<e;i++){
if(arr[i]>arr[i+1]){
swap(arr,i,i+1);
}
}
}
}
public static void swap(int[] arr,int i,int j){
arr[i]=arr[i]^arr[j];
arr[j]=arr[i]^arr[j];
arr[i]=arr[i]^arr[j];
}
2.3插入排序
- 基本思想
插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
- 算法步骤
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
//将数组中的数从下到大排序
public static void selectionSort(int[] arr){
//如果数值中只有一个数,返回
if(arr == null || arr.lenght<2){
return;
}
//0~0是有序的
//0~i 变成有序
for(int i=0;i<arr.length-1;i++){
//最小值的位置索引
//arr[i]与arr[i-1],arr[i-2]...比较,如果小就往前交换,一直交换到合适的位置停止
for(int j=i-1;j>=0 && arr[j]>arr[j+1];j--){
//交换两个数的位置
swap(arr,j,j+1);
}
}
}
public static void swap(int[] arr,int i,int j){
arr[i]=arr[i]^arr[j];
arr[j]=arr[i]^arr[j];
arr[i]=arr[i]^arr[j];
}
3、额外空间复杂度
你要实现一个算法流程,在实现算法流程的过程中,你需要开辟一些空间来支持你的算法流程。
作为输入参数的空间,不算额外空间。
作为输出结果的空间, 也不算额外空间。
因为这些都是必要的、和现实目标有关的。所以都不算。
但除此之外,你的流程如果还需要开辟空间才能让你的流程继续下去。这部分空
间就是额外空间。
如果你的流程只需要开辟有限几个变量,额外空间复杂度就是O(1)。
4、算法流程的常数项
我们会发现,时间复杂度这个指标,是忽略低阶项和所有常数系数的。难道同样时间复杂度的流程,在实际运行时候就一样的好吗?
当然不是。
时间复杂度只是一个很重要的指标而已。如果两个时间复杂度一样的算法,你还要去在时间上拼优劣,就进入到拼常数时间的阶段,简称拼常数项。
补充说明:一个问题的最优解是什么意思?
一般情况下,认为解决一个问题的算法流程,在时间复杂度的指标上,一定要尽可能的低,先满足了时间复杂度最低这个指标之后,使用最少的空间的算法流程,叫这个问题的最优解。
一般说起最优解都是忽略掉常数项这个因素的,因为这个因素只决定了实现层次的优化和考虑,而和怎么解决整个问题的思想无关。
二、认识对数器
1,你想要测的方法a
2,实现复杂度不好但是容易实现的方法b3,实现一个随机样本产生器
4、把方法a和方法b跑相同的随机样本,看看得到的结果是否一样
5,如果有一个随机样本使得比对结果不一致,打印样本进行人工干预,改对方法a和方法b
6,当样本数量很多时比对测试依然正确,可以确定方法a已经正确。
案例说明:产生一个随机数组
public static int[] generateRandomArray(int maxSize,int maxValue){
//Math.random():返回[0,1)范围一个随机小数
int[] arr=new int[(int)((maxSize+1)*Math.random())];
for(int i=0;i<arr.length;i++){
//进行减法是为了可以生成负数
arr[i]=(int)((maxValue+1)*Math.random())-(int)((maxValue+1)*Math.random())
}
return arr;
}
三、认识二分法
1)在一个有序数组中,找某个数是否存在
2)在一个有序数组中,找>=某个数最左侧的位置
3)在一个有序数组中,在<=某个数最右侧 的位置
- 局部最小值问题
1.有序数组使用二分法
public static boolean exist(int[] sortedArr,int num){
if(sortedArr == null || sortedArr.length==0){
return false;
}
int L=0;//左索引
int R=sortedArr.length-1;//右索引
int mid=0;//中间位置索引
while(L<R){
mid=L+((R-L)>>1);//等价于mid=(L+R)/2
if(sorteArr[mid]==num){
return ture
}else if(sottArr[mid]>num){
R=mid-1;
}else{
L=mid+1;
}
return sortArr[L]==num;
}
}
2.无序数组且相邻不等使用二分法
public static int getLessIndex(int[] arr){
//判断数组是否为空
if(arr==null || arr.length==0){
return -1;
}
//判断是否只有一个数据,或者有两个数据,但arr[0]<arr[1]
if(arr.length==1 || arr[0]<arr[1]){
return 0;
}
//判断最后一个是否是小的
if(arr[arr.length-1]<arr[arr.length-2]){
return arr.length-1;
}
int left=1;
int right=arr.length-2;
int mid=0;
while(left<right){
mid=(left+right)/2;
if(arr[mid]>arr[mid-1]){
right=mid-1;
}else if(arr[mid]>arr[mid+1]){
left=mid+1;
}else{
return mid;
}
}
return left;
}
四、认识异或运算
1.异或运算:相同为0,不同为1
同或运算:相同为1,不同为0
2.异或运算的性质
- 0^N=N N^N=0
- 异或运算满足交换律与结合律
举例:如何取出一个整型数最右侧的1(以二进制为例)
比如一个整数N:001101010000
则需要输出: 000000010000
方法:N&((~N)+1)(即N与N的取反加1进行与运算)
原理:
N: 001101010000
~N: 110010101111
~N+1: 110010110000
N&((~N)+1):000000010000
说明:上述例子可应用于多种算法中,如以下例题
给一个整数数组arr,有两个不同的数出现了奇数次,找出这两个数
//假设出现奇数次的数为a,b
public static void printOddTImesNum2(int[] arr){
for eor=0;
for(int i =0;i<arr.length;i++){
eor ^=arr[i];//此时eor的值为a^b的值
//eor=a^b
//且eor !=0
//所以eor(二进制)必然有一个位置上是1
//为了确定这个位置,我们考虑找出最右侧的1的位置p
//保留位置p的数的值
int rightOne=eor & (~eor+1);
//找出最右侧的1的位置p 后,我们将数组分为两类,
//一类是位置p上为1的,一类是位置p上不为1的
//选择其中一类进行异或运算eor1 ^=arr[i]可得出a或b其中一个数
//则另一个数进行eor1 ^eor 可得出
int onlyOne=0;//eor1
for(int i=0;i<arr.length:i++){
//找到位置p上是1 的那一类数
if((arr[i] & rightOne) !=0){
onlyOne ^=arr[i];
}
}
System.out.println(onlyOne+" "+(eor^onlyOne));
}
}
数据结构与算法(LeetCode)第一节:认识复杂度,对数器,二分法与异或运算的更多相关文章
- 为什么我要放弃javaScript数据结构与算法(第一章)—— JavaScript简介
数据结构与算法一直是我算比较薄弱的地方,希望通过阅读<javaScript数据结构与算法>可以有所改变,我相信接下来的记录不单单对于我自己有帮助,也可以帮助到一些这方面的小白,接下来让我们 ...
- 《数据结构与算法之美》 <02>复杂度分析(下):浅析最好、最坏、平均、均摊时间复杂度?
上一节,我们讲了复杂度的大 O 表示法和几个分析技巧,还举了一些常见复杂度分析的例子,比如 O(1).O(logn).O(n).O(nlogn) 复杂度分析.掌握了这些内容,对于复杂度分析这个知识点, ...
- 《数据结构与算法之美》 <01>复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?
我们都知道,数据结构和算法本身解决的是“快”和“省”的问题,即如何让代码运行得更快,如何让代码更省存储空间.所以,执行效率是算法一个非常重要的考量指标. 那如何来衡量你编写的算法代码的执行效率呢?这里 ...
- 算法:第一节课Master定理
1.ctex:要求用Tex编辑器进行作业的书写 2.与东大本科有差距,还需要多点努力才行. 3. 4.考试不考概念 5. 6.时间复杂度和空间复杂度 7.算法好坏的评价标准 8.基本运算 9.时间复杂 ...
- 数据结构与算法/leetcode/lintcode题解
http://algorithm.yuanbin.me/zh-hans/index.html
- 数据结构和算法(What Why How)
数据结构和算法是什么? 从广义上讲,数据结构就是指一组数据的存储结构.算法就是操作数据的一组方法. 从狭义上讲,是指某些著名的数据结构和算法,比如队列.堆.栈.二分查找.动态规划等. 数据结构和算法有 ...
- .NET Core 数据结构与算法 1-1
.NET Core 数据结构与算法 1-1 本节内容为顺序表 简介 线性表是简单.基本.常用的数据结构.线性表是线性结构的抽象 (Abstract),线性结构的特点是结构中的数据元素之间存在一对一的线 ...
- 左神算法第一节课:复杂度、排序(冒泡、选择、插入、归并)、小和问题和逆序对问题、对数器和递归(Master公式)
第一节课 复杂度 排序(冒泡.选择.插入.归并) 小和问题和逆序对问题 对数器 递归 1. 复杂度 认识时间复杂度常数时间的操作:一个操作如果和数据量没有关系,每次都是固定时间内完成的操作,叫做常数 ...
- 【算法】数据结构与算法基础总览(中)——刷Leetcode等算法题时一些很实用的jdk辅助方法锦集
最近重新学习数据结构与算法以及刷leetcode算法题时,发现不少jdk自带的方法可以提升刷题的效率.这些小技巧不仅仅对刷算法题带来便利,对我们平时开发也是很有帮助的.本文以java语言为基础,记录了 ...
- Python3-Cookbook总结 - 第一章:数据结构和算法
第一章:数据结构和算法 Python 提供了大量的内置数据结构,包括列表,集合以及字典.大多数情况下使用这些数据结构是很简单的. 但是,我们也会经常碰到到诸如查询,排序和过滤等等这些普遍存在的问题. ...
随机推荐
- 【SpringBoot】条件装配 @profile
profile 使用说明: @profile注解的作用是指定类或方法在特定的 Profile 环境生效,任何@Component或@Configuration注解的类都可以使用@Profile注解. ...
- MAUI Blazor项目中如何添加一个返回服务,并支持安卓返回键
前言 MAUI Blazor中,安卓项目的返回键体验很不好,只能如同浏览器一样返回上一页.但很多时候,我们想让他返回的上一页,不一定就是实际上的上一页.而且也想让返回键去支持一些事件,按下返回键触发, ...
- mysql-workbench-community报错解决办法
输入以下命令: sudo apt-get -f install 参考链接:https://www.jianshu.com/p/767c9a29b403
- 2023牛客暑期多校训练营4 AFHJL
比赛链接 A 题解 知识点:KMP,构造. 考虑构造全 \(0,1\) 串,至少有一个可行. 我们只需要考虑到 \(t\) 的border \(t'\) ,即 \(t'+s+t'\) : 当 \(t' ...
- Redis从入门到放弃(8):哨兵模式
在前面的文章中介绍了Redis的主从复制,但主从复制存在一定的缺陷.如果Master节点宕机,因为不具备自动恢复功能,需要人工干预,那么在这个干预过程中Redis将不可用. 为了解决这一问题,Redi ...
- asp.net core之HttpClient
本文介绍了ASP.NET Core中的HttpClient和HttpClientFactory的作用.用法以及最佳实践.通过示例代码的展示,读者可以了解如何使用HttpClient发送HTTP请求并处 ...
- 一文理解GIT的代码冲突
对于GIT,不知道有没有人和我一样,很长时间都是小心翼翼.紧张兮兮,生怕一不小心,自己辛苦写的代码没了. 特别是代码冲突,更是难到我无法理解,每次都要求助于百度,跟着人家的教程一步步解决,下一次还是这 ...
- 记一次MySql灾难性事件
2023年8月8日,本来系一个风和日丽的夏天中的平凡一天,但这种平凡,注定住佢一定唔平凡,唉...现在回忆起都阵阵咁痛!!! 重要嘅事情讲三次,唔好手贱,唔好手贱,唔好手贱 事日,如常上班,本人系一名 ...
- selenium-wire兼容selenium和requests
背景 在工作中UI自动化中可能会需要用到API来做一些数据准备或清理的事情,那UI操作是略低效的,但API操作相对高效. 而实战课就有这样一个案例,不过那个案例是UI操作和API分开的. 极少会遇到这 ...
- Ipa打包并安装到iphone
手动运行篇: 在真机上运行appium会进行闪退,因为我们的真机是不合法的真机,怎么样才能合法呢,要注册我们的设备才行 要对app进行打包,要先进行签名,要签名,就需要证书,证书可以自己伪造,但是这一 ...