web-dev-qa-db-ja.com

連続した番号のリストに要素が欠落しているかどうかを効率的に確認する方法

私はこの配列を持っています

var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];

どのssが欠落しているかを教えてくれるアルゴリズムを見つけようとしていました。ご覧のとおり、リストは連続したss(s1s2など)。

最初にこのソリューションを使用しました:

    var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];
for (var i=1;i<arr.length;i++){
    var thisI = parseInt(arr[i].toLowerCase().split("s")[1]);
    var prevI = parseInt(arr[i-1].toLowerCase().split("s")[1]);
    if (thisI != prevI+1)
      console.log(`Seems like ${prevI+1} is missing. thisI is ${thisI} and prevI is ${prevI}`)
}

ただし、この方法は、連続する複数の番号が欠落している場合は失敗します(s15s16)。そこで、動作するwhileループを追加しました。

var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];
for (var i=1;i<arr.length;i++){
  var thisI = parseInt(arr[i].toLowerCase().split("s")[1]);
  var prevI = parseInt(arr[i-1].toLowerCase().split("s")[1]);
  if (thisI != prevI+1) {
    while(thisI-1 !== prevI++){
       console.log(`Seems like ${prevI} is missing. thisI is ${thisI} and prevI is ${prevI}`)
    }
   }
}

しかし、私は物事を過度に複雑化しているように感じます。理想的な配列を作成することを考えました:

var idealArray = [];
for (var i =0; i<200;i++) {
  idealArray.Push(i)
}

そして、チェックしながら、ループが同じ長さの2つの配列をチェックするように、私の配列(arr)を改ざんします。つまり、このソリューションを使用します:

var idealArray = [];
for (var i =0; i<200;i++) {
  idealArray.Push(i)
}
var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];
for (let i = 0; i<idealArray.length;i++){
  if (parseInt(arr[i].toLowerCase().split("s")[1]) != idealArray[i]) {
    console.log(`Seems like ${idealArray[i]}is missing`);
    arr.splice(i,0,"dummyel")
  }
}

しかし、もう一度、この2番目の配列を作成することもあまり効率的ではないと感じています(大きなリストを考えて、不必要なスペースを無駄にします)。

だから... JavaScriptでこのタスクを効率的に実行するにはどうすればよいですか? (時間の複雑さとスペースの複雑さの両方について、可能な限りO(1)に近いことを意味します。)

28
Adelin

あなたはあなたがシーケンシャル配列を期待していることを知っているので、なぜ数値をループするよりも複雑にする必要があるかはわかりませんarr[0] 使って arr[end]配列のどこにいるかを知るためにカウンターを保持します。これはO(n)で実行されますが、それを改善できるとは思いません。最悪の場合、少なくとも一度はすべての要素を調べる必要があります。

var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];

let first = parseInt(arr[0].substring(1))
let last =  parseInt(arr[arr.length-1].substring(1))
let count = 0
for (let i = first; i< last; i++) {
   if (parseInt(arr[count].substring(1)) == i) {count++; continue}
   else console.log(`seem to be missing ${'s'+i.toString().padStart(2,'0')} between: ${arr[count-1]} and ${arr[count]}` )
}

編集:

以下のコメントについて少し考えた後、配列を分割して各半分をチェックする再帰的なアプローチを作成しました。主に実用的な解決策としてではなく、実験として。実際、これはほとんどの場合nより少ない反復で実行されますが、実際に高速である場合は見つかりませんでした。また、構造を見やすくするためにギャップがある場所を示すインデックスをプッシュしましたそしてテスト。 そして、後で見るように、再帰的であるため、結果は順番どおりではありません。

var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];

let missingGaps = []

function missing(arr, low, high) {
  if (high <= low) return

  let l = parseInt(arr[low].substring(1))
  let h = parseInt(arr[high].substring(1))

  if (h - l == high - low) return
  if (high - low === 1) {
    missingGaps.Push([low, high])
    return
  } else {
    let mid = ((high - low) >> 1) + low
    
    missing(arr, low, mid)

    // need to check case where split might contain gap
    let m = parseInt(arr[mid].substring(1))
    let m1 = parseInt(arr[mid + 1].substring(1))
    if (m1 - m !== 1) missingGaps.Push([mid, mid + 1])

    missing(arr, mid + 1, high)
  }
}

missing(arr, 0, arr.length-1)
missingGaps.forEach(g => console.log(`missing between indices ${arr[g[0]]} and ${arr[g[1]]}`))

別の答えやコメントが改善され、少し速くなるかもしれません。

15
Mark Meyer

理想的な配列ソリューションから理解できるように、最大​​配列サイズ(?)を知っています。したがって、最大値が100で、S00-S99を期待している場合、次のことができます。

