K:leetcode 5381.查询带键的排列 这题简单,但我还能优化。精益求精,才是算法的乐趣所在!
前言:
本题来自leetcode第184场周赛的第二小题。以前参加过周赛,觉得很有趣。苦于最近一段时间比较忙就没坚持参加了(实际上是借口来着....),由于昨晚思考一些事情,导致睡不着,所以起得有点早,就参加了本场周赛,然后就碰到了这道题。
这题本身并不难,但是在比赛结束后,参看了别人的题解。基本都是用暴力模拟的方式来解决的(虽然也能accept),但本人觉得有着改进空间。为此,特地整理了思路,并将思路整理成文,以期能够共同获得进步。
为循序渐进的讲解该题,按照以往的习惯,先从最简单的方式入手,再逐步考虑优化。
题目:
给你一个待查数组 queries ,数组中的元素为 1 到 m 之间的正整数。 请你根据以下规则处理所有待查项 queries[i](从 i=0 到 i=queries.length-1):
一开始,排列 P=[1,2,3,...,m]。
对于当前的 i ,请你找出待查项 queries[i] 在排列 P 中的位置(下标从 0 开始),然后将其从原位置移动到排列 P 的起始位置(即下标为 0 处)。注意, queries[i] 在 P 中的位置就是 queries[i] 的查询结果。
请你以数组形式返回待查数组 queries 的查询结果。
示例 1:
输入:queries = [3,1,2,1], m = 5
输出:[2,1,2,1]
解释:待查数组 queries 处理如下:
对于 i=0: queries[i]=3, P=[1,2,3,4,5], 3 在 P 中的位置是 2,接着我们把 3 移动到 P 的起始位置,得到 P=[3,1,2,4,5] 。
对于 i=1: queries[i]=1, P=[3,1,2,4,5], 1 在 P 中的位置是 1,接着我们把 1 移动到 P 的起始位置,得到 P=[1,3,2,4,5] 。
对于 i=2: queries[i]=2, P=[1,3,2,4,5], 2 在 P 中的位置是 2,接着我们把 2 移动到 P 的起始位置,得到 P=[2,1,3,4,5] 。
对于 i=3: queries[i]=1, P=[2,1,3,4,5], 1 在 P 中的位置是 1,接着我们把 1 移动到 P 的起始位置,得到 P=[1,2,3,4,5] 。
因此,返回的结果数组为 [2,1,2,1] 。
示例 2:
输入:queries = [4,1,2,2], m = 4
输出:[3,1,2,0]
示例 3:
输入:queries = [7,5,5,8,3], m = 8
输出:[6,5,0,7,5]
数据结构:
List,Array
结合题意,因为要获取元素的下标值,为此,我们可以生成一个数组P,其包含了元素1~m,每次执行一次查找操作就遍历数组P,并把该元素的下标作为结果记录下来,之后将该元素提取到数组的起始位置。
以题干案例为例:
输入:queries=[3,1,2,1],m=5;
执行步骤如下:
先生成数组P=[1,2,3,4,5]
查找queries第1个元素3,遍历数组P后得到结果result=[2],之后修改数组P,将数组P中的元素3提到数组起始位置,之后数组P=[3,1,2,4,5]
查找queries第2个元素1,遍历数组P后得到结果result=[2,1],之后修改数组P,将数组P中的元素1提到数组起始位置,之后数组P=[1,3,2,4,5]
查找queries第3个元素2,遍历数组P后得到结果result=[2,1,2],之后修改数组P,将数组P中的元素2提到数组起始位置,之后数组P=[2,1,3,4,5]
查找queries第4个元素1,遍历数组P后得到结果result=[2,1,2,1],之后修改数组P,将数组P中的元素1提取到数组起始位置,之后数组P=[1,2,3,4,5]
该过程的代码如下:
public int[] processQueries(int[] queries, int m) {
//存放元素1~m的数组
int[] P = new int[m];
for(int i=0;i<m;i++){
P[i] = i+1;
}
//用于存放结果
int[] result = new int[queries.length];
//遍历queries的元素
for(int i=0;i<queries.length;i++){
//查找元素在P中的下标
for(int j =0;j<P.length;j++){
if(P[j]==queries[i]){
result[i] = j;
System.arraycopy(P,0,P,1,j);
P[0] = queries[i];
break;
}
}
}
return result;
}
分析:
很容易就可以分析出来,该算法的时间复杂度为O(nm),空间复杂度为O(m)
那么,通过上面的分析过程,我们可以改进优化哪个点呢?很明显的,一个可以优化的地方是数组P。数组元素移动的次数与n成正比,每次将数组P中的第index个元素提取到起始位置,都需要将0~index-1的元素往后移动一位,并将第index元素插入到P[0]中。这种场景,采用链表的方式来解决,会更好,于是可以将代码改进为如下形式。
该过程的代码如下:
public int[] processQueries(int[] queries, int m) {
int[] result = new int[queries.length];
List<Integer> P = new LinkedList<Integer>();
//生成数组P
for(int i=1;i<=m;i++){
P.add(i);
}
for(int i=0;i<queries.length;i++){
int index = P.indexOf(queries[i]);
result[i] = index;
P.remove(index);
P.add(0,queries[i]);
}
return result;
}
分析:
改进了数据结构之后,算法的时间复杂度依旧为O(nm),空间复杂度为O(m),改进只是改进了时间复杂度的常系数。那么是否还有改进的空间呢?显然有,否则也不会有这文章。
我们换个思路来思考在数组P中查找元素下标的过程。首先,我们可以将数组P划分为两部分,一部分是已经查找过的queries[0]queries[index-1]的元素,我们**称这部分元素为A**,其必定排序在P的前面,且为乱序的。另一部分由剩下的其它元素所组成,我们**称这部分元素为B,其可能乱序也可能有序,但是元素必定是严格按照升序排列的**,也就是未出现在queries[0]queries[index-1]中的元素。
我们还注意到几个情况
当数组P完全有序时,即P=[1,2,3,4,...,m],queries[index]在数组P中的下标是queries[index] -1。假设queries[index] == 2,则其结果应该返回1,所谓的元素在P中的下标,也就是元素queries[index]在P中前面有几个元素。
当查找的元素在A中时,由于A为乱序的,为此我们只能遍历A,得到元素queries[index]的下标值进行返回。
当要查找的元素在B中,且B为完全有序时,即P=[A,B],B=[k,k+1,k+2,.....m],则元素queries[index]在数组P中的下标为queries[index]-1。我们还可以知道,无论A的排列顺序为何种,均不影响结果。假设A=[2,1,4,3] ,B=[5,6,7,8,9],queries[index]==6,则其结果为5。当A=[1,2,4,3]时,该结果返回依旧为5不变。
当要查找的元素在B中,且B为部分有序(元素按照升序排列,但是会缺少部分值)时,即P=[A,B],B=[k,k+1,k+2,k+4,k+5,...,m],此时我们应该返回的元素queries[index]在数组P中的下标为queries[index]-1+maxThanOnA,其中maxThanOnA为A中大于元素queries[index]值的数目,也就是B中大于queries[index]的值被移动到A中的元素个数。假设queries[index]== k+5,则其应当返回k+4,也就是queries[index]-1的值,因为B中的k+3被移动到A中了,无论其在哪个位置,都不影响k+5前面的元素个数这个结果。为此,下标依旧为queries[index]-1。当queries[index]==k+2时,由于元素k+3被移动到了A中,其使得元素k+2前面的元素个数多了一个,为此,其结果应该返回k+2。
综上分析,我们可以用一个List记录A中的元素的情况。当元素queries[index]在A中时,遍历A获取结果,并将进行查询的那个元素移动到A的起始位置中,否则,统计A中大于queries[index]的元素个数,直接返回queries[index]-1+maxThanOnA,并将queries[index]放置到A的起始位置中。
该算法的代码如下:
public int[] processQueries(int[] queries, int m) {
//用于存放结果
int[] result = new int[queries.length];
//用于放置乱序(A)的那些个元素
List<Integer> list = new LinkedList<Integer>();
for(int i=0;i<queries.length;i++){
//遍历乱序的元素的索引
int index = 0;
//记录列表中比当前元素大的元素个数
int maxThanOnA = 0;
while(index<list.size()){
int number = list.get(index);
if(number == queries[i]){
result[i] = index;
list.remove(index);
list.add(0,number);
break;
}else if(number>queries[i]){
maxThanOnA++;
}
index++;
}
//在列表中找到了元素
if(index<list.size()){
continue;
}
result[i] = queries[i]-1+maxThanOnA;
list.add(0,queries[i]);
}
return result;
}
分析:
该算法的时间复杂度为O(n^2),空间复杂度也为O(n)。
总结:
综上所述,当m>>>n时,时间复杂度为O(n)的算法更加有利。最坏情况下,也是m==n,此时,无论采取何种算法,时间复杂度为O(n^2),空间复杂度为O(n)。
这个是本人的公众号,致力于写出绝大部分人都能读懂的技术文章。欢迎相互交流,我们博采众长,共同进步。
K:leetcode 5381.查询带键的排列 这题简单,但我还能优化。精益求精,才是算法的乐趣所在!的更多相关文章
- 【LeetCode】1409. 查询带键的排列 Queries on a Permutation With Key
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 模拟 日期 题目地址:https://leetcode ...
- sqlite数据库 select 查询带换行符数据
在sqlite 数据库中用 select 语句查询带 换行符的 数据信息 实现 SELECT * from questions_exec where title like '%'||x'0 ...
- MSSQL 随机查询且降序排列
--随机查询且降序排列 * FROM dbo.COMPANY_USER_INFO ORDER BY NEWID()) AS T ORDER BY T.cu_id DESC
- MySQL多表查询之外键、表连接、子查询、索引
MySQL多表查询之外键.表连接.子查询.索引 一.外键: 1.什么是外键 2.外键语法 3.外键的条件 4.添加外键 5.删除外键 1.什么是外键: 主键:是唯一标识一条记录,不能有重复的,不允许为 ...
- [BZ4923][Lydsy1706月赛]K小值查询
K小值查询 题面 维护一个长度为n的正整数序列a_1,a_2,...,a_n,支持以下两种操作: 1 k,将序列a从小到大排序,输出a_k的值. 2 k,将所有严格大于k的数a_i减去k. Input ...
- Oracle中查询主键、外键、sequence、表基本信息等
一次看到某张表中有几条ID相同的数据,通过业务确认该ID应该是唯一的,后来找到原因,因为DBA未对该表建主键. 现在DBA工作比较忙,我们项目有时需要新增或者修改数据库表结构时,可能需要对表结构进行确 ...
- EF 查询外键对应的实例
EF 查询外键对应的实例 1. 查询时易遇到的情况: 能查询到外键值,但对应的外键实例为null. 解决方法: (1) 使用EF的include // 我的应用如下 // SampleResult ...
- [leetcode]90. Subsets II数组子集(有重)
Given a collection of integers that might contain duplicates, nums, return all possible subsets (the ...
- 一篇文章带你了解NoSql数据库——Redis简单入门
一篇文章带你了解NoSql数据库--Redis简单入门 Redis是一个基于内存的key-value结构数据库 我们会利用其内存存储速度快,读写性能高的特点去完成企业中的一些热门数据的储存信息 在本篇 ...
随机推荐
- JS反爬绕过思路之--谷歌学术镜像网链接抓取
首先,从问题出发: http://ac.scmor.com/ 在谷歌学术镜像网收集着多个谷歌镜像的链接.我们目标就是要把这些链接拿到手. F12查看源码可以发现,对应的a标签并不是我们想要的链接,而是 ...
- (转)const的内部链接属性(C++中适用)
转载自:http://xiangwangfeng.com/2011/05/02/const%E7%9A%84%E5%86%85%E9%83%A8%E9%93%BE%E6%8E%A5%E5%B1%9E% ...
- .tar.xz文件的创建和解压
创建tar.xz文件:只要先 tar cvf xxx.tar xxx/ 这样创建xxx.tar文件先,然后使用 xz -z xxx.tar 来将 xxx.tar压缩成为 xxx.tar.xz 解压ta ...
- javaScript 基础知识汇总(八)
1.Map Set WeakMap 和WeakSet Map 是一个键值对的集合,主要的方法包括: new Map() 创建Map map.set(key,value) 根据键(key)存储值(va ...
- 使用Netty如何解决拆包粘包的问题
首先,我们通过一个DEMO来模拟TCP的拆包粘包的情况:客户端连续向服务端发送100个相同消息.服务端的代码如下: AtomicLong count = new AtomicLong(0); NioE ...
- BigDecimal介绍及BigDecimal实现四舍五入
BigDecimal介绍及BigDecimal实现四舍五入 BigDecimal是什么? 我们知道float最大精度是7-8位有效数字,而double的最大精度是16-17位有效数字,那么大于16位的 ...
- macOS开发:调整NSImage尺寸大小
原文链接 extension NSImage { func resize(_ to: CGSize, isPixels: Bool = false) -> NSImage { var toSiz ...
- 二维数组及Arrays工具类
1.二维数组 概念: 数组中的每一个元素类型都是一维数组 二维数组初始化方式: 静态初始化: 格式: 元素类型[][] 数组名 = new 元素类型[][]{{一维数组1},{一维数组2},{一维数组 ...
- Scapy编写UDP扫描脚本
脚本内容如下: from scapy.all import * import optparse import threading def scan(target,port): pkt=IP(dst=t ...
- React Native升级
几个月前创建了一个版本为0.60.4的项目,现要更新到最新版本0.61.4. 具体可查看 https://facebook.github.io/react-native/docs/upgrading# ...