0Pricing
DSA Interview Prep · Lekcja

Operatory bitowe: AND, OR, XOR, NOT i przesunięcia

Powtórzyć działanie wszystkich sześciu operatorów bitowych z użyciem tabel prawdy i przykładów w Pythonie oraz zrozumieć, jak przesunięcia w lewo i w prawo wiążą się z mnożeniem i dzieleniem przez dwa

Operatory bitowe: AND, OR, XOR, NOT i przesunięcia to bezpłatna lekcja DSA Interview Prep na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej DSA Interview Prep, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs DSA Interview Prep zawiera 4 lekcji w sumie.

Dlaczego operacje bitowe mają znaczenie

Operacje bitowe pozwalają działać bezpośrednio na binarnej reprezentacji liczb całkowitych. Wiele problemów, które wydają się złożone, staje się trywialnych dzięki odpowiedniej sztuczce bitowej: można znaleźć brakującą liczbę w czasie O(n) i przy użyciu O(1) pamięci, zamienić wartości zmiennych bez zmiennej tymczasowej lub zwięźle zakodować podzbiory. Rekruterzy wykorzystują takie zadania do sprawdzania znajomości działania na niskim poziomie oraz kreatywnego myślenia.

Liczby całkowite w Pythonie mają dowolną precyzję — mogą być tak duże, jak pozwala na to pamięć — ale operacje bitowe zawsze stosują na poziomie sprzętowym standardową semantykę uzupełnienia do dwóch. Wszystkie sześć operatorów działa bit po bicie na binarnych reprezentacjach liczb całkowitych.

# All six bitwise operators in Python
a, b = 0b1010, 0b1100  # 10 and 12 in decimal
print(f'a = {bin(a)} = {a}')
print(f'b = {bin(b)} = {b}')
print(f'a & b  (AND) = {bin(a & b)} = {a & b}')   # 1000 = 8
print(f'a | b  (OR)  = {bin(a | b)} = {a | b}')   # 1110 = 14
print(f'a ^ b  (XOR) = {bin(a ^ b)} = {a ^ b}')   # 0110 = 6
print(f'~a     (NOT) = {~a}')                       # -11 (two's complement)
print(f'a << 1 (LSH) = {bin(a << 1)} = {a << 1}') # 10100 = 20
print(f'a >> 1 (RSH) = {bin(a >> 1)} = {a >> 1}') # 101 = 5

Operator AND: maskowanie bitów

Operator AND (&) zwraca 1 tylko wtedy, gdy oba bity wejściowe mają wartość 1. Jego głównym zastosowaniem jest maskowanie: wybieranie określonych bitów liczby przy jednoczesnym wyzerowaniu wszystkich pozostałych. Aby sprawdzić, czy bit k jest ustawiony w liczbie n, należy obliczyć n & (1 << k) — jeśli wynik jest różny od zera, bit k ma wartość 1.

AND służy także do zerowania najmniej znaczącego ustawionego bitu: n & (n - 1) usuwa najbardziej prawy bit 1. Wykorzystuje się to do wydajnego zliczania ustawionych bitów oraz do sprawdzania, czy liczba jest potęgą dwójki (potęga dwójki ma dokładnie jeden ustawiony bit, więc n & (n-1) == 0).

n = 0b10110100  # 180

# Check if bit 5 is set (0-indexed from right)
bit_5 = (n >> 5) & 1
print(f'Bit 5 of {n}: {bit_5}')  # 1

# Clear lowest set bit
print(f'n = {bin(n)}')
print(f'n & (n-1) = {bin(n & (n-1))}')  # 10110000, removed the '100'

# Check power of two
for x in [16, 15, 8, 6, 1, 0]:
    is_pow2 = x > 0 and (x & (x - 1)) == 0
    print(f'{x}: power of 2 = {is_pow2}')

Operator OR: ustawianie bitów