var arrayIndex=0;
for (var i =0; i<100;i++) {
   var idealValue="s"+("00"+i).slice(-2); // To get S01-S99
   if(arr.length <= arrayIndex || arr[arrayIndex]!=idealValue){
        console.log(idealValue + 'is missing');
   }
   arrayIndex++;
}

またはそのようなもの。私は今それをテストすることはできません;)しかし、理想的な値のリストを反復処理し、配列内の同じ値を比較します。一致しない場合は印刷します。

6
Veselin Davidov

内側のwhile- loopを使用したソリューションは既に非常に良いようです。不要なifを省略し、毎回前の数値を解析するのではなく、現在見ている数値を追跡します。

このようなもの:

_var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];
var expectedI = 0
for (var i = 0; i < arr.length; i++) {
  var currentI = parseInt(arr[i].toLowerCase().split("s")[1]);
  while (expectedI < currentI) {
    console.log(`Seems like ${expectedI} is missing.`)
    expectedI++
  }
  expectedI = currentI + 1
}_

あなたにあげる:

_Seems like 6 is missing.
Seems like 15 is missing.
Seems like 16 is missing.
Seems like 18 is missing.
Seems like 23 is missing.
Seems like 29 is missing.
Seems like 31 is missing.
Seems like 35 is missing.
Seems like 37 is missing.
Seems like 40 is missing.
Seems like 42 is missing.
Seems like 57 is missing.
Seems like 59 is missing.
Seems like 66 is missing.
Seems like 68 is missing.
_

アイデアは非常に単純です:表示されるはずの番号が表示されない場合は、コンソールに印刷(または他の場所に保存)してから、次の番号に進みます。

リストのすべての要素を少なくとも1回見る必要があるため、O(N)の下にランタイムを取得できないことに注意してください。また、O(N)を印刷する必要がある場合もあります。コンソールに欠けている要素。

上記のアルゴリズムは、リストのすべての要素を1回見て、一定のスペースオーバーヘッドで機能します。

EDIT:vlazによるコメント は、ギャップの少ない配列に対してより高速に動作するアルゴリズムを提案しているようです。ただし、最悪の場合(すべてが欠落している場合)、すべてのN番号を出力する必要があるため、これでも最悪の場合の動作は変わりません。欠落している数値のkNよりも「はるかに小さい」(つまり、Theta(N)にないk)と仮定すると、より効率的なアルゴリズム可能性があります。

5
Andrey Tyukin

これは、指定された配列で、数値シーケンスの一部の要素が欠落しているかどうかを見つけるためのアプローチです。最初のn個の加算を解決する(n *(n + 1))/ 2を使用できます。また、配列がたとえば10で始まる場合、1〜10の合計を削除します。これは、不足しているものではなく、不足しているものがあるかどうかを示すだけです。利点は、配列を並べ替えられない可能性があることです。最小値の計算は、配列全体を注文するよりも安価です。

var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];

let total = 0;

for(let i = 0; i<arr.length; i++){
    arr[i] = parseInt(arr[i].replace("s", ""));
    total += arr[i];
}

let hipFirstSum = ((arr[0]-1)*(arr[0]))/2;  //or minimun
let n = arr[arr.length -1];
let hipSum = (n*(n+1))/2;
let realSum = hipSum - hipFirstSum;

(realSum != total)?console.log("wrong"):console.log("good");
3
Emeeus

配列の2つの要素を取り、存在する場合はギャップを埋めることで配列を縮小できます。

const
    getNumber = s => +s.slice(1),
    pad = i => ('00' + i).slice(-2);

var array = ["s00", "s01", "s02", "s03", "s04", "s05", "s07", "s08", "s09", "s10", "s11", "s12", "s13", "s14", "s17", "s19", "s20", "s21", "s22", "s24", "s25", "s26", "s27", "s28", "s30", "s32", "s33", "s34", "s36", "s38", "s39", "s41", "s43", "s44", "s45", "s46", "s47", "s48", "s49", "s50", "s51", "s52", "s53", "s54", "s55", "s56", "s58", "s60", "s61", "s62", "s63", "s64", "s65", "s67", "s69", "s70"],
    result = [];

array.reduce((left, right) => {
    var l = getNumber(left),
        r = getNumber(right);

    while (++l < r) {
        result.Push('s' + pad(l));
    }
    return right;
});

console.log(result);
3
Nina Scholz

以下は、受け入れられた回答に基づいた再帰的アプローチですが、データをreturnにリファクタリングしています。

var arr = ["s00", "s01", "s02", "s03", "s04", "s05", "s07", "s08", "s09", "s10", "s11", "s12", "s13", "s14", "s17", "s19", "s20", "s21", "s22", "s24", "s25", "s26", "s27", "s28", "s30", "s32", "s33", "s34", "s36", "s38", "s39", "s41", "s43", "s44", "s45", "s46", "s47", "s48", "s49", "s50", "s51", "s52", "s53", "s54", "s55", "s56", "s58", "s60", "s61", "s62", "s63", "s64", "s65", "s67", "s69", "s70"];

