1409. Queries on a Permutation With Key
问题:
给定一个数字m,则有一个数组Array:1~m分别在数组的0~m-1位上放置。
在给定一个操作对象数组 queries,表示操作对象数字,
返回当前该数字的位置到结果数组res中,并将该数字移到Array数组开头。
求操作完所有queries中的对象后,得到的res。
Example 1: Input: queries = [3,1,2,1], m = 5 Output: [2,1,2,1] Explanation: The queries are processed as follow: For i=0: queries[i]=3, P=[1,2,3,4,5], position of 3 in P is 2, then we move 3 to the beginning of P resulting in P=[3,1,2,4,5]. For i=1: queries[i]=1, P=[3,1,2,4,5], position of 1 in P is 1, then we move 1 to the beginning of P resulting in P=[1,3,2,4,5]. For i=2: queries[i]=2, P=[1,3,2,4,5], position of 2 in P is 2, then we move 2 to the beginning of P resulting in P=[2,1,3,4,5]. For i=3: queries[i]=1, P=[2,1,3,4,5], position of 1 in P is 1, then we move 1 to the beginning of P resulting in P=[1,2,3,4,5]. Therefore, the array containing the result is [2,1,2,1]. Example 2: Input: queries = [4,1,2,2], m = 4 Output: [3,1,2,0] Example 3: Input: queries = [7,5,5,8,3], m = 8 Output: [6,5,0,7,5] Constraints: 1 <= m <= 10^3 1 <= queries.length <= m 1 <= queries[i] <= m
解法:
Constraints: 1 <= m <= 10^3
则可说明,该问题满足 m*m 的时间复杂度
leetcode允许运行时间10^6
解法一:
position记录法,
完全按照题意,进行操作,
只是使用pos数组记录每个数字的位置。(hash方便查找)
pos[q]=j 代表数字 q 所在的位置在 j 上。
那么queries中的每一个数字 i 即能瞬间找到其对应位置pos[q]。
找到后,res.push_back(pos[q])
同时对1~m的所有数字的位置进行更新:
1.pos[i] < pos[q] :都往后移动一位:pos[i]++
2.pos[q]=0:把q移动到首位。
代码参考:
1 class Solution { 2 public: 3 vector<int> processQueries(vector<int>& queries, int m) { 4 vector<int> pos(m+1); 5 vector<int> res; 6 iota(pos.begin(), pos.end(), -1);//从pos[0]=-1开始,pos[i]=(pos[i-1]++); 7 for(int q:queries){ 8 res.push_back(pos[q]); 9 for(int i=1; i<=m; i++){ 10 if(pos[i]<pos[q]) pos[i]++; 11 } 12 pos[q]=0; 13 } 14 return res; 15 } 16 };
解法二:
Fenwick Tree方法:
Fenwick Tree介绍:https://zxi.mytechroad.com/blog/sp/fenwick-tree-binary-indexed-tree-sp3/
【转自:花花酱 的博客,地址如上】
一般对应问题:
求数组前缀和(query方法)。(且中间含有多次,单独更新(update方法)其中某个值的操作)
解法思想:存储连续多个元素之和->使得所求=多个既存连续组之和。(空间换时间)
选择元素和的根据:Tree->使得时间复杂度为: log n
选择方法:
- partsum[1]=元素1的值=1 【1】
- partsum[2]=元素1+元素2的值=1+2=3 【1-2】
- partsum[3]=元素3的值=3 【3】
- partsum[4]=partsum[2]+partsum[3]+元素4的值=1+2+3+4=6+4=10 【1-4】
- ……
那么当我们要求前3个数之和的时候,
我们要用【3】+【1-2】= partsum[3]+partsum[2]=3+3=6
要得到上述的Tree,使用lowbit方法进行构建:
对要更新的数,取二进制,对该数+=该数的(为1的)最低位,都进行更新。如要更新1,则需更新1(1),2(10),4(100),8(1000)...直到最后一个数。
求和,则对该数取二进制,累加:对该数-=该数的(为1的)最低位。如要求7,则累加7(111)+6(110)+4(100)
对于本问题:
由于FenwickTree能将变动一个元素,并查找前缀累计的问题,时间复杂度转为log n
那么,我们可以将本问题,转化为:
在给定数组前面再预留queries.size的空位,用来插入所要插入的数字,
插入后,将该数字原来的位置置空即可。其他数字位置不变。
因为要求指定数字的位置,要联系到前缀和,那么我们将存在数字的位置置为1,前缀和,即为该数字目前的位置。
另外,同样我们为了快速定位数字位置,引入pos[]的hash用来记录位置。
下图的
每一行,代表queries中的一次操作。
每一个cell
如第8位-> 3:1 其中3表示数字,并不实际存在tree中,后面的1才是我们要存在tree中的内容。
而pos[3]=8来确定3所在的位置。
代码参考:
1 class FenwickTree { 2 private: 3 vector<int> preSum; 4 int size; 5 int lowbit(int x) { 6 return x&(-x); 7 } 8 public: 9 FenwickTree(int n):preSum(n+1, 0), size(n+1) {}; 10 void update(int idx, int delta) { 11 idx++;//update idx(0~idx)-> update preSum[idx+1] 12 while(idx<size) { 13 preSum[idx] += delta; 14 idx += lowbit(idx); 15 } 16 } 17 int getSum(int idx) { 18 int res = 0; 19 while(idx>0) { 20 res += preSum[idx]; 21 idx -= lowbit(idx); 22 } 23 return res; 24 } 25 }; 26 27 class Solution { 28 public: 29 vector<int> processQueries(vector<int>& queries, int m) { 30 vector<int> res; 31 int n = queries.size(); 32 vector<int> pos(m+1); // pos[1~m]=0~m-1; 33 FenwickTree ft(m+n); 34 for(int i=1; i<=m; i++) { 35 pos[i] = n+i-1; 36 ft.update(pos[i], 1); 37 } 38 for(auto q:queries) { 39 res.push_back(ft.getSum(pos[q])); 40 ft.update(pos[q], -1); 41 pos[q] = n-1; 42 ft.update(pos[q], 1); 43 n--; 44 } 45 return res; 46 } 47 };