Operator OR (|) zwraca 1, jeśli co najmniej jeden bit wejściowy ma wartość 1. Jego głównym zastosowaniem jest ustawianie określonego bitu na 1 bez wpływania na pozostałe. Aby ustawić bit k w liczbie n, należy użyć n | (1 << k). Jedynka przesunięta na pozycję k włącza ten bit, a wszystkie pozostałe bity pozostają niezmienione, ponieważ operacja OR dowolnego bitu z 0 daje jego pierwotną wartość.

OR służy także do łączenia flag: jeśli flagi funkcji są reprezentowane jako pojedyncze bity, za pomocą OR można włączyć wiele flag. Na przykład READ | WRITE | EXECUTE łączy trzy bity uprawnień w jedną liczbę całkowitą.

# Set bit k in n
def set_bit(n, k):
    return n | (1 << k)

n = 0b1000  # 8
print(f'Original: {bin(n)}')
print(f'Set bit 1: {bin(set_bit(n, 1))}')  # 1010
print(f'Set bit 0: {bin(set_bit(n, 0))}')  # 1001

# Flag combination example
READ    = 0b001  # 1
WRITE   = 0b010  # 2
EXECUTE = 0b100  # 4

perms = READ | EXECUTE
print(f'READ|EXECUTE permissions: {bin(perms)} = {perms}')
print(f'Has READ:    {bool(perms & READ)}')
print(f'Has WRITE:   {bool(perms & WRITE)}')
print(f'Has EXECUTE: {bool(perms & EXECUTE)}')

Operator XOR: przełączanie i wykrywanie różnic

Operator XOR (^) zwraca 1, gdy bity wejściowe różnią się. XOR ma trzy ważne właściwości algebraiczne: a ^ a = 0 (takie same wartości wejściowe się znoszą), a ^ 0 = a (zero jest elementem neutralnym), a także jest przemienny i łączny. Dzięki tym właściwościom XOR jest podstawowym narzędziem do znajdowania unikalnych elementów.

XOR służy także do przełączania określonego bitu: n ^ (1 << k) odwraca bit k, pozostawiając pozostałe bity bez zmian. Jeśli bit k miał wartość 0, otrzyma wartość 1, a jeśli miał wartość 1, otrzyma wartość 0.

# XOR properties
print(5 ^ 5)    # 0 — same values cancel
print(5 ^ 0)    # 5 — zero is identity
print(5 ^ 3 ^ 3)  # 5 — 3 cancels itself

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

n = 0b1010
print(f'Toggle bit 3: {bin(toggle_bit(n, 3))}')  # 0010 (was 1)
print(f'Toggle bit 0: {bin(toggle_bit(n, 0))}')  # 1011 (was 0)

# XOR swap without temp variable
a, b = 7, 13
a = a ^ b
b = a ^ b   # b now gets original a
a = a ^ b   # a now gets original b
print(f'After XOR swap: a={a}, b={b}')  # a=13, b=7

Operator NOT i uzupełnienie do dwóch

Operator NOT (~) odwraca wszystkie bity. W Pythonie ~n jest równe -(n+1) ze względu na reprezentację w uzupełnieniu do dwóch. Dla wielu osób jest to zaskakujące: ~5 = -6, a nie naiwnie oczekiwane 0b11111010. Liczby całkowite w Pythonie mają nieskończoną precyzję, więc odwrócenie wszystkich bitów liczby dodatniej daje wynik ujemny w uzupełnieniu do dwóch.

W praktyce operatora ~ rzadko używa się w Pythonie samodzielnie do operacji bitowych. Zamiast tego stosuje się go w połączeniu z AND do zerowania określonych bitów albo oblicza ~n & mask, gdzie maska ogranicza szerokość do określonej liczby bitów (np. & 0xFFFFFFFF dla 32 bitów).

# NOT in Python: ~n = -(n+1)
for n in [0, 1, 5, 127]:
    print(f'~{n} = {~n}')   # all give -(n+1)

# Clear bit k using NOT
def clear_bit(n, k):
    return n & ~(1 << k)

n = 0b1111
print(f'Clear bit 2: {bin(clear_bit(n, 2))}')  # 1011
print(f'Clear bit 0: {bin(clear_bit(n, 0))}')  # 1110

# Limiting to 32-bit with mask
def bitwise_not_32(n):
    return ~n & 0xFFFFFFFF

print(f'32-bit NOT of 5: {bin(bitwise_not_32(5))}')  # 32 zeros then ones

Przesunięcie w lewo: mnożenie przez potęgi dwójki

Operator przesunięcia w lewo (<<) przesuwa wszystkie bity o k pozycji w lewo, wypełniając zwolnione pozycje z prawej strony zerami. Jest to równoważne mnożeniu przez 2^k. Przesunięcie w lewo o 1 podwaja wartość, a przesunięcie o k mnoży ją przez 2^k.

W zadaniach rekrutacyjnych przesunięcia w lewo najczęściej służą do tworzenia masek bitowych: 1 << k tworzy liczbę, w której ustawiony jest tylko bit k. Stanowi to podstawę wszystkich operacji bitowych — ustawianie, zerowanie, przełączanie i sprawdzanie pojedynczych bitów zaczyna się od 1 << k.

# Left shift = multiply by 2^k
n = 1
for k in range(8):
    print(f'1 << {k} = {1 << k}')   # 1,2,4,8,16,32,64,128

# Practical use: creating bitmasks
def bit_mask(k):
    return 1 << k

print(f'\nBitmask for bit 0: {bin(bit_mask(0))}')  # 1
print(f'Bitmask for bit 3: {bin(bit_mask(3))}')  # 1000
print(f'Bitmask for bit 7: {bin(bit_mask(7))}')  # 10000000

# Fast exponentiation: 2^10 = 1024
print(f'2^10 = {1 << 10}')  # 1024

Przesunięcie w prawo: dzielenie przez potęgi dwójki

Operator przesunięcia w prawo (>>) przesuwa wszystkie bity o k pozycji w prawo, odrzucając k najbardziej prawych bitów. Jest to równoważne dzieleniu całkowitemu przez 2^k. Przesunięcie w prawo w Pythonie jest zawsze arytmetyczne: najbardziej lewe bity są wypełniane bitem znaku (0 dla liczby dodatniej, 1 dla liczby ujemnej).

Typowa sztuczka rekrutacyjna polega na wyodrębnieniu bitu k z liczby n za pomocą (n >> k) & 1. Bit k zostaje w ten sposób przesunięty na pozycję 0, a wszystkie pozostałe bity są odfiltrowywane za pomocą maski. Jest to najprostszy sposób sprawdzenia dowolnego określonego bitu bez konieczności obliczania i porównywania pełnej maski.

# Right shift = integer division by 2^k
n = 64
for k in range(7):
    print(f'{n} >> {k} = {n >> k}')   # 64,32,16,8,4,2,1

# Extract bit k from n
def get_bit(n, k):
    return (n >> k) & 1

n = 0b10110101  # 181
print(f'\nBits of {n} ({bin(n)}):')
for k in range(8):
    print(f'  Bit {k}: {get_bit(n, k)}')

# Negative number right shift (arithmetic)
print(f'-8 >> 1 = {-8 >> 1}')   # -4 (fills with sign bit 1)

Ściągawka z praktycznych sztuczek bitowych

Oto zestawienie najczęściej spotykanych idiomów operacji bitowych, z którymi można się zetknąć podczas rozmów rekrutacyjnych. Należy zapamiętać te wzorce — pojawiają się wielokrotnie w dziesiątkach zadań:

  • n & 1 — sprawdzenie, czy n jest nieparzyste
  • n & (n-1) — wyzerowanie najmniej znaczącego ustawionego bitu
  • n & -n — wyodrębnienie najmniej znaczącego ustawionego bitu
  • n | (1 << k) — ustawienie bitu k
  • n & ~(1 << k) — wyzerowanie bitu k
  • n ^ (1 << k) — przełączenie bitu k
  • (n >> k) & 1 — sprawdzenie bitu k
