簡體   English   中英

使用 TreeSet 計數反轉 Java

[英]Counting Inversions using TreeSet Java

我正在使用treeSet來解決計數反轉問題。 我正在使用以下方法,它使用在 O(logn) 時間內工作的gnu_pbds

算法

  1. Ordered_Set中插入數組的第一個元素。
  2. 對於arr[]中的所有剩余元素,請執行以下操作:
  • Ordered_Set中插入當前元素。
  • 使用 function order_of_key (arr[i]+1) 在Ordered_Set中找到嚴格小於當前元素 + 1 的元素數。
  • Ordered_Setorder_of_key (current_element + 1) 的大小之間的差異將給出當前元素的反轉計數。

您可以在此處閱讀有關此算法的更多信息。

對於order_of_key方法,我使用 TreeSet 的耳機(k)方法進行計算。

這是我的代碼。

public class Solution {
    
    static int order_of_key(TreeSet<Integer> s, int k)
    {
                return s.headSet(k,true).size();
    }
    public int solve(ArrayList<Integer> a) {
    
        TreeSet<Integer> s = new TreeSet<>();
        s.add(a.get(0) );
        int invcount = 0;
        for(int i=1;i<a.size();i++)
        {
            s.add(a.get(i) );
            int key = order_of_key(s, a.get(i)+1);
            // if(i+1 == a.size() ) key--;
            invcount+= s.size() - key;
            
           // System.out.println(s+" "+(a.get(i)+1)+" "+ key+" "+invcount);
                  
        }
        return invcount;
        
    }
}

相應的C++代碼

// Ordered set in GNU C++ based 
// approach for inversion count 
#include <bits/stdc++.h> 
#include <ext/pb_ds/assoc_container.hpp> 
#include <ext/pb_ds/tree_policy.hpp> 
using namespace __gnu_pbds; 
using namespace std; 

// Ordered Set Tree 
typedef tree<int, null_type, less_equal<int>, 
            rb_tree_tag, 
            tree_order_statistics_node_update> 
    ordered_set; 

// Returns inversion count in 
// arr[0..n-1] 


void print(ordered_set s,int n){
  
 for(int i=0;i<n;i++)
 {
  // printf("%d ",s[i]);// <<endl; 
 cout << *(s.find_by_order(i))  
         << " ";
 }
  //cout<<endl;
}

int getInvCount(int arr[], int n) 
{ 
    int key; 
    // Intialise the ordered_set 
    ordered_set set1; 

    // Insert the first 
    // element in set 
    set1.insert(arr[0]); 

    // Intialise inversion 
    // count to zero 
    int invcount = 0; 

    // Finding the inversion 
    // count for current element 
    for (int i = 1; i < n; i++) { 
        set1.insert(arr[i]); 
        // Number of elements strictly 
        // less than arr[i]+1 
        key = set1.order_of_key(arr[i] + 1); 

        // Difference between set size 
        // and key will give the 
        // inversion count 
        invcount += set1.size() - key; 
        print(set1,n);
      cout<<arr[i]+1<<" ";
      cout<<key<<" ";
      
        cout<<" "<<invcount<<endl;
        
    } 
    return invcount; 
} 

// Driver's Code 
int main() 
{ 
    int arr[] = { 32, 35, 43, 1, 38, 39, 42 }; 
    int n = sizeof(arr) / sizeof(int); 
     cout<<n<<endl;
    // Function call to count 
    // inversion 
    cout << getInvCount(arr, n); 
    return 0; 
} 

觀察

  • 我的解決方案適用於所有測試用例,但對於一些瑣碎的測試用例,它返回1- inversion count 如果我將order_of_key的工作更改為以下內容,它會破壞其他一些測試用例。
int order_of_key(TreeSet<Integer> s, int k)
    {
            if(s.contains(k))
                return s.headSet(k).size();
            else
                return s.headSet(k,true).size();
    }
  • gnu_pbds有效。

請幫助我使用TreeSet修復此代碼。 另外,如果我遺漏了關於pbds order_of_key 方法的其他幾點,請告訴我。

