前言:

本题来自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;

执行步骤如下:

  1. 先生成数组P=[1,2,3,4,5]

  2. 查找queries第1个元素3,遍历数组P后得到结果result=[2],之后修改数组P,将数组P中的元素3提到数组起始位置,之后数组P=[3,1,2,4,5]

  3. 查找queries第2个元素1,遍历数组P后得到结果result=[2,1],之后修改数组P,将数组P中的元素1提到数组起始位置,之后数组P=[1,3,2,4,5]

  4. 查找queries第3个元素2,遍历数组P后得到结果result=[2,1,2],之后修改数组P,将数组P中的元素2提到数组起始位置,之后数组P=[2,1,3,4,5]

  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]中的元素。

我们还注意到几个情况

  1. 当数组P完全有序时,即P=[1,2,3,4,...,m],queries[index]在数组P中的下标是queries[index] -1。假设queries[index] == 2,则其结果应该返回1,所谓的元素在P中的下标,也就是元素queries[index]在P中前面有几个元素。

  2. 当查找的元素在A中时,由于A为乱序的,为此我们只能遍历A,得到元素queries[index]的下标值进行返回。

  3. 当要查找的元素在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不变。

  4. 当要查找的元素在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.查询带键的排列 这题简单,但我还能优化。精益求精,才是算法的乐趣所在!的更多相关文章

  1. 【LeetCode】1409. 查询带键的排列 Queries on a Permutation With Key

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 模拟 日期 题目地址:https://leetcode ...

  2. sqlite数据库 select 查询带换行符数据

    在sqlite 数据库中用 select 语句查询带 换行符的 数据信息 实现 SELECT   * from questions_exec where title like     '%'||x'0 ...

  3. MSSQL 随机查询且降序排列

    --随机查询且降序排列 * FROM dbo.COMPANY_USER_INFO ORDER BY NEWID()) AS T ORDER BY T.cu_id DESC

  4. MySQL多表查询之外键、表连接、子查询、索引

    MySQL多表查询之外键.表连接.子查询.索引 一.外键: 1.什么是外键 2.外键语法 3.外键的条件 4.添加外键 5.删除外键 1.什么是外键: 主键:是唯一标识一条记录,不能有重复的,不允许为 ...

  5. [BZ4923][Lydsy1706月赛]K小值查询

    K小值查询 题面 维护一个长度为n的正整数序列a_1,a_2,...,a_n,支持以下两种操作: 1 k,将序列a从小到大排序,输出a_k的值. 2 k,将所有严格大于k的数a_i减去k. Input ...

  6. Oracle中查询主键、外键、sequence、表基本信息等

    一次看到某张表中有几条ID相同的数据,通过业务确认该ID应该是唯一的,后来找到原因,因为DBA未对该表建主键. 现在DBA工作比较忙,我们项目有时需要新增或者修改数据库表结构时,可能需要对表结构进行确 ...

  7. EF 查询外键对应的实例

    EF 查询外键对应的实例   1. 查询时易遇到的情况: 能查询到外键值,但对应的外键实例为null. 解决方法: (1) 使用EF的include // 我的应用如下 // SampleResult ...

  8. [leetcode]90. Subsets II数组子集(有重)

    Given a collection of integers that might contain duplicates, nums, return all possible subsets (the ...

  9. 一篇文章带你了解NoSql数据库——Redis简单入门

    一篇文章带你了解NoSql数据库--Redis简单入门 Redis是一个基于内存的key-value结构数据库 我们会利用其内存存储速度快,读写性能高的特点去完成企业中的一些热门数据的储存信息 在本篇 ...

随机推荐

  1. 实现Sobel算子滤波、Robers算子滤波、Laplace算子滤波

    前几天,老师布置了这样一个任务,读取图片并显示,反色后进行显示:进行Sobel算子滤波,然后反色,进行显示:进行Robers算子滤波,然后反色,进行显示.我最后加上了Laplace算子滤波,进行了比较 ...

  2. Mozilla的 Firefox Graphics 团队向社区寻求重现WebRender bug的方法

    导读 Mozilla 的 Firefox Graphics 团队正在向社区寻求帮助,由于他们收到了一些随机发生的 UI 错误报告,却一直无法找出错误的重现步骤(STR),因此现在向外寻求社区用户的帮助 ...

  3. 阿里AI芯片:12nm工艺、709平方毫米大核心

    含光出自<列子·汤问>篇有“上古三剑”一章,寓意含而不露,光而不耀,象征含光 800 无形却强劲的算力. 含光 800 是一款 AI 芯片,偏重推理.据介绍,1 颗含光 800 的算力相当 ...

  4. JS高精度乘法计算问题(牛客网乘法-求 a 和 b 相乘的值,a 和 b 可能是小数,需要注意结果的精度问题)

    用到的知识点===> toFixed(num); toFixed() 方法可把 Number 四舍五入为指定小数位数的数字; 参数num: 代表小数位数: 例:var num = 5.56789 ...

  5. vue基础----key的作用

    1.key 在虚拟dom中 标记不同的结构,for循环中需要添加上 <body> <div id="app"> <div v-if="fla ...

  6. XSS(跨站脚本攻击)详解

    跨站脚本攻击XSS(Cross Site Scripting),为了不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS.恶意攻击者往Web ...

  7. PLINQ 并行操作Linq

    C#并行编程-PLINQ:声明式数据并行   目录 C#并行编程-相关概念 C#并行编程-Parallel C#并行编程-Task C#并行编程-并发集合 C#并行编程-线程同步原语 C#并行编程-P ...

  8. idea安装 阿里巴巴Java编码准则插件

    首先还是打开熟悉的idea 在marketplace 输入 alibaba 我这是已经安装过了 下载完成之后重启idea生效 如果需要那就手动的扫描 当然已经自动的扫描了 如果你的代码不符合阿里的标准 ...

  9. Java中内部类和静态内部类的区别

    内部类和静态内部类 示例 public class OuterClass { private int numPrivate = 1; public int numPublic = 2; public ...

  10. python的进制转换

    转载于:https://www.cnblogs.com/FWF1944/p/11132409.html(方法论190404) Python整数能够以十六进制,八进制和二进制来编写,作为一般以10位基数 ...