📜  解决每个查询后以最小绝对差配对

📅  最后修改于: 2021-04-29 15:26:19             🧑  作者: Mango

给定Q查询和一个空列表。

查询可以有两种类型:

  1. addToList(x):将x添加到列表中。
  2. removeFromList(x):从列表中删除x。

任务是,在每次查询之后,您必须打印abs(list [i] -list [j])的最小值,其中0 <= i <= n,0 <= j <= n且i≠j和n是列表中存在的元素总数。例子

方法1:想法是使用C++中的set来存储所有元素,以便可以在O(log(n))中完成插入或删除操作,同时保持元素的排序顺序。现在,要找到最小差异,我们必须遍历整个集合并找到仅相邻元素之间的差异,因为元素按排序顺序排列,即,最小差异将始终由相邻元素贡献。这可以以O(n)时间复杂度来完成。因此,此方法的总体时间复杂度将为(q * log(n)+ q * n)。

方法2:
我们可以使用多重集来存储和维护所有元素,并使用map来按排序顺序存储相邻元素之间的差异,其中map的键表示相邻元素之间的差异,而对应的map的值表示出现这种差异的频率。

下面是完整的算法:
首先,在多组中插入两个前哨值。假设(-10 ^ 9 + 7和10 ^ 9 + 7)并使用这些哨兵值的差(即2 * 10 ^ 7 + 7)将地图初始化,并将其频率设置为1。

现在,我们有两种类型的查询:

  1. 插入查询:对于插入查询,请首先在集合中插入值。插入元素后,我们还必须更新地图中相邻元素的差异。要做到这一点,发现在一组新插入的元素的的值。早在此新值未插入到集合中时,abs(right-left)会影响地图中的差分项。要在插入新元素后更新地图,请先删除先前的差异abs(右-左),因为在元素之间左右插入了新值,并向地图添加了两个差异。即abs(right-x)和abs(x-left)。
  2. 删除查询:从集合中删除值的情况。首先,需要找到要删除的元素的的元素,例如x。然后根据上述算法,降低abs(右x)和abs(左x)的频率,并增加abs(右-左)的频率。

因此,可以以log(n)的时间复杂度解决每个查询。因此,总体复杂度将为O(q * log(n))。

// C++ implementation of above approach
#include 
#define ll long long int
const ll MOD = 1e9 + 7;
using namespace std;
  
// Function to add an element into the list
void addToList(int x, multiset& s, map& mp)
{
    // firstly insert value in set
    s.insert(x);
  
    // find left and right value of inserted value.
    ll left, right;
  
    // now here is logic explained below
    left = *(--s.find(x));
    right = *(++s.find(x));
    mp[abs(left - x)]++;
    mp[abs(right - x)]++;
    mp[abs(left - right)]--;
    if (mp[abs(left - right)] == 0)
        mp.erase(abs(left - right));
}
  
// Function to remove an element from the list
void removeFromList(int x, multiset& s, map& mp)
{
    ll left, right;
  
    // Find left element of number that we want to delete.
    left = *(--s.find(x));
  
    // Find right element of number that we want to delete.
    right = *(++s.find(x));
  
    // remove x from set
    s.erase(s.find(x));
  
    // Again this will explain below in article.
    mp[abs(left - x)]--;
    if (mp[abs(left - x)] == 0)
        mp.erase(abs(left - x));
    mp[abs(right - x)]--;
    if (mp[abs(right - x)] == 0)
        mp.erase(abs(right - x));
  
    mp[abs(left - right)]++;
}
  
// Driver code
int main()
{
  
    int q = 4;
  
    // define set to store all values.
    multiset s;
  
    // define map to store difference in sorted
    map mp;
  
    // initialize set with sentinel values
    s.insert(-MOD);
    s.insert(MOD);
  
    // maintain freq of difference in map so, here intially
    // difference b/w sentinel value and its freq is one.
    mp[2 * MOD] = 1;
  
    // 1st query 1 1 i.e, include 1 in our set
    addToList(1, s, mp);
  
    // As now we have only one element {-10^9+7, 1, 10^9+7}
    // (except two are sentinel values)
    // so minimum among all pair is zero
    cout << 0 << endl;
  
    // 2nd query 1 5 i.e, include 5 in our set.
    addToList(5, s, mp);
  
    // find smallest difference possible
    // as it should be in beginning of map
    cout << mp.begin()->first << endl;
  
    // 3rd query 1 3 i.e, include 3 in our set.
    addToList(3, s, mp);
  
    // find smallest difference possible
    // as it should be in beginning of map
    cout << mp.begin()->first << endl;
  
    // 4th query 2 3 i.e, remove 3 from our list
    removeFromList(3, s, mp);
  
    cout << mp.begin()->first << endl;
  
 return 0;
}
输出:
0
4
2
4


每个查询的说明:

  1. addToList(1):现在我们只有一个元素{-10 ^ 9 + 7,1,10 ^ 9 + 7}(除了两个是前哨值),因此所有对中的最小值为零。
  2. addToList(5):在集合中插入1和5后,集合变为{-10 ^ 9 + 7,1,5,10 ^ 9 + 7}并映射:mp [5-1] = 1其中键表示差异,它的值代表频率。
  3. addToList(3):在这里我们要插入3。因此,首先,我们在插入后在集合中找到3的左元素和右元素,即left = 1,right =5。由于3在1到5之间,所以我们删除了地图[5-1] –。 (因为3介于1和5之间,所以5-1将不再最小)并且我们包括了这些差异map [3-1] ++和map [5-3] ++(因为3介于1和5之间,所以可能的最小值有两种情况)。
  4. removeFromList(3):与上面的查询类似,这里我们要删除元素。因此,首先找到左元素和右元素为3,即left = 1,right =5。由于我们要删除1和5之间的3,因此最小差异仅会影响3和5,
    before deleting ==>{1, 3, 5}, 
    after deleting ==> {1, 5}. 
    So, map[3-1]--, map[5-3]-- and add map[5-1]++.