0Pricing
DSA Interview Prep · Lezione

Maschere di bit: impostare, azzerare, invertire e verificare

Implementi funzioni di supporto per impostare, azzerare, invertire e verificare singoli bit e applichi le maschere di bit per rappresentare i sottoinsiemi nei problemi di enumerazione dei sottoinsiemi.

Maschere di bit: impostare, azzerare, invertire e verificare è una lezione DSA Interview Prep gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento DSA Interview Prep, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso DSA Interview Prep include 4 lezioni in totale.

Che cosa sono le maschere di bit

Una maschera di bit è un intero usato per selezionare, modificare o verificare bit specifici di un altro intero. La maschera contiene 1 nelle posizioni di interesse e 0 nelle altre. In combinazione con gli operatori bitwise, le maschere consentono di eseguire operazioni precise sui bit senza modificare gli altri.

Le quattro operazioni fondamentali con le maschere sono: impostare un bit (attivarlo), cancellare un bit (disattivarlo), invertire un bit (scambiarne il valore) e verificare un bit (controllare se vale 1). Ciascuna usa un operatore diverso, rispettivamente OR, AND-NOT, XOR e AND, con la maschera 1 << k.

# The four fundamental bit mask operations
def set_bit(n, k):    return n | (1 << k)       # OR to set
def clear_bit(n, k):  return n & ~(1 << k)      # AND-NOT to clear
def toggle_bit(n, k): return n ^ (1 << k)       # XOR to toggle
def check_bit(n, k):  return (n >> k) & 1       # shift+AND to check

n = 0b10110101  # 181
print(f'n = {bin(n)}')
print(f'set   bit 1: {bin(set_bit(n, 1))}')
print(f'clear bit 2: {bin(clear_bit(n, 2))}')
print(f'toggle bit 0: {bin(toggle_bit(n, 0))}')
print(f'check bit 4: {check_bit(n, 4)}')

Impostare un bit: attivarlo

Per impostare il bit k, cioè forzarlo a 1 indipendentemente dal suo valore corrente, si applica OR al numero con la maschera 1 << k. Poiché 0 OR 1 = 1 e 1 OR 1 = 1, il bit interessato diventa 1. A tutti gli altri bit viene applicato OR con 0, lasciandoli invariati.

Impostare un bit è un'operazione idempotente: chiamarla più volte produce lo stesso effetto di una sola chiamata. Se il bit k vale già 1, il risultato non cambia. Questa proprietà è importante nella gestione dei flag, quando si desidera abilitare una funzionalità senza preoccuparsi del suo stato corrente.

def set_bit(n, k):
    mask = 1 << k
    return n | mask

# Set various bits
n = 0b00001010  # 10
print(f'Original: {bin(n)} = {n}')
for k in [0, 3, 6, 7]:
    result = set_bit(n, k)
    print(f'Set bit {k}: {bin(result)} = {result}')

# Idempotence: setting already-set bit does nothing
n = 0b1111
print(f'\nAlready set: {bin(set_bit(n, 2))} = {bin(n)} (unchanged)')

# Setting multiple bits at once with a combined mask
mask = (1 << 0) | (1 << 2) | (1 << 4)  # bits 0, 2, 4
print(f'Set bits 0,2,4: {bin(0 | mask)} = {0 | mask}')

Cancellare un bit: disattivarlo

Per cancellare il bit k, cioè forzarlo a 0 indipendentemente dal suo valore corrente, si applica AND al numero con il complemento della maschera: n & ~(1 << k). Il complemento ~(1 << k) ha tutti i bit impostati a 1 tranne il bit k, che vale 0. Applicare AND con 0 forza a 0 il bit interessato; applicarlo con 1 conserva tutti gli altri bit.

Come l'impostazione, anche la cancellazione è idempotente. Cancellare un bit che vale già 0 lascia invariato il numero. In Python, ~(1 << k) funziona correttamente per qualsiasi k, perché Python gestisce automaticamente l'estensione del segno: concettualmente, il complemento ha tutti i bit più alti impostati a 1.

