Il problema del carico di lavoro appartiene a una classe di problemi informatici?

2

Sono andato per un colloquio e ho avuto un problema con il carico di lavoro:

Problem: write a function to tell whether a series of workloads will exceed 
         the maximum workload or not

Input: MaxWorkLoad: example 10
       Timeslot and workload: example [(2, 6, 3), (3, 8, 2), ... ]
       The (2, 6, 3) is begin time, end time, and workload
       And it means from time 2 to time 6, the workload is 3
       You can treat the 2, 6 as the UNIX epoch time.
       The time may not be integers, so instead of 2, it can be 2.2
       The input can be in any time order. For example: [(20, 60, 3), (3, 8, 2)]
       The workload will "add up", so a 3 and 2 will add up to 5

Output: a boolean indicating whether the series of workload can fit in without
        exceeding MaxWorkLoad 

La domanda breve è: questo problema del carico di lavoro appartiene a una classe di algoritmi e quando la matrice è vuota, ma i dati continuano a venire in M di volte e dobbiamo dire possibile o non M di volte, c'è una soluzione migliore di O(M * M) ?

dettagli:

Se mi concentro su come determinare se gli intervalli di tempo si sovrapporranno l'uno con l'altro, si scopre che non è una soluzione facile.

Quindi non sono sicuro che sia adatto come domanda per un colloquio, perché potresti sapere come risolverlo o non lo fai. Se l'hai già visto, lo risolverai come un gioco da ragazzi. Se non l'hai visto prima, non credo che 20 minuti potrebbero essere il tempo sufficiente per sbloccare.

Potresti voler pensare a come potresti risolverlo, se vuoi divertirti.

La soluzione semplice, che potrei inventare, ma dopo 15 minuti dopo, in realtà, può essere: usa semplicemente un dizionario e usa il limite di tempo come chiave, e se è (2, 6, 3) , quindi segna come dict[2] = 3 e dict[6] = -3 .

Allo stesso modo, per (3, 8, 2) , quindi dict[3] = 2 e dict[8] = -2