order_of_key function 返回小於提供的鍵的元素數。 在您的 C++ 代碼中,您將元素添加到集合中,然后調用order_of_key元素加一,以包含您剛剛添加的元素。

在 Java 代碼中保留這種安排,使用 Java TreeSet 忠實地實現類似order_of_key的方法是調用

return s.headSet(k, false).size();

或者干脆

return s.headSet(k).size();

因為其中任何一個都返回包含嚴格小於k元素的頭集的大小。 無需先進行包含檢查。 當我使用 C++ 代碼中的輸入數組運行它時,我得到相同的中間結果和最終結果的反轉次數:6。

請注意,Java 的 TreeSet 還包括創建尾集的能力,即大於提供的元素的元素。 事實證明(在我看來)這是一種更簡單的計算反轉次數的方法。 在添加元素之前,大於當前元素的尾集的大小是相對於該元素的反轉次數。 由於我們希望尾集嚴格大於當前元素,因此我們使用tailSet(k, false) 然后我們可以對輸入列表中的每個元素執行此操作:

int inversions(List<Integer> a) {
    var s = new TreeSet<Integer>();
    int invcount = 0;
    for (int k : a) {
        invcount += s.tailSet(k, false).size();
        s.add(k);
    }
    return invcount;
}

inversions(List.of(32, 35, 43, 1, 38, 39, 42)) // result is 6

更新 2020-06-24

上面的代碼僅適用於具有唯一值的輸入。 如果輸入具有重復值,則它不起作用。 我注意到在 C++ 代碼中,使用less_equal<int>比較的樹 function 保留重復項,而使用less<int>壓縮重復項。

保持重復很重要的原因是每個元素——即使它是重復的——都可以算作倒置。 因此,[2, 2, 1] 的輸入被認為有兩個反轉。 Java TreeSet 壓縮了重復項,因此我們必須做一些額外的工作來保存它們。

允許“重復” int 值的一種方法是以某種方式使它們唯一。 這可以通過創建一個新的 object 來包含與始終遞增的計數器配對的 int 值來完成。 重復值是唯一的,因為它們將具有不同的計數器值。 這是執行此操作的 class:

static class UniqInt implements Comparable<UniqInt> {
    static int count = 0;
    final int value;
    final int uniq;
    UniqInt(int value) { this.value = value; uniq = count++; }
    public int compareTo(UniqInt other) {
        int c = Integer.compare(this.value, other.value);
        return c != 0 ? c : Integer.compare(this.uniq, other.uniq);
    }
}

請注意,這里的compareTo方法比較值和“uniq”計數器值,因此創建多個 UniqInt 實例將彼此不同,並且將完全有序。 一旦我們有了這個 class,我們基本上做同樣的事情,除了我們在 TreeSet 中跟蹤 UniqInt 而不是 Integer 對象。

int inversions1(List<Integer> a) {
    var s = new TreeSet<UniqInt>();
    int invcount = 0;
    for (int k : a) {
        var u = new UniqInt(k);
        invcount += s.tailSet(u, false).size();
        s.add(u);
    }
    return invcount;
}

對於評論中提供的輸入(包括重復值),

84, 2, 37, 3, 67, 82, 19, 97, 91, 63, 27, 6, 13, 90, 63, 89, 100, 60, 47, 96, 54, 26, 64, 50, 71, 16、6、40、84、93、67、85、16、22、60

這給出了 290 的預期結果。

然而,這有點脆弱。 請注意,創建具有相同值的新 UniqInt 始終會創建一個大於任何現有實例的實例,因為計數器始終遞增。 (直到你創建了 2^31 個。)當新實例被創建時,它的尾部將永遠不會包含任何已經在 TreeSet 中的重復值。 這對於這個小例子來說可能是合理的,但如果這是一個更大系統的一部分,我會更仔細地考慮如何獲得相對於某個值的正確的 head-set 或 tail-set,而不必依賴大多數最近創建的 UniqInt 比所有以前的都大。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM