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;

执行步骤如下:

  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)。



这个是本人的公众号,致力于写出绝大部分人都能读懂的技术文章。欢迎相互交流,我们博采众长,共同进步。

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

时间: 2024-10-08 06:15:39

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

20.4.12 周赛 查询带键的排列 中等

时间复杂度O(m*n),空间复杂度不会分析... 题目 给你一个待查数组 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 中的

leetcode-184周赛-5381-查询带键的排列

题目描述: 自己的提交: class Solution: def processQueries(self, queries: List[int], m: int) -> List[int]: l = [i for i in range(1,m+1)] ans = [] for i in queries: index = l.index(i) ans.append(index) l.remove(i) l.insert(0,i) return ans 原文地址:https://www.cnblog

一款可支持物流查询带营销功能的小程序商城 附源码

零基础快速部署一款可支持物流查询带营销功能的小程序商城只要有基础的计算机能力都可以开发自己的小程序商城,让技术门槛降低,服务更多用户,创造无限价值!只需三步,就可开发自己的小程序商城,带拼团砍价秒杀分销优惠券等强大功能一.注册小程序账号,认证小程序.开通微信支付商户付款功能https://mp.weixin.qq.com二.下载安装小程序商城源码,部署服务器http://github.crmeb.net/u/crmeb三.后台配置 小程序基本参数,小程序域名,支付配置,下载小程序开发工具,提交源

怎么查询别人的qq聊天记录.被删除的微信聊天记录还能恢复么

怎么查询别人的qq聊天记录.被删除的微信聊天记录还能恢复么--推荐他给你他{扣-扣} 56005049 手-机-定-位 QQ 微-信-查-看-记-录,监-控-手-机-微-信.手-机-清-单-等-等-我老婆手机通话清单,就是他 帮 我 查 的,他信誉很好,他 的 技 术 一流,我给他办 过业务,好技术 分享你我,新闻资讯顶,我给他办 过业务,他人很好而且办事儿的速度快,是我见过技 术最好的人了,认准竭诚为您排忧解难

ASP.NET Core 使用 EF 框架查询数据 - ASP.NET Core 基础教程 - 简单教程,简单编程

原文:ASP.NET Core 使用 EF 框架查询数据 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 使用 EF 框架查询数据 上一章节我们学习了如何设置和初始化数据库,以及如何创建迁移代码和应用迁移代码.本章节我们就学习如何使用 EF 框架来查询数据库,至于添加和修改,后面的章节中我们会慢慢学习到 添加测试数据 我们首先使用 SQLite Studio 添加三条数据 ID Name 1 李白 2 杜甫 3 白居易 使用 SQLite Studio

【poj1901-求区间第k大值(带修改)】树状数组套主席树

901: Zju2112 Dynamic Rankings Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 7025  Solved: 2925[Submit][Status][Discuss] Description 给定一个含有n个数的序列a[1],a[2],a[3]--a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]--a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]

LeetCode (18) Permutations I &amp; II (排列一、二)

不存在重复的情况:题目描述 Given a collection of numbers, return all possible permutations. For example, [1,2,3] have the following permutations: [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], and [3,2,1]. 本题要求输入一数组,输出数组的所有排列方式.题目中给定的为int类型的数组,有些题目中给定的为字符类型的,两种思路是一

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

一.外键: 1.什么是外键 2.外键语法 3.外键的条件 4.添加外键 5.删除外键 1.什么是外键: 主键:是唯一标识一条记录,不能有重复的,不允许为空,用来保证数据完整性 外键:是另一表的主键, 外键可以有重复的, 可以是空值,用来和其他表建立联系用的.所以说,如果谈到了外键,一定是至少涉及到两张表.例如下面这两张表: 上面有两张表:部门表(dept).员工表(emp).Id=Dept_id,而Dept_id就是员工表中的外键:因为员工表中的员工需要知道自己属于哪个部门,就可以通过外键Dep

MySQL数据库学习笔记(六)----MySQL多表查询之外键、表连接、子查询、索引

注:本文转自:http://www.cnblogs.com/smyhvae/p/4042303.html 本章主要内容: 一.外键 二.表连接 三.子查询 四.索引 一.外键: 1.什么是外键 2.外键语法 3.外键的条件 4.添加外键 5.删除外键 1.什么是外键: 主键:是唯一标识一条记录,不能有重复的,不允许为空,用来保证数据完整性 外键:是另一表的主键, 外键可以有重复的, 可以是空值,用来和其他表建立联系用的.所以说,如果谈到了外键,一定是至少涉及到两张表.例如下面这两张表: 上面有两