function findMissing(arr, l, r) {
  var lval = Number(arr[l].substr(1));
  var rval = Number(arr[r].substr(1));
  // the segment has no gaps
  if (r - l === rval - lval) {
    return [];
  }
  // the segment has exactly two items
  if (r - l === 1) {
    return Array.from({ length: rval - lval - 1 }, function(x, i) {
      return "s" + (lval + 1 + i);
    });
  }
  // calculate middle using integer cast trick
  var m = (l + r) / 2 | 0;
  // process the segments [l, m] and [m, r]
  // note that m is processed twice and requires extra recursion
  // however this eliminates the extra coding needed to handle
  // the case where m and m + 1 are not consecutive
  return findMissing(arr, l, m).concat(findMissing(arr, m, r));
}
var result = findMissing(arr, 0, arr.length - 1);
console.log(result);
2
Salman A

各配列項目をその隣の配列項目と比較するこのようなものに行くことができ、その差が1より大きい場合、その間のすべての数値がログに記録されます。

const arr = ["s00", "s01", "s02", "s03", "s04", "s05", "s07", "s08", "s09", "s10", "s11", "s12", "s13", "s14", "s17", "s19", "s20", "s21", "s22", "s24", "s25", "s26", "s27", "s28", "s30", "s32", "s33", "s34", "s36", "s38", "s39", "s41", "s43", "s44", "s45", "s46", "s47", "s48", "s49", "s50", "s51", "s52", "s53", "s54", "s55", "s56", "s58", "s60", "s61", "s62", "s63", "s64", "s65", "s67", "s69", "s70"];

for (let i = 0; i < arr.length - 1; i++) {
  let currentNum = parseInt(arr[i].split("s")[1]);
  let difference = parseInt(arr[i + 1].split("s")[1]) - currentNum;
  if (difference === 1) continue

  for (let d = 1; d < difference; d++)
    console.log(`Seems likes ${currentNum+d} is missing`)
}

これがあなたのお役に立てば幸いです。

2
Andrew Bone

上記のCプログラムのJavascriptバージョン。欠落している要素のシーケンスを許可するもの。

var util = require( 'util' );

//  Array of data.
var arr = [
        1,  2,  3,  4,  5,  6,  7,  8,  9,
    10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
    20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
    30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
    40, 41, 42, 43,         46, 47, 48, 49,
    50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
    60, 61, 62, 63, 64, 65, 66, 67, 68, 70
];
var arr_len = arr.length;

//  Empty array?
if (arr_len == 0)
{
    console.log(
        util.format(
            "No elements." ));
    process.exit( 0 );
}

//  Pre-check.
var lim = arr[arr_len - 1] - (arr_len - 1);
if (lim == 0)
{
    printf(
        "No missing elements.\n" );
    return 0;
}

//  Initialize binary search.
var lo  = 0;
var hi  = arr_len;
var mid = 0;

//  Search metadata.
var cnt = 0;
var prv = 0;
var val = 0;
var i;

for (i = 0; i < arr_len && cnt < lim; i++)
{
    //  Get mid point of search.
    mid = (lo + hi) >> 1;

    //  Get array value, adjust and do comparisons
    val = arr[ mid ] - cnt;
    if (val === mid)
        lo = mid + 1;
    if (val > mid)
        hi = mid - 1;

    //  Have we found something?
    if (lo > hi)
    {
        //  Yes.  Divide and conquer.
        hi  = arr_len;
        prv = cnt;
        cnt = arr[ lo ] - lo;

        //  Report missing element(s).
        console.log(
            util.format(
                "Missing %d elements @ arr[ %d ] == %d, probes = %d",
                cnt - prv,
                lo,
                arr[ lo ],
                i + 1 ));
    }
}

console.log(
    util.format(
        "Probes: %d",
        i ));
1
John Stevens

このバージョンでは、配列にすべての可能な値が入力され、欠落している値が選択されます。

var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];

var fullArray = Array(71).fill().map((item, index) => "s"+(""+(0 + index)).padStart(2,"0"));

var missingValues = fullArray.filter( ( el ) => !arr.includes( el ) );

console.log(missingValues);

読みやすさと再利用性をもう少し高めます。

var arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];

var prependString = "s";
var numberOfDigits = 2;
var initialNumber = 0;
var finalNumber = 70;

var fullArray = Array(finalNumber - initialNumber + 1)
    .fill()
    .map((item, index) => prependString+(""+(initialNumber + index)).padStart(numberOfDigits,"0"));

var missingValues = fullArray.filter( ( el ) => !arr.includes( el ) );

console.log(missingValues);
1
Bernat

すばやく簡単な解決策は、配列のすべての要素を文字列に結合し、その文字列内を検索することです。

