码迷,mamicode.com
首页 > 编程语言 > 详细

K:leetcode 5381.查询带键的排列 这题简单,但我还能优化。精益求精,才是算法的乐趣所在!

时间:2020-04-12 22:35:13      阅读:86      评论:0      收藏:0      [点我收藏+]

标签:数组   while   情况   length   遍历数组   获取元素   步骤   max   img   

前言:

本题来自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.查询带键的排列 这题简单,但我还能优化。精益求精,才是算法的乐趣所在!

标签:数组   while   情况   length   遍历数组   获取元素   步骤   max   img   

原文地址:https://www.cnblogs.com/MyStringIsNotNull/p/12687832.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!