def clear_bit(n, k):
    mask = ~(1 << k)     # all 1s except bit k
    return n & mask

n = 0b11111111  # 255: all bits set
print(f'Original: {bin(n)} = {n}')
for k in [0, 3, 6, 7]:
    result = clear_bit(n, k)
    print(f'Clear bit {k}: {bin(result)} = {result}')

# Clear multiple bits with combined mask complement
def clear_bits(n, positions):
    mask = 0
    for k in positions:
        mask |= (1 << k)
    return n & ~mask

result = clear_bits(0b11111111, [1, 3, 5, 7])
print(f'Clear bits 1,3,5,7: {bin(result)} = {result}')  # 0b01010101 = 85

Invertire un bit: cambiarne il valore

Per invertire il bit k, cioè passarlo da 0 a 1 o da 1 a 0, si applica XOR al numero con la maschera 1 << k. Applicare XOR con 1 inverte il bit; applicarlo con 0 lo lascia invariato. Questa è la proprietà fondamentale di XOR applicata a un singolo bit.

L'inversione è l'unica delle quattro operazioni a non essere idempotente: chiamarla due volte riporta al valore originale. Questo la rende perfetta per le funzionalità che alternano due stati, come un interruttore acceso/spento o un flag booleano in una rappresentazione compatta tramite interi.

def toggle_bit(n, k):
    return n ^ (1 << k)

n = 0b10101010  # 170
print(f'Original:    {bin(n)}')
print(f'Toggle bit 0: {bin(toggle_bit(n, 0))}')  # off->on: 10101011
print(f'Toggle bit 1: {bin(toggle_bit(n, 1))}')  # on->off: 10101000
print(f'Toggle bit 7: {bin(toggle_bit(n, 7))}')  # on->off: 00101010

# Toggle is its own inverse: two toggles = no change
result = toggle_bit(toggle_bit(n, 3), 3)
print(f'Double toggle bit 3: {bin(result)} == original {bin(n)}? {result == n}')

# Toggle all lower k bits
def toggle_lower_k(n, k):
    mask = (1 << k) - 1   # k ones in the lowest positions
    return n ^ mask

print(f'Toggle lower 4 bits of {bin(n)}: {bin(toggle_lower_k(n, 4))}')

Verificare un bit: controllare se è impostato

Per verificare se il bit k è impostato, si fa uno scorrimento a destra di n di k posizioni e si applica AND con 1: (n >> k) & 1. In questo modo il bit k viene portato nella posizione 0 e tutti i bit più alti vengono mascherati, lasciando 0 (il bit k valeva 0) oppure 1 (il bit k valeva 1). In alternativa, si può usare bool(n & (1 << k)) per ottenere un risultato True/False.

La verifica di un bit non è distruttiva: non modifica n. È possibile verificare più bit facendo scorrimento e mascheramento di ciascuna posizione separatamente. Questa è la base per scorrere la rappresentazione binaria di un numero, operazione usata nell'enumerazione dei sottoinsiemi e nella programmazione dinamica con stati rappresentati da maschere di bit.

def check_bit(n, k):
    return (n >> k) & 1

def is_bit_set(n, k):
    return bool(n & (1 << k))

n = 0b10110101  # 181
print(f'n = {bin(n)} = {n}')
for k in range(8):
    print(f'Bit {k}: {check_bit(n, k)} ({"set" if check_bit(n, k) else "clear"})')

# Count set bits using check_bit
def count_set_bits(n):
    return sum(check_bit(n, k) for k in range(n.bit_length()))

print(f'\nSet bits in {n}: {count_set_bits(n)}')

# Get bit representation as list (LSB first)
def to_bit_list(n, width=8):
    return [check_bit(n, k) for k in range(width)]

print(f'Bit list (LSB first): {to_bit_list(n)}')

Maschere di bit per rappresentare i sottoinsiemi

Un intero con n bit può rappresentare un sottoinsieme di un insieme di n elementi: il bit k vale 1 se l'elemento k appartiene al sottoinsieme, altrimenti vale 0. In questo modo un sottoinsieme viene compresso in un singolo intero, consentendo operazioni O(1): verifica dell'appartenenza (mask & (1 << k)), aggiunta di un elemento (mask | (1 << k)), rimozione di un elemento (mask & ~(1 << k)) e unione/intersezione di insiemi (mask1 | mask2 e mask1 & mask2).

Con n elementi esistono 2^n sottoinsiemi possibili, ciascuno rappresentato in modo univoco da un intero di n bit compreso tra 0 e 2^n - 1. Scorrere tutti gli interi da 0 a 2^n - 1 permette di enumerare tutti i sottoinsiemi.

# Subset representation with bitmasks
elements = ['A', 'B', 'C', 'D']
n = len(elements)

def subset_from_mask(mask):
    return [elements[k] for k in range(n) if (mask >> k) & 1]

# Enumerate all 2^n subsets
print('All subsets:')
for mask in range(1 << n):   # 0 to 15 for n=4
    print(f'  {mask:04b}: {subset_from_mask(mask)}')

# Set operations
mask_ab = 0b0011   # {A, B}
mask_bc = 0b0110   # {B, C}
print(f'\nUnion:        {subset_from_mask(mask_ab | mask_bc)}')
print(f'Intersection: {subset_from_mask(mask_ab & mask_bc)}')
print(f'Difference A\\B: {subset_from_mask(mask_ab & ~mask_bc & 0b1111)}')

Scorrere tutti i sottoinsiemi di una maschera

Nella programmazione dinamica con maschere di bit, spesso è necessario scorrere tutti i sottoinsiemi di una determinata maschera. Un trucco comune consiste nel partire da sub = mask e aggiornare il valore con sub = (sub - 1) & mask finché sub non raggiunge 0. Ogni iterazione produce una sottomaschera diversa. Il costo totale su tutte le maschere è O(3^n), perché ogni elemento può appartenere solo alla maschera esterna, solo alla sottomaschera, a entrambe oppure a nessuna delle due.

Questa tecnica compare in problemi come «dividere un array in sottoinsiemi con lo stesso XOR» o «trovare l'AND massimo di un qualsiasi sottoinsieme». La capacità di enumerare in modo efficiente le sottomaschere è una caratteristica distintiva della programmazione dinamica avanzata con maschere di bit.

def all_submasks(mask):
    submasks = []
    sub = mask
    while sub > 0:
        submasks.append(sub)
        sub = (sub - 1) & mask
    submasks.append(0)  # empty subset
    return submasks

mask = 0b1011   # {0, 1, 3}
elements = ['A', 'B', 'C', 'D']
def show(m): return '{' + ','.join(elements[k] for k in range(4) if (m>>k)&1) + '}'

print(f'All submasks of {bin(mask)} = {show(mask)}:')
for sub in all_submasks(mask):
    print(f'  {bin(sub):6s}: {show(sub)}')
print(f'Total: {len(all_submasks(mask))} submasks (should be 2^{bin(mask).count("1")} = {2**bin(mask).count("1")})')

DP con maschere di bit: anteprima del problema del commesso viaggiatore

La DP con maschere di bit risolve problemi in cui lo stato include un sottoinsieme di elementi visitati. L'esempio classico è il problema del commesso viaggiatore (TSP): trovare il tour di costo minimo che visiti n città. Lo stato è dp[mask][city] = costo minimo per visitare le città in mask, terminando nella città city. Con n città, ci sono 2^n × n stati, con un tempo di O(n^2 × 2^n) — gestibile per n ≤ 20.

La maschera funge da insieme compresso degli elementi visitati. Impostare, cancellare e controllare i bit corrisponde rispettivamente a visitare, lasciare e interrogare le città. Questo è il principio fondamentale della DP con maschere di bit: usare i bit come insieme compatto per rappresentare lo stato.