# Bit trick cheatsheet — all at once
n = 0b10110100  # 180

print(f'n = {bin(n)} = {n}')
print(f'n & 1       (odd check)         = {n & 1}')          # 0: even
print(f'n & (n-1)   (clear lowest bit)  = {bin(n & (n-1))}')
print(f'n & -n      (isolate lowest bit) = {bin(n & -n)}')
print(f'n | (1<<1)  (set bit 1)          = {bin(n | (1<<1))}')
print(f'n & ~(1<<2) (clear bit 2)        = {bin(n & ~(1<<2))}')
print(f'n ^ (1<<5)  (toggle bit 5)       = {bin(n ^ (1<<5))}')
print(f'(n>>4) & 1  (check bit 4)        = {(n>>4) & 1}')

Zliczanie ustawionych bitów (popcount)

Zliczanie liczby bitów 1 w liczbie całkowitej nazywa się zliczaniem populacji (popcount). Naiwne podejście polega na iterowaniu po wszystkich bitach. Sztuczka Briana Kernighana jest szybsza: polega na wielokrotnym zerowaniu najmniej znaczącego ustawionego bitu za pomocą n &= n - 1 i zliczaniu iteracji aż do momentu, gdy n stanie się równe 0. Każda iteracja usuwa dokładnie jeden bit 1, więc pętla wykonuje się dokładnie tyle razy, ile jest bitów 1.

Python 3.10+ udostępnia metodę int.bit_count(), która bezpośrednio zwraca tę liczbę. W starszych wersjach standardowym ręcznym rozwiązaniem jest sztuczka Kernighana. Technika ta rozwiązuje także problem „Hamming Weight” na LeetCode.

# Method 1: naive O(log n)
def count_bits_naive(n):
    count = 0
    while n:
        count += n & 1
        n >>= 1
    return count

# Method 2: Brian Kernighan O(k) where k = number of set bits
def count_bits_fast(n):
    count = 0
    while n:
        n &= n - 1   # clear lowest set bit
        count += 1
    return count

# Method 3: Python built-in (3.10+)
# n.bit_count()

for x in [0, 1, 7, 255, 180, 1024]:
    naive = count_bits_naive(x)
    fast  = count_bits_fast(x)
    print(f'{x:4d} ({bin(x):10s}): naive={naive}, fast={fast}')

Operacje bitowe w Pythonie: ważne pułapki

W przeciwieństwie do C/Java liczby całkowite w Pythonie są dowolnie duże — nie występuje przepełnienie 32-bitowe ani 64-bitowe. Oznacza to, że podczas rozwiązywania problemów wymagających działania na 32 bitach trzeba ręcznie ograniczać wyniki za pomocą maski: należy użyć & 0xFFFFFFFF, aby zachować tylko 32 najmłodsze bity.

Operator NOT ~n w Pythonie zwraca -(n+1), a nie wersję z odwróconymi bitami, której można by oczekiwać na podstawie języka C. W problemach 32-bitowych należy użyć ~n & 0xFFFFFFFF albo obliczyć 0xFFFFFFFF ^ n, aby uzyskać oczekiwane 32-bitowe dopełnienie. Te różnice często sprawiają problemy kandydatom przyzwyczajonym do operacji bitowych w stylu języka C.

# Python vs C gotchas
# In C: unsigned 32-bit NOT of 5 = 4294967290
# In Python: ~5 = -6
print(f'Python ~5 = {~5}')              # -6
print(f'32-bit ~5 = {~5 & 0xFFFFFFFF}') # 4294967290

# No integer overflow in Python
big = 1 << 100   # 2^100: huge number, no overflow
print(f'2^100 = {big}')  # works fine

# Right shift on negatives: arithmetic (sign-extending)
print(f'-1 >> 3 = {-1 >> 3}')   # -1 (all ones shifted in)

# Safe 32-bit mask for problems expecting C/Java semantics
MASK32 = 0xFFFFFFFF
result = (5 + 0xFFFFFFFE) & MASK32  # simulates 32-bit overflow
print(f'5 + (-2) in 32-bit = {result}')  # 3

