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结构数据库 我们会利用其内存存储速度快,读写性能高的特点去完成企业中的一些热门数据的储存信息 在本篇 ...
随机推荐
- Linux下MongoDB单实例的安装和配置详解
推荐网站 MongoDB官网:http://www.mongodb.org/ MongoDB学习网站:http://www.runoob.com/mongodb 一.创建MongoDB的资源目录和安装 ...
- Simulink仿真入门到精通(十五) Simulink在流程工业中的仿真应用
15.1 工业乙醇生产与计算机仿真 乙醇作为可再生清洁能源不仅可以代替四乙基铅作为汽油的防爆剂,还可以制造汽油醇.这一巨大的潜在需求促使人们去寻找提高乙醇工业生产率的途径,使人们着手于发酵工程的研究. ...
- Java多线程并发03——在Java中线程是如何调度的
在前两篇文章中,我们已经了解了关于线程的创建与常用方法等相关知识.接下来就来了解下,当你运行线程时,线程是如何调度的.关注我的公众号「Java面典」了解更多 Java 相关知识点. 多任务系统往往需要 ...
- .NET Core学习笔记(5)——WebAPI从Server端push消息到Client
标题起得有点厉害,汉字夹杂着E文,不符合教育部公布的“向社会推荐使用的外语词中文译名”规范.不过他管不着我.写本篇的起因,是重构一个现有的WinForms程序,将Server端的部分逻辑从raw so ...
- Natas12 Writeup(文件上传漏洞)
Natas12: 文件上传页面,源码如下: function genRandomString() { $length = 10; $characters = "0123456789abcde ...
- 1. chromedriver的下载和配置
使用selenium时,需要用到不同浏览器的driver. 我常用chromedriver,所以先记录chromedriver的使用和配置.其他浏览器的driver配置大同小异. 一. 确定浏览器的版 ...
- Beef xss神器
KALI中启动BEEFXSS PAYLOAD为 <script src=”http://攻击机IP:3000/hook.js”></script> 将攻击代码插入到存储型XSS ...
- 关于Web2.0
前言:本来是想写HTML的,发现没什么好写的,就简单写一下Web2.0好了 什么是Web 2.0: "Web 2.0 is the business revolution in the co ...
- CF1327D Infinite Path 题解
原题链接 太坑了我谔谔 简要题意: 求一个排列的多少次幂能达到另一个排列.排列的幂定义见题.(其实不是新定义的,本来就是这么乘的) 很显然,这不像快速幂那样可以结合律. 既然这样,就从图入手. 将 \ ...
- 洛谷 P3808 【模板】AC自动机(简单版) 题解
原题链接 前置知识: 字典树.(会 \(\texttt{KMP}\) 就更好) 显然呢,本题用 字典树 和 \(\texttt{KMP}\) 无法解决问题. 所以我们发明了一个东西: \(\textt ...