数据结构与算法(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 提供了大量的内置数据结构,包括列表,集合以及字典.大多数情况下使用这些数据结构是很简单的. 但是,我们也会经常碰到到诸如查询,排序和过滤等等这些普遍存在的问题. ...
随机推荐
- 我真的想知道,AI编译器中的IR是什么?
随着深度学习的不断发展,AI 模型结构在快速演化,底层计算硬件技术更是层出不穷,对于广大开发者来说不仅要考虑如何在复杂多变的场景下有效的将算力发挥出来,还要应对 AI 框架的持续迭代. AI 编译器就 ...
- python:时间模块dateutil
安装 pip install python-dateutil dateutil模块主要有两个函数,parser和rrule. 其中parser是根据字符串解析成datetime,而rrule则是根据定 ...
- ubuntu 安装sublime
Install the GPG key: wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | sudo apt-key ad ...
- openlayers动态添加自定义div图层 具有筛选功能 和浮窗
https://blog.csdn.net/weixin_43863505/article/details/119493664
- PoW是什么?
PoW是什么? 工作量证明(proof of work,PoW)是一种用于确认和验证区块链交易和新区块有效性的共识算法.区块链中常见的工作量证明算法包括比特币的SHA-256.以太坊的Ethash.莱 ...
- windows配置supervisor实现nginx自启
前言 有些老项目的nginx部署在windows server上,而且服务器比较老旧,经常异常重启.鉴于个人并不熟悉windows server,因此配置supervisor自启nginx,实现win ...
- CAP 7.2 版本发布通告
前言 今天,我们很高兴宣布 CAP 发布 7.2 版本正式版,我们在这个版本中主要致力于 Dashboard 对 k8s 服务发现的支持. 从 7.1 版本以来,我们发布了4个小版本,在这些版本中我们 ...
- 【Unity3D】反射和折射
1 前言 立方体纹理(Cubemap)和天空盒子(Skybox)中介绍了生成立方体纹理和制作天空盒子的方法,本文将使用立方体纹理进行采样,实现反射.菲涅耳反射和折射效果.另外,本文还使用了 Gra ...
- 以程序员为视角,关于商品详情API接口的说明
商品详情API接口是现代电商平台中非常重要的一部分,它提供了获取淘宝商品详细信息的能力.作为一个程序员,了解如何调用这个API接口是非常关键的. 首先,我们需要明确的是,API接口是应用程序与服务 ...
- 运用手机运营商二要素Api接口,守护您的账户和隐私,让您安心使用!
随着移动互联网的普及,我们的生活离不开手机,手机成为了我们生活中不可或缺的一部分.但是随着移动支付的普及,手机支付在我们的生活中也变得越来越重要.手机支付是一种方便快捷的支付方式,但是也存在一些安全隐 ...