# TSP with bitmask DP
import sys

def tsp(dist):
    n = len(dist)
    INF = float('inf')
    # dp[mask][v] = min cost to reach v having visited cities in mask
    dp = [[INF] * n for _ in range(1 << n)]
    dp[1][0] = 0   # start at city 0, only city 0 visited (mask=1=0b0001)

    for mask in range(1 << n):
        for v in range(n):
            if dp[mask][v] == INF: continue
            if not (mask >> v) & 1: continue  # v must be in mask
            for u in range(n):
                if (mask >> u) & 1: continue  # u must not be visited
                new_mask = mask | (1 << u)
                dp[new_mask][u] = min(dp[new_mask][u], dp[mask][v] + dist[v][u])

    full_mask = (1 << n) - 1
    return min(dp[full_mask][v] + dist[v][0] for v in range(1, n))

dist = [[0,10,15,20],[10,0,35,25],[15,35,0,30],[20,25,30,0]]
print('TSP minimum tour cost:', tsp(dist))  # should be 80

Mascheramento multi-bit: estrazione di un campo

A volte è necessario estrarre non solo un singolo bit, ma un campo multi-bit — un intervallo contiguo di bit. Per estrarre i bit dalla posizione start a start+length-1, crei una maschera composta da length bit consecutivi a 1: mask = (1 << length) - 1, quindi applichi (n >> start) & mask.

Questa tecnica viene usata per analizzare formati di interi impacchettati, come indirizzi IP, dati dei pixel o registri hardware, in cui diversi valori piccoli sono memorizzati in un unico intero. Ad esempio, un pixel RGB565 a 16 bit memorizza il rosso nei bit 15-11, il verde nei bit 10-5 e il blu nei bit 4-0.

def extract_field(n, start, length):
    mask = (1 << length) - 1   # e.g., length=3 => mask=0b111
    return (n >> start) & mask

# RGB565 pixel format: RRRRRGGGGGGBBBBB
pixel = 0b1111100111001000  # 63432
red   = extract_field(pixel, 11, 5)   # bits 15-11
green = extract_field(pixel, 5, 6)    # bits 10-5
blue  = extract_field(pixel, 0, 5)    # bits 4-0
print(f'Pixel: {hex(pixel)}')
print(f'Red:   {red}   ({bin(red)})')
print(f'Green: {green} ({bin(green)})')
print(f'Blue:  {blue}  ({bin(blue)})')

# Packing values back
def pack_rgb565(r, g, b):
    return (r << 11) | (g << 5) | b

packe = pack_rgb565(red, green, blue)
print(f'Repacked: {hex(packed) if (packed := pack_rgb565(red,green,blue)) else 0}')

Maschere di bit nei problemi da colloquio

Le maschere di bit compaiono comunemente nei seguenti tipi di problemi da colloquio:

  • Enumerazione dei sottoinsiemi: iterare su tutti i 2^n sottoinsiemi usando maschere da 0 a 2^n-1
  • DP con compressione dello stato: codificare un insieme di nodi o elementi visitati come maschera di bit nello stato della DP
  • Sistemi di autorizzazioni: combinare i flag READ/WRITE/EXECUTE con OR e verificarli con AND
  • Tracciamento delle celle visitate in una griglia: per le griglie piccole, memorizzare le celle visitate in un unico intero

Un indizio importante dell'utilità delle maschere di bit è che il problema coinvolga un insieme piccolo (n ≤ 20 elementi) e richieda di tenere traccia delle combinazioni di appartenenza. Per insiemi più grandi sono necessarie rappresentazioni diverse.

# Subset sum with bitmask enumeration
def subset_sum_exists(nums, target):
    n = len(nums)
    for mask in range(1 << n):
        total = sum(nums[k] for k in range(n) if (mask >> k) & 1)
        if total == target:
            subset = [nums[k] for k in range(n) if (mask >> k) & 1]
            print(f'Found subset {subset} summing to {target}')
            return True
    return False