(e in realtà, se consideriamo l'endpoint temporale come inclusivo, allora non avremo dict[8] = -2 ma abbiamo dict[9] = -2 , trattandolo come un calo del carico di lavoro al momento 9 anziché a 8 )

E poi, una volta che hai l'intero dizionario, ora esegui il loop di ogni chiave nel dizionario, in ordine ordinato, e mantieni un numero CurrentWorkLoad come carico di lavoro. Pertanto, quando vedi dict[2] come 3 , aggiungi 3 a CurrentWorkLoad e quando vedi dict[3] , aggiungi 2 a CurrentWorkLoad e quando vedi dict[6] , aggiungi% da-3 a CurrentWorkLoad .

Quindi, non appena CurrentWorkLoad è maggiore di MaxWorkLoad , puoi immediatamente restituire false . Altrimenti, alla fine del ciclo, restituisci semplicemente true .

E se ci fosse (2, 6, 3) e (6, 8, 1) , il che significa che l'endpoint può "sovrapporsi" al momento 6 ? Quindi, mi sono inventato, o usare un array per ricordare tutti i valori quando collide a 6 , o, semplicemente, sommare i valori. Quindi la prima volta che vedi (2, 6, 3) , poi dict[6] is -3 e quando vedi (6, 8, 1) , poi dict[6] += 1 e diventa -2 .

Quindi, se in JavaScript, è come

dict[beginTime] ||= 0;    // if not defined, then set it to 0
dict[beginTime] += workload;

dict[endTime] ||= 0; 
dict[endTime] -= workload;

e il resto dell'algoritmo rimarrà lo stesso.

Quindi la complessità temporale per la dimensione dell'array N è O(N log N) , perché abbiamo bisogno di ordinare le chiavi.

L'intervistatore mi ha chiesto, e se questa operazione fosse ripetuta M volte?

Quindi, ad esempio, se l'array iniziale è vuoto, ma i dati continuano ad arrivare, per M volte e M può essere un milione o dieci milioni. Allora qual è la complessità del tempo? Inizialmente ho detto che è O(M * M log M) , ma in seguito ho scoperto che potrebbe essere O(M * M) , perché non abbiamo bisogno di ordinare le chiavi ogni volta. Possiamo semplicemente "inserire" la chiave in una lista già ordinata.

Esiste una classe di algoritmo o di problem solving correlata a questa e una soluzione migliore di O(M * M) ?

    
posta 太極者無極而生 19.01.2017 - 20:20
fonte

6 risposte

3

Chiedere "il problema del carico di lavoro appartiene a una classe di algoritmi" non ha molto senso - il problema in sé non è un algoritmo. Tuttavia, presumo che la tua vera domanda sia quale classe di algoritmi la tua soluzione proposta appartiene.

Lo vedo nella categoria degli algoritmi della linea di scansione . Sebbene il tuo non sia in realtà un problema geometrico, l'idea principale è la stessa: prendi i punti rilevanti "dove qualcosa cambia" sull'asse x (qui la linea temporale), li ordina in ordine ascendente e poi li elabora da sinistra a destra. In questa fase di elaborazione, una sorta di "stato" viene cambiato in ogni momento rilevante. Nei problemi geometrici, questo tipo di stato è spesso un insieme geometrico di punti o qualcosa di simile, nel tuo caso si tratta semplicemente del carico di lavoro totale.

L'unica cosa che cambierei nell'algoritmo è che non utilizzerei un dizionario come contenitore. Un semplice elenco di coppie numeriche ("point in time", "workload change") lo farà, ordinato in base al loro primo valore. Ciò non cambia molto con la tua soluzione, specialmente non con il tempo di esecuzione, ma evita i problemi con le chiavi duplicate.

    
risposta data 20.01.2017 - 10:16
fonte
2

Non sei sicuro di cosa intendi per dati in arrivo per M di volte. Se ipotizziamo un flusso infinito di N timeslot / tripletti di carico di lavoro. Il meglio che ho trovato è anche O(N*N) . La mia principale differenza è che non uso un dizionario / hashtable.

Idea di base: inizialmente abbiamo 0 timeslots e non vi è alcuna violazione di MaxWorkLoad . In ogni nuova finestra temporale, trova tutti i timeperiod sovrapposti e controlla se la nuova aggiunta causa una violazione. Nel peggiore dei casi, tutte le voci attuali si sovrappongono.

Se utilizziamo una struttura ordinata, possiamo ordinare le voci principalmente per orario di inizio, con tempo di fine secondario (e per il più grande carico di lavoro di microottimizzazione come terza priorità). Quindi quando aggiungiamo un nuovo timeslot (xBegin, xEnd, workload) possiamo trovare il limite destro (e anche l'inserimento) in log N tempo, confrontando il xEnd con yBegin per il y già inserito. Quindi sommiamo il carico di lavoro rimasto fino alla fine (dobbiamo). Se la somma in qualsiasi punto è maggiore di MaxWorkLoad , restituisce true altrimenti restituisce false. Nel peggiore dei casi, dobbiamo sommare ogni volta%% di elementi diN, che avremmo comunque se passassimo tutto il ciclo.

Se la granularità del carico di lavoro è fissa, ad esempio gli interi e M è il carico di lavoro massimo, questo cambia in O(min(N*M, N*N)) , perché durante la somma al massimo M le voci saranno elaborate.

Non penso che in generale tu possa fare meglio di O(N*N) . Devi ricordare tutti i valori di N ricevuti in qualsiasi momento (a meno che non concordino sull'ora di inizio e di fine), al fine di determinare eventuali violazioni future.

Il problema più correlato che riesco a pensare è il problema di assegnazione della sala conferenze (in basso) che è O(N*N) . Per convertire (xBegin, xEnd, K) , per attività di lezione, crea K copie di (xBegin, xEnd) . Andando nella direzione opposta, possiamo ridurre la variante decisionale del problema di assegnazione della lezione al tuo problema. Per ogni attività, creare un periodo di applicazione con il carico di lavoro impostato su 1 . Il problema della decisione della sala conferenze è soddisfacente con P sale, se il tuo problema corrispondente è soddisfacente con MaxWorkLoad = P . Se riteniamo che la versione decisionale del problema di una lezione sia anche almeno O(N*N) , allora il problema è il tuo problema.

    
risposta data 19.01.2017 - 23:11
fonte
2

In realtà pensavo a una possibile soluzione che dovrebbe essere O(M log M) , ma è dopo un po 'di riflessione sull'obiettivo dell'ottimizzazione. Non penso che i 20 minuti corti in un'intervista significano davvero qualcosa, che ci pensi o no. A volte hai anche voluto essere più socievole e amichevole quando parli durante l'intervista, quindi il lato logico di te potrebbe non funzionare a tutta velocità.

Ecco la mia soluzione:

  1. Rompi il punto dati (123, 456.7, 10) in due voci: (123, 10) e (456.7, -10) per indicare l'aumento del carico di lavoro e la diminuzione del carico di lavoro.

  2. Ora, mantieni una struttura dati di: in quel momento, il carico di lavoro diventa il valore. Ad esempio, quando vedi (123, 10) , quindi imposta o aumenta workload[123] di 10 . (ma come fai a sapere quale carico di lavoro è all'ora 123 o prima dell'ora 123? Vedi la descrizione al punto 6 più avanti). (il wordload è un dizionario / hash).

  3. Quindi se il carico di lavoro nel passaggio 2 supera il carico di lavoro massimo, si può già restituire falso (e non aggiungere questo "compito", suppongo, perché supera il carico di lavoro massimo - che significa annullare (2) o semplicemente fai questo test prima di aumentare il passaggio (2))

  4. Allo stesso modo, imposta o diminuisci workload[456.7] di 10 .

  5. Mantenere un albero binario di questi punti di tempo. Quindi questo albero avrà ora il nodo foglia di 123 e 456.7. Nota che quando cerchi o inserisci in questo albero binario, è O (log M).

  6. Ora puoi ripetere il passaggio (1) per il prossimo punto di dati. Ma si noti che nel passaggio (1), come si fa a sapere se è superiore o no? È facendo una ricerca nell'albero binario. Ad esempio, quando il prossimo punto dati è (234, 345, 20) , cercare 234 nell'albero binario per il nodo foglia uguale o inferiore. Ora vedrai 123 e userai workload[123] per sapere del carico di lavoro esistente, quando raggiunge il tempo 234 .

Così facendo, con M punti di dati che entrano come flusso di dati, il tempo di dire sì o no, dovrebbe essere O(M log M) .

    
risposta data 04.04.2017 - 02:41
fonte
0

Un altro modo per farlo.

Poiché l'esaminatore ha chiesto "cosa succede se questa operazione viene ripetuta M volte?" Vorrei andare con un algoritmo di database per mantenere i dati e ridurre al minimo la necessità di ricalcolare il passato ogni volta che arrivava una nuova osservazione.

Il mio approccio è che il carico di lavoro è un segmento di linea. Quando è arrivata una nuova osservazione, spezza i segmenti di linea esistenti sul database per abbinare e aumentare il carico solo per i segmenti all'interno della nuova osservazione. L'ho fatto principalmente per divertimento. È un concetto interessante e richiederà molti più test per vedere se la mia matematica non ha effetti collaterali.

Se non sbaglio, la prestazione sarà simile al profilo O (log2 N). Essere N la dimensione del tavolo. Che dipende dai dati Se c'è molta sovrapposizione che potrebbe non crescere con il numero di osservazioni. In un caso del mondo reale possiamo persino cancellare periodi troppo vecchi perché questo comportamento fuori-sequenza che si trova nella dichiarazione del problema è probabilmente nei dati recenti.

A proposito, lo codifico in circa 20 minuti. Ma ho già fatto programmi con intersezione del segmento di linea sul database 12 anni fa.

Il problema che hai presentato:

Problem: write a function to tell whether a series of workloads will exceed 
         the maximum workload or not

Input: MaxWorkLoad: example 10
       Timeslot and workload: example [(2, 6, 3), (3, 8, 2), ... ]
       The (2, 6, 3) is begin time, end time, and workload
       And it means from time 2 to time 6, the workload is 3
       You can treat the 2, 6 as the UNIX epoch time.
       The time may not be integers, so instead of 2, it can be 2.2
       The input can be in any time order. For example: [(20, 60, 3), (3, 8, 2)]
       The workload will "add up", so a 3 and 2 will add up to 5

Output: a boolean indicating whether the series of workload can fit in without
        exceeding MaxWorkLoad 

Il mio codice

drop schema if exists t1 cascade;
create schema t1;
set search_path='t1';
create table segment
(
    start_dt bigint,
    end_dt bigint,
    load bigint
);
create index segment_idx_start_dt on segment(start_dt);
create index segment_idx_end_dt on segment(end_dt);

create or replace function segment_add(bigint,bigint,bigint) returns void as $$
declare
    xr1 record;
    xr2 record;
    xr3 record;
    xs bigint;
begin
    raise info 'segment_add(%,%,%)', $1, $2, $3;
    -- select all segments where the $1 is in (one or zero)
    select into xr1 * from segment where $1 > start_dt and $1 < end_dt;
    if xr1 is not null then
        -- Split the segment on point $1
        raise info 'add % % % ($1)', xr1.start_dt, $1, xr1.load;
        raise info 'add % % % ($1)', $1, xr1.end_dt, xr1.load;
        insert into segment values 
            ( xr1.start_dt, $1, xr1.load ),
            ( $1, xr1.end_dt, xr1.load );   

        raise info 'del % %', xr1.start_dt, xr1.end_dt;     
        delete from segment where start_dt = xr1.start_dt and end_dt=xr1.end_dt;        

    end if;

    -- select all segments where the $2 is in (one or zero)
    select into xr2 * from segment where $2 > start_dt and $2 < end_dt;
    if xr2 is not null then
        -- split the segment on pont $2
        raise info 'add % % % ($2)', xr2.start_dt, $2, xr2.load;
        raise info 'add % % % ($2)', $2, xr2.end_dt, xr2.load;
        insert into segment values 
            ( xr2.start_dt, $2, xr2.load ),
            ( $2, xr2.end_dt, xr2.load );

        raise info 'del % %', xr2.start_dt, xr2.end_dt;         
        delete from segment where start_dt = xr2.start_dt and end_dt=xr2.end_dt;        

    end if;

    -- add segments that does not exist yet
    xs = $1;
    for xr3 in 
        select * from segment where start_dt between $1 and $2 order by start_dt
    loop
        if xs < xr3.start_dt then
            -- there is no segment before the space 
            raise info 'add % % % (2)', xs, xr3.start_dt, 0;        
            insert into segment values (xs,xr3.start_dt,0);         
        end if;
        xs = xr3.end_dt;
    end loop;

    if xs < $2 then
        raise info 'add % % % (3)', xs, $2, 0;      
        insert into segment values (xs,$2,0);
    end if;     

    -- now I must have all the fragment created.... I just need to update it
    update
        segment
    set
        load = load + $3
    where
        start_dt >= $1 and end_dt <= $2
    ;

end
$$ language 'plpgsql';

select segment_add(0,4000,6000);
select segment_add(1000,10000,5000);
select segment_add(12000,15000,5000);
select segment_add(13000,14000,6000);
-- the answer should have 0-1 1-4 4-10 12-13 13-14 14-15
select * from segment order by start_dt;
    
risposta data 20.01.2017 - 11:37
fonte
0

Se consideriamo anche che "Il tempo potrebbe non essere un numero intero, quindi invece di 2, può essere 2,2", quindi il meglio che potrei fare è O (n log n), con questo algoritmo .

  • aggiungi campo is_started = false
  • Ordina per ora di inizio
  • fora l'elemento in coda
    • se non item.is_started
      • carico incrementale (+ item.load)
      • contrassegna l'elemento come avviato
      • reinserisci in coda, digita in tempo appropriato (end_time se is_started altro start_time)
    • altro
      • decremento carico (- item.load)
      • elimina l'elemento
    • verifica caricamento

Questo è algoritmo per risolvere il tuo problema .

    
risposta data 20.01.2017 - 10:12
fonte
-4

O (n)

Abbastanza semplice

  • Crea un array per il caricamento per ogni slot
  • Leggi l'input e aggiungi il carico per ogni slot
    Per 3, 5, 7 aggiungi solo 7, 3, 4 e 5 slot

Non richiede input ordinato

public static bool WorkLoadN(int[,] workload, int max)
{
    int rowCount = workload.GetLength(0);
    int colCount = workload.GetLength(1);
    int[] loadNet = new int[rowCount];
    int start;
    int end;
    int load;
    bool underMax = true;
    for (int i = rowCount - 1; i >= 0; i--)  // process in reverse order to show it does not need order
    {
        start = workload[i, 0];
        end = workload[i, 1];
        if (end > rowCount - 1)
            end = rowCount - 1;
        load = workload[i, 2];
        for (int j = start; j <= end; j++)
            loadNet[j] += load;
    }
    Debug.WriteLine("");
    for (int i = 0; i < rowCount; i++)
    {
        Debug.WriteLine("loadNet[{0}] {1}", i, loadNet[i]);
        if (loadNet[i] > max)
        {
            Debug.WriteLine("max exceeded loadNet[{0}] {1}", i, loadNet[i]);
            underMax = false;
        }
    }
    return underMax;
}

// this is just the test code to test
public static void WorkLoadTestN()
{ 
    int rowCount = 20;
    int max = 100;
    int[,] workload = new int[rowCount, 3];
    for (int i = 0; i < rowCount; i++)
    {
        workload[i, 0] = i;
        workload[i, 1] = i + rand.Next(1, max / 6);
        workload[i, 2] = rand.Next(max / 4);
        Debug.WriteLine("intput start {0}  end {1}  load {2}", i, workload[i, 1], workload[i, 2]);
    }
    bool underMax = WorkLoadN(workload, max);
}
    
risposta data 20.01.2017 - 23:17
fonte

Leggi altre domande sui tag