it-swarm.com.ru

Как эффективно проверить, если в списке последовательных номеров отсутствуют какие-либо элементы

У меня есть этот массив

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 (s1, s2 и т.д.).

Сначала я пошел с этим решением:

    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}`)
}

Но этот метод завершается ошибкой, если отсутствует более одного последовательного числа (s15, s16). Поэтому я добавил цикл 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)
}

А затем, проверяя, вмешивайтесь в мой массив (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")
  }
}

Но, опять же, у меня есть ощущение, что создание второго массива тоже не очень эффективно (если подумать о большом списке, я бы потратил ненужное место).

Итак ... как мне эффективно выполнить эту задачу в 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- кажется уже достаточно хорошим, просто пропустите ненужный 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.

Идея очень проста: если вы не видите числа, которое ожидали увидеть, распечатайте его на консоли (или сохраните в другом месте), а затем переходите к следующему номеру.

Обратите внимание, что вы не можете получить время выполнения ниже O(N), потому что вам нужно просмотреть каждый элемент списка хотя бы один раз, и может также случиться, что вам придется вывести O(N) пропущенные элементы на консоль.

Вышеприведенный алгоритм просматривает каждый элемент списка по одному разу и может функционировать с постоянными пробелами.

EDIT: Комментарий от vlaz , кажется, предлагает алгоритм, который должен работать быстрее для массивов с небольшими пробелами. Однако это все равно не меняет поведение в худшем случае, потому что в худшем случае (если все отсутствует) вам все равно придется печатать все числа N. Если вы предполагаете, что число k пропущенных чисел «намного меньше», чем N (т.е. k не в Theta(N)), то могут быть возможны более эффективные алгоритмы.

5
Andrey Tyukin

Это просто подход, позволяющий определить, отсутствует ли в данном массиве какой-либо элемент в числовой последовательности. Мы могли бы использовать (n * (n + 1))/2, которые разрешают сложение по n первым числам. Также, если массив начинается, например, с 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

Вы можете уменьшить массив, взяв два элемента массива и заполнив пробелы, если они существуют.

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

Вот рекурсивный подход, основанный на принятом ответе, но рефакторированный к возврату данным:

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

Javascript версия C-программы выше, та, которая учитывает последовательности отсутствующих элементов.

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» находится в элементе семь, то вы знаете, что ранее в массиве отсутствовал элемент.

Бинарный поиск довольно прост и, вероятно, также выиграет от менее наивного способа сравнения, поскольку элементы являются фиксированной строкой поля.

Настройка недостающих элементов также довольно проста. Каждый отсутствующий элемент сдвигает остальные элементы на один индекс влево, что превращается в простую целочисленную операцию.

Шутки в сторону? Это не так сложно:

#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

Таким образом, нет необходимости в линейном поиске, и для поиска трех отсутствующих элементов требуется максимум 16 проб.

0
John Stevens