subset_sum_exists([3, 1, 4, 1, 5], 10)  # finds a subset summing to 10

# Check if permutation covers all required elements (bitmask approach)
required = 0b11111  # need all 5 elements
visited  = 0b01101  # visited elements 0, 2, 3
all_visited = (visited & required) == required
print(f'All required visited: {all_visited}')  # False: missing bits 1 and 4

Tecniche efficienti per enumerare i bit

Quando si iterano i bit impostati di una maschera, si usano comunemente due tecniche. Il metodo shift-and-check: spostare a destra e controllare l'LSB. Il metodo di isolamento del bit meno significativo impostato: isolare il bit meno significativo impostato con n & -n, elaborarlo e poi cancellarlo con n &= n - 1. Il secondo metodo visita solo i bit impostati ed è più veloce quando la maschera è sparsa.

In Python, per il popcount può anche usare bin(n).count('1') o n.bit_count() (3.10+). Per ottenere la posizione di ciascun bit impostato, usi n.bit_length() - 1 per il bit impostato più significativo.

# Iterate over set bit positions
def set_bit_positions(n):
    positions = []
    k = 0
    while n:
        if n & 1:
            positions.append(k)
        n >>= 1
        k += 1
    return positions

# Faster: use lowest-set-bit isolation
def set_bit_positions_fast(n):
    positions = []
    while n:
        lsb = n & -n           # isolate lowest set bit
        k = lsb.bit_length() - 1  # position of that bit
        positions.append(k)
        n &= n - 1             # clear lowest set bit
    return positions

mask = 0b10110101
print(f'Set positions (naive): {set_bit_positions(mask)}')
print(f'Set positions (fast):  {set_bit_positions_fast(mask)}')
print(f'Bit count: {bin(mask).count("1")}')
print(f'Highest set bit: {mask.bit_length() - 1}')

Verifica rapida

Metta alla prova la Sua comprensione dei concetti di Data Structures & Algorithms — Coding Interview Prep affrontati in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato che: le quattro operazioni fondamentali sulle maschere di bit sono impostazione (OR), cancellazione (AND-NOT), inversione (XOR) e controllo (shift-AND); gli interi possono rappresentare sottoinsiemi, in cui ogni bit codifica l'appartenenza di un elemento, permettendo di enumerare 2^n sottoinsiemi; inoltre, l'estrazione di campi multi-bit e la DP con maschere di bit usano gli stessi principi di mascheramento per codificare stati più complessi. Prossimamente esploreremo il conteggio dei bit, i numeri mancanti e l'inversione dei bit usando le tecniche di questa lezione e della precedente.

Domande Frequenti

La lezione «Maschere di bit: impostare, azzerare, invertire e verificare» è gratuita?

Sì — il testo completo di «Maschere di bit: impostare, azzerare, invertire e verificare» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso DSA Interview Prep, passa a CoddyKit PRO. Il corso DSA Interview Prep include 4 lezioni in totale.

Cosa imparerò in «Maschere di bit: impostare, azzerare, invertire e verificare»?

Implementi funzioni di supporto per impostare, azzerare, invertire e verificare singoli bit e applichi le maschere di bit per rappresentare i sottoinsiemi nei problemi di enumerazione dei sottoinsiem… Eserciti DSA Interview Prep con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare DSA Interview Prep?

Non è richiesta alcuna esperienza precedente. DSA Interview Prep su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Maschere di bit: impostare, azzerare, invertire e verificare»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione DSA Interview Prep?

Sì. Ogni lezione DSA Interview Prep include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Operatori bitwise: AND, OR, XOR, NOT e shift
  2. Single Number e proprietà di XOR
  3. Maschere di bit: impostare, azzerare, invertire e verificare
  4. Conteggio dei bit, numero mancante e inversione dei bit
← Torna a DSA Interview Prep