비트 마스크: 설정, 해제, 전환, 확인
개별 비트를 설정하고 해제하고 전환하고 확인하는 도우미를 구현하며, 부분집합 열거 문제에서 부분집합을 표현하기 위해 비트 마스크를 적용합니다.
비트 마스크: 설정, 해제, 전환, 확인은(는) CoddyKit의 무료 DSA Interview Prep 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 DSA Interview Prep 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. DSA Interview Prep 강의에는 총 4개의 강의가 포함되어 있습니다.
비트 마스크란 무엇인가요
비트 마스크는 다른 정수에서 특정 비트를 선택하거나 수정하거나 검사하는 데 사용하는 정수입니다. 마스크는 관심 있는 위치에는 1을, 나머지 위치에는 0을 가집니다. 비트 단위 연산자와 마스크를 함께 사용하면 다른 비트에 영향을 주지 않고 세밀한 비트 연산을 수행할 수 있습니다.
마스크의 네 가지 기본 연산은 다음과 같습니다. 설정(비트를 켜기), 지우기(비트를 끄기), 전환(비트 뒤집기), 확인(비트가 1인지 검사)입니다. 각각 마스크 1 << k와 함께 OR, AND-NOT, XOR, AND 연산자를 사용합니다.
# 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)}')비트 설정: 비트를 켜기
k번 비트를 설정하려면(현재 값과 관계없이 1로 만들려면) 숫자와 마스크 1 << k를 OR하십시오. 0 OR 1 = 1이고 1 OR 1 = 1이므로 대상 비트는 1이 됩니다. 나머지 모든 비트는 0과 OR되므로 값이 변하지 않습니다.
비트 설정은 멱등적입니다. 여러 번 호출해도 한 번 호출한 것과 같은 효과가 납니다. k번 비트가 이미 1이면 결과는 변하지 않습니다. 이 성질은 현재 상태를 신경 쓰지 않고 기능을 활성화하려는 플래그 관리에서 중요합니다.
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}')비트 지우기: 비트를 끄기
k번 비트를 지우려면(현재 값과 관계없이 0으로 만들려면) 숫자를 마스크의 보수와 AND하십시오: n & ~(1 << k). 보수 ~(1 << k)는 k번 비트만 0이고 나머지 모든 비트가 1입니다. 0과 AND하면 대상 비트가 0이 되고, 1과 AND하면 나머지 비트가 그대로 유지됩니다.
설정과 마찬가지로 지우기도 멱등적입니다. 이미 0인 비트를 지워도 숫자는 변하지 않습니다. Python에서는 Python이 부호 확장을 자동으로 처리하므로 어떤 k에 대해서도 ~(1 << k)가 올바르게 작동합니다. 개념적으로 보수의 상위 비트는 모두 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비트 전환: 비트 뒤집기
k번 비트를 전환하려면(0에서 1로 또는 1에서 0으로 뒤집으려면) 숫자와 마스크 1 << k를 XOR하십시오. 1과 XOR하면 비트가 뒤집히고, 0과 XOR하면 비트가 그대로 유지됩니다. 이는 단일 비트에 XOR을 적용했을 때의 기본 성질입니다.
전환은 네 가지 연산 중 멱등적이지 않은 유일한 연산입니다. 두 번 호출하면 원래 값으로 돌아갑니다. 따라서 간결한 정수 표현에서 켜기/끄기 스위치나 불리언 플래그처럼 두 상태를 번갈아 사용하는 기능에 적합합니다.
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))}')비트 확인: 비트 설정 여부 검사
k번 비트가 설정되었는지 확인하려면 n을 k자리만큼 오른쪽 시프트한 뒤 1과 AND하십시오: (n >> k) & 1. 이렇게 하면 k번 비트가 0번 위치로 이동하고 더 높은 비트는 모두 마스크로 제거되어, k번 비트가 0이었다면 0이, 1이었다면 1이 남습니다. 또는 bool(n & (1 << k))를 사용하여 True/False 결과를 얻을 수 있습니다.
비트 확인은 비파괴적이므로 n을 수정하지 않습니다. 각 위치를 독립적으로 시프트하고 마스크하면 여러 비트를 확인할 수 있습니다. 이는 숫자의 비트 표현을 순회하는 기초이며, 부분 집합 열거와 비트 마스크 상태를 사용하는 동적 프로그래밍에 활용됩니다.
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)}')부분 집합 표현을 위한 비트 마스크
n개의 비트를 가진 정수는 n개 원소 집합의 부분 집합을 표현할 수 있습니다. k번 비트가 1이면 k번 원소가 부분 집합에 포함되고, 0이면 포함되지 않습니다. 이렇게 하면 부분 집합을 하나의 정수로 압축하여 다음과 같은 O(1) 연산을 수행할 수 있습니다. 원소 포함 여부 검사(mask & (1 << k)), 원소 추가(mask | (1 << k)), 원소 제거(mask & ~(1 << k)), 집합의 합집합과 교집합(mask1 | mask2 및 mask1 & mask2)입니다.
n개의 원소가 있으면 가능한 부분 집합은 2^n개이며, 각 부분 집합은 0부터 2^n - 1까지의 n비트 정수로 고유하게 표현됩니다. 0부터 2^n - 1까지 모든 정수를 순회하면 모든 부분 집합을 열거할 수 있습니다.
# 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)}')마스크의 모든 부분 마스크 순회
비트 마스크 동적 프로그래밍에서는 주어진 마스크의 모든 부분 마스크를 순회해야 하는 경우가 많습니다. 일반적인 방법은 sub = mask로 시작하고 sub = (sub - 1) & mask를 사용하여 sub가 0에 도달할 때까지 반복하는 것입니다. 반복할 때마다 서로 다른 부분 마스크가 생성됩니다. 각 원소가 바깥쪽 마스크에만 포함되거나, 부분 마스크에만 포함되거나, 둘 다에 포함되거나, 어느 쪽에도 포함되지 않을 수 있으므로 모든 마스크에 걸친 총 시간 복잡도는 O(3^n)입니다.
이 기법은 'XOR이 같은 부분 집합으로 배열 분할' 또는 '어떤 부분 집합의 AND 최댓값 찾기'와 같은 문제에 등장합니다. 부분 마스크를 효율적으로 열거하는 능력은 고급 비트 마스크 DP의 특징입니다.
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: 외판원 문제 미리 보기
비트마스크 DP는 상태에 방문한 항목의 부분집합이 포함되는 문제를 해결합니다. 전형적인 예는 외판원 문제(TSP)입니다. n개의 도시를 방문하는 최소 비용 순회를 찾는 문제입니다. 상태는 dp[mask][city] = mask에 포함된 도시를 방문하고 city에서 끝나는 최소 비용입니다. n개의 도시가 있으면 2^n × n개의 상태가 존재하므로 O(n^2 × 2^n) 시간이 걸리며, n ≤ 20이면 실행 가능한 수준입니다.
the 마스크는 방문 집합을 압축한 표현으로 사용됩니다. 비트를 설정하고 해제하고 확인하는 것은 각각 도시를 방문하고 떠나고 조회하는 것에 대응합니다. 이것이 비트마스크 DP의 핵심입니다. 비트를 상태를 위한 간결한 집합으로 사용하는 것입니다.
# 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다중 비트 마스킹: 필드 추출
때로는 단일 비트뿐 아니라 다중 비트 필드인 연속적인 비트 범위를 추출해야 합니다. start 위치부터 start+length-1 위치까지의 비트를 추출하려면 length개의 연속된 1비트로 이루어진 마스크를 만듭니다. mask = (1 << length) - 1을 만든 다음 (n >> start) & mask를 사용합니다.
이 기법은 압축된 정수 형식(예: IP 주소, 픽셀 데이터 또는 하드웨어 레지스터)을 분석할 때 사용되며, 여러 개의 작은 값이 하나의 정수에 저장되는 경우에 유용합니다. 예를 들어 16비트 RGB565 픽셀은 빨간색을 비트 15-11에, 녹색을 10-5에, 파란색을 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}')면접 문제에서의 비트 마스크
비트마스크는 다음과 같은 면접 문제 유형에 자주 등장합니다.
- 부분집합 열거: 0부터 2^n-1까지의 마스크를 사용해 모든 2^n개의 부분집합을 순회합니다
- 상태 압축 DP: 방문한 노드나 항목의 집합을 DP 상태에서 비트마스크로 인코딩합니다
- 권한 시스템: READ/WRITE/EXECUTE 플래그를 OR로 결합하고 AND로 확인합니다
- 격자 방문 추적: 작은 격자에서는 방문한 칸을 하나의 정수에 묶어 저장합니다
비트마스크가 유용하다는 핵심 신호는 작은 집합(n ≤ 20개의 항목)이 있고 소속 여부의 조합을 추적해야 한다는 것입니다. 더 큰 집합에는 다른 표현이 필요합니다.
# 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효율적인 비트 열거 기법
the 마스크에서 설정된 비트를 순회할 때는 두 가지 기법이 흔히 사용됩니다. 시프트 후 확인 방법은 오른쪽으로 시프트한 뒤 LSB를 확인합니다. 최하위 설정 비트 분리 방법은 n & -n으로 최하위 설정 비트를 분리하고 처리한 다음, n &= n - 1로 해당 비트를 해제합니다. 두 번째 방법은 설정된 비트만 방문하므로 마스크가 성긴 경우 더 빠릅니다.
Python에서는 1비트 개수를 세기 위해 bin(n).count('1') 또는 n.bit_count()(3.10 이상)를 사용할 수도 있습니다. 각 설정된 비트의 위치를 구하려면 가장 높은 설정 비트에 n.bit_length() - 1을 사용합니다.
# 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}')빠른 확인
이 단원에서 다룬 자료 구조 및 알고리즘 — 코딩 면접 준비 개념에 대한 이해도를 확인해 보십시오.
단원 요약
이 단원에서 배운 내용은 다음과 같습니다. 네 가지 기본 비트 마스크 연산은 설정(OR), 해제(AND-NOT), 토글(XOR), 확인(시프트-AND)입니다. 정수는 부분집합을 나타낼 수 있으며 각 비트가 한 원소의 소속 여부를 인코딩하므로 2^n개의 부분집합을 열거할 수 있습니다. 다중 비트 필드 추출과 비트마스크 DP는 더 복잡한 상태 인코딩에도 동일한 마스킹 원리를 사용합니다. 다음으로는 이번 단원과 이전 단원에서 배운 기법을 사용해 비트 개수 세기, 누락된 수 찾기, 비트 반전을 살펴봅니다.
자주 묻는 질문
“비트 마스크: 설정, 해제, 전환, 확인” 강의는 무료인가요?
네 — “비트 마스크: 설정, 해제, 전환, 확인” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 DSA Interview Prep 강의 전체를 잠금 해제할 수 있습니다. DSA Interview Prep 강의에는 총 4개의 강의가 포함되어 있습니다.
“비트 마스크: 설정, 해제, 전환, 확인”에서 뭘 배우나요?
개별 비트를 설정하고 해제하고 전환하고 확인하는 도우미를 구현하며, 부분집합 열거 문제에서 부분집합을 표현하기 위해 비트 마스크를 적용합니다. 브라우저에서 직접 실행하는 실습 코드로 DSA Interview Prep을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
DSA Interview Prep을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 DSA Interview Prep은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“비트 마스크: 설정, 해제, 전환, 확인” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 DSA Interview Prep 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 DSA Interview Prep 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 비트 연산자: AND, OR, XOR, NOT, 시프트
- 단일 숫자와 XOR의 성질
- 비트 마스크: 설정, 해제, 전환, 확인
- 비트 세기, 누락된 숫자와 비트 뒤집기