Operatory przesunięcia a mnożenie

Przesunięcia w lewo i w prawo zapewniają niezwykle szybki sposób mnożenia lub dzielenia przez potęgi dwójki. Na poziomie sprzętowym przesunięcia bitowe są operacjami wykonywanymi w jednej instrukcji, podczas gdy mnożenie i dzielenie wymagają wielu cykli. W Pythonie mnożenie liczb całkowitych jest już wydajne, ale zrozumienie tej zależności pomaga wyraźniej dostrzegać wzorce bitowe.

Przydatna zależność: aby sprawdzić, czy n jest wielokrotnością 2^k, należy użyć (n & (2^k - 1)) == 0. Maska 2^k - 1 ma ustawione wszystkie niższe k bitów; wykonanie na niej operacji AND daje resztę z dzielenia przez 2^k. Jest to równoważne n % (2^k), ale szybsze w językach opartych na C.

# Shift vs arithmetic equivalence
for k in range(1, 5):
    n = 48
    print(f'{n} * 2^{k} = {n * (2**k)} = {n << k} (left shift)')
    print(f'{n} // 2^{k} = {n // (2**k)} = {n >> k} (right shift)')
    print()

# Check divisibility by power of 2
def divisible_by_power_of_2(n, k):
    mask = (1 << k) - 1   # 2^k - 1: lower k bits all 1
    return (n & mask) == 0

for n in [16, 24, 32, 15, 100]:
    print(f'{n} divisible by 4? {divisible_by_power_of_2(n, 2)}')

Szybki test

Sprawdź swoją znajomość zagadnień Data Structures & Algorithms — Coding Interview Prep z tej lekcji.

Podsumowanie lekcji

W tej lekcji poznali Państwo następujące zagadnienia: AND maskuje bity, OR ustawia bity, XOR przełącza bity i wykrywa różnice, NOT odwraca bity (w Pythonie daje wynik -(n+1)), a przesunięcia służą do mnożenia i dzielenia przez potęgi dwójki, n & (n-1) zeruje najmniej znaczący ustawiony bit i stanowi podstawę sprawdzania potęg dwójki oraz zliczania bitów, a także Python nie ma przepełnienia dla stałej szerokości, dlatego problemy 32-bitowe wymagają jawnego maskowania za pomocą & 0xFFFFFFFF. W następnej części wykorzystamy właściwość samoodwrotności XOR do rozwiązywania rodziny problemów z pojedynczą liczbą.

Często zadawane pytania

Czy lekcja „Operatory bitowe: AND, OR, XOR, NOT i przesunięcia” jest bezpłatna?

Tak — pełny tekst „Operatory bitowe: AND, OR, XOR, NOT i przesunięcia” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu DSA Interview Prep, przejdź na CoddyKit PRO. Kurs DSA Interview Prep zawiera 4 lekcji w sumie.

Co nauczysz się w „Operatory bitowe: AND, OR, XOR, NOT i przesunięcia”?

Powtórzyć działanie wszystkich sześciu operatorów bitowych z użyciem tabel prawdy i przykładów w Pythonie oraz zrozumieć, jak przesunięcia w lewo i w prawo wiążą się z mnożeniem i dzieleniem przez dwa Ćwiczysz DSA Interview Prep z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć DSA Interview Prep?

Nie wymagamy żadnego doświadczenia. DSA Interview Prep w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.

Ile czasu zajmuje lekcja „Operatory bitowe: AND, OR, XOR, NOT i przesunięcia”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji DSA Interview Prep?

Tak. Każda lekcja DSA Interview Prep zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Operatory bitowe: AND, OR, XOR, NOT i przesunięcia
  2. Single Number i właściwości XOR
  3. Maski bitowe: ustawianie, zerowanie, przełączanie i sprawdzanie
  4. Zliczanie bitów, brakująca liczba i odwracanie bitów
← Powrót do DSA Interview Prep