私は就職の面接でこの質問をされましたが、正しい答えについて疑問に思っていました。
0からn-1までの数字の配列があり、その数字の1つが削除され、その数字の複製を作成する配列に既にある数字に置き換えられます。この重複を時間内にどのように検出できますかO(n)?
たとえば、1,2,3,4
の配列は1,2,2,4
になります。
時間の簡単な解決法O(n2)は、ネストされたループを使用して、各要素の重複を探します。
元の配列がありますint A[N];
タイプbool B[N]
の2番目の配列bool=false
も作成します。最初の配列を繰り返し、falseの場合はB[A[i]]=true
を設定し、そうでない場合はbing!
これは、O(n)
時間およびO(1)
スペースで実行できます。
(アルゴリズムは、数値が既知の範囲内の連続した整数であるためにのみ機能します):
ベクトルの1回のパスで、すべての数値の合計と、すべての数値の平方和を計算します。
N(N-1)/2
からすべての数値の合計を引きます。これをA
と呼びます。
N(N-1)(2N-1)/6
から平方和を引きます。これをA
で割ります。結果をB
と呼びます。
削除された番号は_(B + A)/2
_であり、置き換えられた番号は_(B - A)/2
_です。
ベクトルは_[0, 1, 1, 2, 3, 5]
_です:
N = 6
ベクトルの合計は0 + 1 + 1 + 2 + 3 + 5 = 12です。N(N-1)/ 2は15です。A= 3。
平方和は0 + 1 + 1 + 4 + 9 + 25 = 40です。N(N-1)(2N-1)/ 6は55です。B=(55-40)/ A = 5。
削除された数は(5 + 3)/ 2 = 4です。
置き換えられた数は(5-3)/ 2 = 1です。
元のベクトル_[0, ..., N-1]
_の合計はN(N-1)/2
です。値a
が削除され、b
に置き換えられたとします。変更されたベクトルの合計はN(N-1)/2 + b - a
になります。修正されたベクトルの合計をN(N-1)/2
から引くと、_a - b
_が得られます。だから_A = a - b
_。
同様に、元のベクトルの平方和はN(N-1)(2N-1)/6
です。変更されたベクトルの二乗の合計はN(N-1)(2N-1)/6 + b2 - a2
です。変更されたベクトルの二乗和を元の和から減算すると、_a2 - b2
_が得られます。これは_(a+b)(a-b)
_と同じです。したがって、それを_a - b
_(つまり、A
)で割ると、_B = a + b
_が得られます。
今_B + A = a + b + a - b = 2a
_およびB - A = a + b - (a - b) = 2b
。
余分なスペースなしでO(N)時間でそれを行うことができます。アルゴリズムの仕組みは次のとおりです。
次の方法で配列を反復処理します。
検出された各要素について、対応するインデックス値を負に設定します。例:a [0] = 2が見つかった場合、a [2]に移動して値を否定します。
これを行うことで、遭遇するようにフラグを立てます。あなたは負の数を持つことはできないことを知っているので、あなたはそれを否定した人であることも知っています。
値に対応するインデックスが既に負のフラグが付けられているかどうかを確認し、はいの場合は重複した要素を取得します。例:a [0] = 2の場合、a [2]に進み、負かどうかを確認します。
次の配列があるとしましょう:
int a[] = {2,1,2,3,4};
最初の要素の後、配列は次のようになります。
int a[] = {2,1,-2,3,4};
2番目の要素の後、配列は次のようになります。
int a[] = {2,-1,-2,3,4};
3番目の要素に到達すると、a [2]に移動し、すでに負の値が表示されます。重複します。
アレイを3回スキャンします。
A
。 XOR 0からN-1までのすべての数値を一緒に-> B
。今度はA XOR B = X XOR D
、Xは削除された要素、Dは重複した要素です。A XOR B
のゼロ以外のビットを選択します。 このビットが設定されているすべての配列要素をXORで結合します-> A1
。 XORこのビットが設定されている0からN-1までのすべての数字を一緒に-> B1
。今ではA1 XOR B1 = X
またはA1 XOR B1 = D
のいずれかです。A1 XOR B1
。見つかった場合、これは重複要素です。そうでない場合、重複する要素はA XOR B XOR A1 XOR B1
です。BitSetを使用することをお勧めします。 Nは配列のインデックス付けに十分小さいため、BitSetは適切なサイズになります。
配列の各要素について、その値に対応するビットを確認します。既に設定されている場合、それは複製です。そうでない場合は、ビットを設定します。
HashSet
を使用して、すでに表示されているすべての数値を保持します。 (償却)O(1)
時間で動作するため、合計はO(N)
です。
@riciは時間とスペースの使用量について正しいです:「これはO(n)時間とO(1)スペースで実行できます。]
ただし、質問はより広範な要件に拡張できます。重複する番号が1つだけである必要はなく、番号が連続していなくてもかまいません。
OJはこのように言います ここ :(注3は明らかに狭めることができます)
各整数が1〜n(両端を含む)のn + 1個の整数を含む配列numsが与えられた場合、少なくとも1つの重複する数値が存在する必要があることを証明します。重複する番号は1つだけであると想定し、重複する番号を見つけます。
注意:
- 配列を変更しないでください(配列は読み取り専用であると想定)。
- 定数O(1)追加スペースのみ)を使用する必要があります。
- ランタイムの複雑さはO(n2)未満でなければなりません。
- 配列には重複した番号は1つしかありませんが、複数回繰り返すことができます。
質問はveryよく説明され回答された ここ キース・シュワルツによる Floyd's cycle-finding アルゴリズム:
この問題を解決するために使用する必要がある主なトリックは、0からn-2の範囲のn個の要素の配列があるため、配列{0、1から関数fを定義していると考えることができることです。 ...、n-1}自体に。この関数は、f(i) = A [i]で定義されます。この設定が与えられると、複製された値は、f(i) = f(j)。したがって、私たちの課題は、このペア(i、j)を見つけることです。それが得られたら、f(i) = A [i]。
しかし、この繰り返される値をどのようにして見つけるのでしょうか?これは、サイクル検出と呼ばれるコンピューターサイエンスでよく研究されている問題であることがわかりました。問題の一般的な形式は次のとおりです。関数fが与えられます。シーケンスx_iを次のように定義します
x_0 = k (for some k)
x_1 = f(x_0)
x_2 = f(f(x_0))
...
x_{n+1} = f(x_n)
Fがドメインからそれ自体にマッピングされると仮定すると、この関数は3つの形式のいずれかを持ちます。まず、ドメインが無限である場合、シーケンスは無限に長く、繰り返しない可能性があります。たとえば、整数の関数f(n) = n + 1にはこのプロパティがあります-番号が重複することはありません。次に、シーケンスは閉ループである可能性があります。 x_0 = x_iになるようなiこの場合、シーケンスは値の固定セットを無期限に循環します。最後に、シーケンスは「ロー型」になる可能性があります。この場合、シーケンスは次のようになります。
x_0 -> x_1 -> ... x_k -> x_{k+1} ... -> x_{k+j}
^ |
| |
+-----------------------+
つまり、シーケンスはサイクルに入る要素のチェーンで始まり、その後無限に循環します。シーケンスで最初に到達したサイクルの最初の要素をサイクルの「エントリ」と示します。
python実装も見つけることができます here :
def findDuplicate(self, nums):
# The "tortoise and hare" step. We start at the end of the array and try
# to find an intersection point in the cycle.
slow = 0
fast = 0
# Keep advancing 'slow' by one step and 'fast' by two steps until they
# meet inside the loop.
while True:
slow = nums[slow]
fast = nums[nums[fast]]
if slow == fast:
break
# Start up another pointer from the end of the array and march it forward
# until it hits the pointer inside the array.
Finder = 0
while True:
slow = nums[slow]
Finder = nums[Finder]
# If the two hit, the intersection index is the duplicate element.
if slow == Finder:
return slow
ハッシュテーブルを使用します。ハッシュテーブルに要素を含めることはO(1)です。
実用的なソリューション:
仮定番号は整数です
[0 .. N]の配列を作成します
int[] counter = new int[N];
次に、読み取りを繰り返し、カウンターをインクリメントします。
if (counter[val] >0) {
// duplicate
} else {
counter[val]++;
}
これは、O(n)
timeおよびO(1)
spaceの代替ソリューションです。 rici's に似ています。少し理解しやすいと思いますが、実際には、より速くオーバーフローします。
X
を欠番とし、R
を繰り返し数とします。
数値は_[1..n]
_からのものであると想定できます。つまり、ゼロは表示されません。実際、配列をループしながら、ゼロが見つかったかどうかをテストし、見つからなければすぐに返すことができます。
今考慮してください:
_sum(A) = n (n + 1) / 2 - X + R
product(A) = n! R / X
_
ここで、product(A)
は、ゼロをスキップするA
のすべての要素の積です。 X
とR
を代数的に導出できる2つの未知数に2つの方程式があります。
Edit:人気のある需要により、ここに解決例があります:
設定しましょう:
_S = sum(A) - n (n + 1) / 2
P = n! / product(A)
_
次に、方程式は次のようになります。
_R - X = S
X = R P
_
次のように解決できます:
_R = S / (1 - P)
X = P R = P S / (1 - P)
_
例:
_A = [0 1 2 2 4]
n = A.length - 1 = 4
S = (1 + 2 + 2 + 4) - 4 * 5 / 2 = -1
P = 4! / (1 * 2 * 2 * 4) = 3 / 2
R = -1 / (1 - 3/2) = -1 / -1/2 = 2
X = 3/2 * 2 = 3
_
public class FindDuplicate {
public static void main(String[] args) {
// assume the array is sorted, otherwise first we have to sort it.
// time efficiency is o(n)
int elementData[] = new int[] { 1, 2, 3, 3, 4, 5, 6, 8, 8 };
int count = 1;
int element1;
int element2;
for (int i = 0; i < elementData.length - 1; i++) {
element1 = elementData[i];
element2 = elementData[count];
count++;
if (element1 == element2) {
System.out.println(element2);
}
}
}
}
配列を走査し、array[abs(array[i])]
の符号を確認します。正の場合は負にし、負の場合は次のように出力します。
import static Java.lang.Math.abs;
public class FindRepeatedNumber {
private static void findRepeatedNumber(int arr[]) {
int i;
for (i = 0; i < arr.length; i++) {
if (arr[abs(arr[i])] > 0)
arr[abs(arr[i])] = -arr[abs(arr[i])];
else {
System.out.print(abs(arr[i]) + ",");
}
}
}
public static void main(String[] args) {
int arr[] = { 4, 2, 4, 5, 2, 3, 1 };
findRepeatedNumber(arr);
}
}
リファレンス:http://www.geeksforgeeks.org/find-duplicates-in-on-on-time-and-constant-extra-スペース/
O(n) time。
#include<iostream>
#include<map>
using namespace std;
int main()
{
int a[]={1,3,2,7,5,1,8,3,6,10};
map<int,int> mp;
for(int i=0;i<10;i++){
if(mp.find(a[i]) == mp.end())
mp.insert({a[i],1});
else
mp[a[i]]++;
}
for(auto i=mp.begin();i!=mp.end();++i){
if(i->second > 1)
cout<<i->first<<" ";
}
}
public void duplicateNumberInArray {
int a[] = new int[10];
Scanner inp = new Scanner(System.in);
for(int i=1;i<=5;i++){
System.out.println("enter no. ");
a[i] = inp.nextInt();
}
Set<Integer> st = new HashSet<Integer>();
Set<Integer> s = new HashSet<Integer>();
for(int i=1;i<=5;i++){
if(!st.add(a[i])){
s.add(a[i]);
}
}
Iterator<Integer> itr = s.iterator();
System.out.println("Duplicate numbers are");
while(itr.hasNext()){
System.out.println(itr.next());
}
}
まず、スキャナークラスを使用して整数の配列を作成します。次に、番号をループで繰り返し、セットに番号を追加できるかどうかを確認します(特定の番号がまだセットに含まれていない場合にのみ番号を追加して設定できます。つまり、セットは重複番号を追加してブール値を返すことはできません重複する値を追加する場合はfalse)。追加できないということは、それが重複していることを意味するので、その重複番号を別のセットに追加して、後で印刷できるようにします。重複番号が数回繰り返される可能性があるため、重複番号をセットに追加することに注意してください。したがって、重複番号は1回だけ追加します。最後に、Iteratorを使用してセットを印刷しています。
次の手順を実行できます。
//これはHashSetアプローチに似ていますが、1つのデータ構造のみを使用します。
int[] a = { 1, 4, 6, 7, 4, 6, 5, 22, 33, 44, 11, 5 };
LinkedHashMap<Integer, Integer> map = new LinkedHashMap<Integer, Integer>();
for (int i : a) {
map.put(i, map.containsKey(i) ? (map.get(i)) + 1 : 1);
}
Set<Entry<Integer, Integer>> es = map.entrySet();
Iterator<Entry<Integer, Integer>> it = es.iterator();
while (it.hasNext()) {
Entry<Integer, Integer> e = it.next();
if (e.getValue() > 1) {
System.out.println("Dupe " + e.getKey());
}
}
このプログラムはc#に基づいており、別のプログラミング言語を使用してこのプログラムを実行する場合、最初に配列を昇順で変更し、最初の要素を2番目の要素と比較する必要があります。
int[] array=new int[]{1,2,3,4,5,6,7,8,9,4};
Array.Sort(array);
for(int a=0;a<array.Length-1;a++)
{
if(array[a]==array[a+1]
{
Console.WriteLine("This {0} element is repeated",array[a]);
}
}
Console.WriteLine("Not repeated number in array");
スライディングウィンドウトリックを使用して配列O(n)を走査する
スペースはO(1)
Arrays.sort(input);
for(int i = 0, j = 1; j < input.length ; j++, i++){
if( input[i] == input[j]){
System.out.println(input[i]);
while(j < input.length && input[i] == input[j]) j++;
i = j - 1;
}
}
テストケースint [] {1、2、3、7、7、8、3、5、7、1、2、7}
出力1、2、3、7
前述のとおり、
0からn-1までの数字の配列があり、その数字の1つが削除され、その数字の複製を作成する配列に既にある数字に置き換えられます。
重複したエントリを除き、配列内の要素がソートされていると想定しています。これがシナリオの場合、次のように簡単に目標を達成できます。
public static void main(String[] args) {
//int arr[] = { 0, 1, 2, 2, 3 };
int arr[] = { 1, 2, 3, 4, 3, 6 };
int len = arr.length;
int iMax = arr[0];
for (int i = 1; i < len; i++) {
iMax = Math.max(iMax, arr[i]);
if (arr[i] < iMax) {
System.out.println(arr[i]);
break;
}else if(arr[i+1] <= iMax) {
System.out.println(arr[i+1]);
break;
}
}
}
HashMapを効率的に使用できます。
Integer[] a = {1,2,3,4,0,1,5,2,1,1,1,};
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
for(int x : a)
{
if (map.containsKey(x)) map.put(x,map.get(x)+1);
else map.put(x,1);
}
Integer [] keys = map.keySet().toArray(new Integer[map.size()]);
for(int x : keys)
{
if(map.get(x)!=1)
{
System.out.println(x+" repeats : "+map.get(x));
}
}
これはO(n) timeおよびO(1) spaceで実行できます。 入力配列を変更せずに
slow = a[0]
fast = a[a[0]]
slow = 0
while(slow != fast){
slow = a[slow];
fast = a[fast];
}
Java実装:
class Solution {
public int findDuplicate(int[] nums) {
if(nums.length <= 1) return -1;
int slow = nums[0], fast = nums[nums[0]]; //slow = head.next, fast = head.next.next
while(slow != fast){ //check for loop
slow = nums[slow];
fast = nums[nums[fast]];
}
if(slow != fast) return -1;
slow = 0; //reset one pointer
while(slow != fast){ //find starting point of loop
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
}