これは、任意のパターン(ハードコードされたs0xパターンは不要)で配列(順序付きまたは順序なし、どんな場合でも正常に動作します)を取るソリューションです。

    const arr = ["s00","s01","s02","s03","s04","s05","s07","s08","s09","s10","s11","s12","s13","s14","s17","s19","s20","s21","s22","s24","s25","s26","s27","s28","s30","s32","s33","s34","s36","s38","s39","s41","s43","s44","s45","s46","s47","s48","s49","s50","s51","s52","s53","s54","s55","s56","s58","s60","s61","s62","s63","s64","s65","s67","s69","s70"];
    let firstIndex = Number(arr[0].replace(/\D/, ''));
    let lastIndex = Number(arr[arr.length-1].replace(/\D/, ''));
    let separator = ',';
    
    let arrString = separator + arr.join(separator) + separator;
    for (let i = firstIndex; i <= lastIndex; i++) {
        let element = arr[0].slice(0, arr[0].length - String(i).length) + i;
    
        if (arrString.indexOf(separator + element + separator) < 0) {
                        console.log(element)
        }
    }
1
Vitaly

ブールの配列を使用して、存在するアイテムを追跡します:

let numbers = arr.map(s => +s.slice(1)); // Convert to numbers
let maximum = Math.max.apply(null, numbers); // Get the maximum
let missing = Array(maximum).fill(true); // start with all missing
let answer = numbers.reduce((p, c) => (p[c] = false, p), missing); // set to false if there
answer.forEach((e,i) =>  (e && console.log(i + " seems to be missing"))); // show answer

ランダムに配置された番号にも機能します。

1
Quentin 2

したがって、重複がなく、エントリが正しいことを知っている場合、リスト内の要素の数をチェックすることから始めるのはかなり簡単なプロセスです。

Arrの長さは、連続する要素の数と等しくする必要がありますか?

***警告:要素がソートされていない場合、または重複している可能性がある場合、これはもちろん機能しません。

条件が適用されると仮定すると、単純なバイナリ検索は最初の欠落要素を見つけます。

その後、分割と征服が行われ、次の行方不明の要素を見つけるために、検索スペースの上部に限定されたバイナリ検索が行われます。再帰アルゴリズムは簡単に理解できますが、単一の関数で行うこともできます。

要素にはリストインデックス間の関係が含まれていることに注意してください。バイナリ検索で、「s08」が要素7にある場合、配列の前の方に要素が欠落していることがわかります。

バイナリ検索は非常に簡単で、要素は固定フィールド文字列であるため、単純な比較方法からも恩恵を受ける可能性があります。

欠落している要素の調整もかなり簡単です。欠落している各要素は、残りの要素を1インデックス左にシフトするため、単純な整数演算になります。

マジ?そんなに難しくない:

#include    <stdio.h>
#include    <stdlib.h>

#define ARY_SZ  68

static
int arr[ARY_SZ] =
{
        1,  2,  3,  4,  5,  6,  7,  8,  9,
    10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
    20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
    30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
    40, 41, 42, 43,     45, 46, 47, 48, 49,
    50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
    60, 61, 62, 63, 64, 65, 66, 67, 68, 70
};

int
main(
    void )
{
    int     i,
            lim,
            lo,
            hi,
            mid,
            val,
            cnt;

    /*  Pre-check.  */
    lim = arr[ARY_SZ - 1] - (ARY_SZ - 1);
    if (lim == 0)
    {
        /*  No missing elements.    */
        printf(
            "No missing elements.\n" );
        return 0;
    }

    /*  Initialize binary search.   */
    lo  = 0;
    hi  = ARY_SZ;
    cnt = 0;

    /*  For (at most) the number of array elements, do: */
    for (i = 0; i < ARY_SZ && cnt < lim; i++)
    {
        /*  Get mid point of search.    */
        mid = lo + hi >> 1;

        /*  Get array value, adjust and do comparisons. */
        val = arr[ mid ] - cnt;
        if (val == mid)
            lo = mid + 1;
        if (val > mid)
            hi = mid - 1;

        if (lo > hi)
        {
            /*  Report missing element. */
            printf(
                "Missing element @ arr[ %d ] == %d, probes = %d\n",
                lo,
                arr[ lo ],
                i );

            /*  Divide and conquer. */
            hi   = ARY_SZ;
            cnt += 1;
        }
    }

    printf(
        "Probes = %d\n",
        i - 1);

    return 0;
}

このCコードをコンパイルして実行した結果:

Missing element @ arr[ 0 ] == 1, probes = 5
Missing element @ arr[ 43 ] == 45, probes = 11
Missing element @ arr[ 67 ] == 70, probes = 16
Probes = 16

そのため、線形検索は不要であり、3つの欠落要素を見つけるために最大16のプローブが必要です。

0
John Stevens