さまざまな状況で何度もこの問題に直面しました。私はCまたはJavaに慣れていますが、すべてのプログラミング言語に共通です。
2つの配列(またはコレクション)を考えてみましょう。
char[] A = {'a', 'b', 'c', 'd'};
char[] B = {'c', 'd', 'e', 'f'};
2つの配列間の共通要素を新しい配列として取得するにはどうすればよいですか?この場合、配列AとBの共通部分はchar[] c = {'c', 'd'}
です。
ある配列が他の配列内で繰り返されることを避けたいため、実行時間が(Aの長さとBの長さ)増加します。これは、巨大な配列の場合は多すぎます。
共通の要素を取得するために各配列で単一のパスを実行する方法はありますか?
これは文字列アルゴリズムのように見えるので、このシーケンス(したがって文字列)を並べ替えることができないとしばらく仮定します。次に、 Longest Common Sequence algorithm(LCS)
入力サイズが一定であると仮定すると、問題はO(nxm)、(2つの入力の長さ)の複雑さを持ちます。
foreach element e in array A
insert e into hash table H
foreach element e in array B
if H contains e
print e
このアルゴリズムは、時間でO(N)
、空間でO(N)
です。
余分なスペースを避けるために、ソートベースのアプローチを使用できます。
効率の下限はO(n)です-少なくともすべての要素を読み取る必要があります。次に、いくつかのアプローチがあります。
配列2の配列1からすべての要素を検索します。時間の複雑さO(n ^ 2)。
配列1のみをソートしてから、バイナリ検索を使用して配列2から要素を検索する必要があります。時間の複雑さ:ソートO(nlogn)、検索O(n * logn)= O(nlogn)、合計O(nlogn)。
配列1要素からハッシュテーブルを作成します。ハッシュテーブルの2番目のテーブルから要素を検索します。時間の複雑さはハッシュ関数に依存します。最適な場合の検索ではO(1)を実現できます(すべての要素は異なるハッシュ値を持ちます)が、最悪の場合はO(n)を実現できます(すべての要素は同じハッシュ値)。合計時間の複雑さ:O(n ^ x)、ここでxはハッシュ関数の効率の要因です(1〜2)。
一部のハッシュ関数は、衝突なしでテーブルを構築することが保証されています。しかし、建物はすべての要素に対して厳密にO(1)時間を必要としません。ほとんどの場合O(1)になりますが、テーブルがいっぱいになった場合や衝突が発生した場合は、O(n)時間をかけてテーブルを再ハッシュする必要があります。これはそれほど頻繁には発生せず、クリーンアドよりはるかに少ない頻度で発生します。 AMORTIZED時間の複雑さはO(1)です。追加の大部分がO(n)時間かかる限り、一部の追加にO(1)時間かかることは気にしません。
それでも、極端な場合、テーブルは挿入ごとに再ハッシュする必要があるため、厳密な時間の複雑さはO(n ^ 2)になります。
私が知っているいくつかの言語には、あなたが望むものを正確に行ういくつかのメソッドがありますが、これらの実装のいくつかを検討することを検討しましたか?
PHP- array_intersect()
$array1 = array("a" => "green", "red", "blue");
$array2 = array("b" => "green", "yellow", "red");
$result = array_intersect($array1, $array2);
print_r($result);
>> green
red
Java- List.retainAll
Collection listOne = new ArrayList(Arrays.asList("milan","dingo", "elpha", "hafil", "meat", "iga", "neeta.peeta"));
Collection listTwo = new ArrayList(Arrays.asList("hafil", "iga", "binga", "mike", "dingo"));
listOne.retainAll( listTwo );
System.out.println( listOne );
>> dingo, hafil, iga
public static void main(String[] args) {
char[] a = {'a', 'b', 'c', 'd'};
char[] b = {'c', 'd', 'e', 'f'};
System.out.println(intersect(a, b));
}
private static Set<Character> intersect(char[] a, char[] b) {
Set<Character> aSet = new HashSet<Character>();
Set<Character> intersection = new HashSet<Character>();
for (char c : a) {
aSet.add(c);
}
for (char c : b) {
if (aSet.contains(c)) {
intersection.add(c);
}
}
return intersection;
}
int s[256] // for considering all ascii values, serves as a hash function
for(int i=0;i<256;i++)
s[i]=0;
char a[]={'a','b','c','d'};
char b[]={'c','d','e','f'};
for(int i=0;i<sizeof(a);i++)
{
s[a[i]]++;
}
for(int i=0;i<sizeof(b);i++)//checker function
{
if(s[b[i]]>0)
cout<<b[i];
}
complexity O(m+n);
m- length of array a
n- length of array b
これにはすでに多くの良い答えがありますが、レイジーコーディング用のライブラリを使用したワンライナーアプローチが必要な場合は、 Google Guava (Javaの場合)および Sets.intersection
メソッド。
(手元にコンパイラはありません、我慢してください)
char[] A = {'a', 'b', 'c', 'd'};
char[] B = {'c', 'd', 'e', 'f'};
Set<Character> intersection = Sets.intersection(
Sets.newHashSet<Character>(Chars.asList(a)),
Sets.newHashSet<Character>(Chars.asList(b))
);
明らかに、これは両方の配列に重複がないことを前提としています。その場合、特に最初からプリミティブの配列から開始しない場合は、セットデータ構造を使用するとより意味があり、この種の操作がより効率的になります。
あなたのユースケースに合うかもしれないし、そうでないかもしれないが、一般的なケースのための一種の簡単なアプローチ。
重複が気になる場合は、ハッシュマップを使用してインデックスをリストAに設定します。キーは要素で、値はその要素が表示された回数です。
Aの最初のすべての要素を反復処理し、マップに存在しない場合は値1でそこに配置し、マップに既に存在する場合はその値に1を追加します。
次に、Bを反復処理し、値が存在する場合は1を減算します。存在しない場合は、その要素のテーブルの値に-1を入れます。
最後に、マップを反復処理し、値が!= 0の要素については、差として出力します。
private static <T> List<T> intersectArrays(List<T> a, List<T> b) {
Map<T, Long> intersectionCountMap = new HashMap<T, Long>((((Math.max(a.size(), b.size()))*4)/3)+1);
List<T> returnList = new LinkedList<T>();
for(T element : a) {
Long count = intersectionCountMap.get(element);
if (count != null) {
intersectionCountMap.put(element, count+1);
} else {
intersectionCountMap.put(element, 1L);
}
}
for (T element : b) {
Long count = intersectionCountMap.get(element);
if (count != null) {
intersectionCountMap.put(element, count-1);
} else {
intersectionCountMap.put(element, -1L);
}
}
for(T key : intersectionCountMap.keySet()) {
Long count = intersectionCountMap.get(key);
if (count != null && count != 0) {
for(long i = 0; i < count; i++) {
returnList.add(key);
}
}
}
return returnList;
}
これはO(n)
で実行する必要があります。リストを1回ずつ、マップを1回だけ繰り返しているからです。ここでJavaで使用されるデータ構造は、HashMap
がリストの最大サイズを処理できる容量で構築されているため、効率的でなければなりません。
戻り値にLinkedList
を使用しています。これは、サイズが不明な交差点のリストを追加および反復する方法を提供するためです。
漸近的に、これにはソートの複雑さがかかります。すなわちO(NlogN)ここで、Nはより長い入力配列の長さです。
最初に、最適なソートアルゴリズムを使用して2つの配列をソートします。
次に、線形検索を使用して、共通の要素を取得できます。
余分なスペースが提供される場合、ハッシュテーブルを使用してそれを行うことができます。
最初に2つの配列を並べ替え、次にそれらを繰り返します。それらが同じ要素である場合は、追加して配列を返します。
コードはこちら:
public static void printArr(int[] arr){
for (int a:arr){
System.out.print(a + ", ");
}
System.out.println();
}
public static int[] intersectionOf(int[] arr1, int[] arr2){
Arrays.sort(arr1);
Arrays.sort(arr2);
printArr(arr1);
printArr(arr2);
int i=0, j=0, k=0;
int[] arr = new int[Math.min(arr1.length, arr2.length)];
while( i < arr1.length && j < arr2.length){
if(arr1[i] < arr2[j]){
i++;
} else if(arr1[i] > arr2[j]){
j++;
} else {
arr[k++] = arr1[i++];
j++;
}
}
return Arrays.copyOf(arr, k);
}
public static void main(String[] args) {
int[] arr1 = {1, 2, 6};
int[] arr2 = {10, 2, 5, 1};
printArr(intersectionOf(arr1,arr2));
}
出力:
arr1: 1, 2, 6,
arr2: 1, 2, 5, 10,
arr: 1, 2,
最善の方法は、配列をまったく使用しないことです。配列は、要素へのランダムアクセスには最適ですが、検索には最適ではありません(これが交差点の検出です)。 intersectionについて話しているように、配列をセットと見なす必要があります。したがって、より適切なデータ構造(JavaではSet
)を使用します。その後、タスクははるかに効率的です。
Rubyで言うことができます
a = ['a', 'b', 'c', 'd']
b = ['c', 'd', 'e', 'f']
c = a & b
cには['c'、 'd']が含まれます
ツリーを使用できますが、時間はO(n(log n))になり、要素は同等でなければなりません
import Java.util.Scanner;
パブリッククラスarraycommon {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
// display common element in two diffrent array
int sizea,sizeb,i=0,j=0,k=0;
int count=0;
System.out.println("enter the size array A:"+'\n');
sizea=sc.nextInt();
System.out.println("enter the size array B"+'\n');
sizeb=sc.nextInt();
int a[]=new int[sizea];
int b[]=new int[sizeb];
int c[]=new int[sizea];
System.out.println("enter the element in array A:"+'\n');
for (i = 0; i < sizea; i++) {
a[i]=sc.nextInt();
}
System.out.println("enter the element in array B:"+'\n');
for (i = 0; i < sizeb; i++) {
b[i]=sc.nextInt();
}
System.out.println("the element in array A:"+'\n');
for (i = 0; i < sizea; i++) {
System.out.print(a[i]+" ");
}
System.out.println('\n');
System.out.println("the element in array B:"+'\n');
for (i = 0; i < sizeb; i++)
{
System.out.print(b[i]+" ");
}
for (i = 0; i <sizea; i++)
{
for (j = 0; j < sizeb; j++)
{
if(a[i]==b[j])
{
count++;
c[k]=a[i];
k=k+1;
}
}
}
System.out.println('\n');
System.out.println("element common in array is");
if(count==0)
{
System.out.println("sorry no common elements");
}
else
{
for (i = 0; i <count; i++)
{
System.out.print(c[i]+" ");
}
}
}
}
ANSI文字を扱っていると仮定します。アプローチはユニコードでも同様で、範囲を変更するだけです。
char[] A = {'a', 'b', 'c', 'd'};
char[] B = {'c', 'd', 'e', 'f'};
int[] charset = new int[256]
for(int i=0; i<A.length; i++) {
charset[A[i]]++;
}
ここでBを反復処理し、反復される文字に対応する文字セット値が0より大きいかどうかを確認できます。それらをリストまたは他のコレクションに保存できます。
このアプローチは、O(n)時間の複雑さと、共通要素を保持するために使用される新しい配列/リストを考慮しないチェックのための一定のスペースを取ります。
これは、スペースの複雑さの点で、HashSet/Hashtableアプローチよりも優れています。
配列の1つ(m Log(m))をソートします。他の配列から各要素を選択し、最初の配列(ソートされたもの)でバイナリ検索を実行します-> n Log(m)
合計時間の複雑さ:-(n + m)Log(m).
Java 8機能を使用して、リストをセットに変える代わりにリスト内の重複を優先するアルゴリズムを以下に示します。並べ替えがないため、n log n
はありません。
したがって、全体のコストはO(n)です。コード:
import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.List;
import Java.util.Map;
import Java.util.stream.Collectors;
public class Dup {
public static void main(String[] args) {
List<Integer> listA = Arrays.asList(3, 1, 4, 1, 9, 5, 9);
List<Integer> listB = Arrays.asList(2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3);
findCommons(listA, listB);
}
static void findCommons(List<Integer> listA, List<Integer> listB) {
Map<Integer, Long> mapA =
listA.stream().collect(
Collectors.groupingBy(Integer::intValue, Collectors.counting()));
List<Integer> commons = new ArrayList<>();
listB.stream()
.filter(e -> mapA.get(e) != null)
.filter(e -> mapA.get(e) > 0)
.forEach(e -> {
mapA.put(e, mapA.get(e) - 1);
commons.add(e);
});
System.out.println(commons);
}
}
上記のコードは次の出力を提供します:[5, 3, 9, 9]
。
以下が役に立つことを願っています。これらは2つの異なるアプローチです。
ある配列のすべての要素を別の配列と比較する単純な交差点。
1つの配列をソートし、バイナリ検索を使用して最初の配列の2番目の配列要素を検索するソートおよび検索ベースのアプローチ。
//
public class IntersectionOfUnsortedArrays {
public static void main(String[] args) {
int[] arr1 = { 12, 4, 17 };
int[] arr2 = { 1, 12, 7, 17 };
System.out.println("Intersection Using Simple Comparision");
printArray(simpleIntersection(arr1, arr2));
System.out.println("Intersection Using Sort and Binary Search");
printArray(sortingBasedIntersection(arr1, arr2));
}
/*
* Simple intersection based on the comparison without any sorting.
* Complexity O(n^2)
*/
public static int[] simpleIntersection(int[] a, int[] b) {
int minlen = a.length > b.length ? b.length : a.length;
int c[] = new int[minlen];
int k=0;
for(int i=0;i<a.length;i++){
for(int j=0;j<b.length;j++){
if(a[i]==b[j]){
c[k++]=a[i];
}
}
}
int arr[] = new int[k];
// copy the final array to remove unwanted 0's from the array c
System.arraycopy(c, 0, arr, 0, k);
return arr;
}
/*
* Sorting and Searching based intersection.
* Complexity Sorting O(n^2) + Searching O(log n)
*/
public static int[] sortingBasedIntersection(int[] a, int[] b){
insertionSort(a);
int minlen = a.length > b.length ? b.length : a.length;
int c[] = new int[minlen];
int k=0;
for(int i=0;i<b.length;i++){
int result = binarySearch(a,0,a.length,b[i]);
if(result > -1){
c[k++] = a[result];
}
}
int arr[] = new int[k];
// copy the final array to remove unwanted 0's from the array c
System.arraycopy(c, 0, arr, 0, k);
return arr;
}
public static void insertionSort(int array[]) {
for (int i = 1; i < array.length; i++) {
int j = i;
int b = array[i];
while ((j > 0) && (array[j - 1] > b)) {
array[j] = array[j - 1];
j--;
}
array[j] = b;
}
}
static int binarySearch(int arr[], int low, int high, int num) {
if (high < low)
return -1;
int mid = (low + high) / 2;
if (num == arr[mid])
return mid;
if (num > arr[mid])
return binarySearch(arr, (mid + 1), high, num);
else
return binarySearch(arr, low, (mid - 1), num);
}
public static void printArray(int[] array) {
for (int value : array) {
System.out.print(" "+value);
}
System.out.println("\n");
}
}
</ code>
.NET 3.5以降でHashSetを使用できます。 C#コードの例:
HashSet<int> set1 = new HashSet<int>(new int[]{8, 12, 13, 15});
HashSet<int> set2 = new HashSet<int>(new int[] { 15, 16, 7, 8, 9 });
set1.IntersectWith(set2);
foreach (int i in set1)
Console.Write(i+ " ");
//出力:8 15
simply search each element of first array with each element of second array and stored matched result in third array
class Union
{
public static void main(String[] args) {
char a[] ={'f','g','d','v','a'};
char b[] ={'a','b','c','d','e'};
char temp[] = new char[5];
int p=0;
for(int i=0;i<a.length;i++)
{
for(int j=0;j<b.length;j++)
{
if(a[i]==b[j]) //searches if both array has common element
{
temp[p] = a[i]; //if match found store it in a new array
p++;
}
}
}
for(int k=0;k<temp.length;k++)
{
System.out.println(temp[k]);
}
}
}
質問に示されているように、コレクションが既にソートされている場合、最良のソリューション(まだ言及されていません)は、O(n + m)で実行されるマージソートのようなアルゴリズムです。
各コレクションの最初の要素を比較します。同じ場合は、要素を交差セットに追加し、コレクションから両方の要素をポップします。要素が異なる場合は、他の要素よりも大きい要素をポップします。 1つのコレクションが空になるまで繰り返します。