Dorito
[프로그래머스 Lv. 2] 다음 큰 숫자 - 비트연산자 사용한 시간 복잡도 O(1) JavaScript 풀이
Dorito
Dorito's Dev
Dorito
전체
오늘
어제
  • 분류 전체보기 (34)
    • 재밌는 개발글이지만 아직 미분류 (6)
    • Infra (2)
      • ELK stack (2)
    • 인프라 (0)
    • 백엔드 (3)
      • 🐈️ Nest.js (3)
    • 프론트엔드 (0)
      • 🐏️ Next.js (0)
    • 알고리즘 (12)
      • 프로그래머스 (8)
      • 리트코드 (1)
      • SQL (1)
      • SQL-1 (2)
    • 궁금증 해결 (8)
      • 개념 (7)
      • 에러 (0)
      • 구현 (0)
    • 비공개 (0)
    • 잡동사니 (1)

블로그 메뉴

  • 글쓰기
  • 홈
  • 태그
  • 방명록
  • 관리

공지사항

인기 글

태그

  • 에러 해결
  • ELK Stack
  • web
  • error-log
  • study
  • Elasticsearch
  • nestjs
  • Next.js
  • react
  • Infra
  • redis
  • typescript
  • CachManager
  • JavaScript

최근 댓글

최근 글

hELLO · Designed By 정상우.
알고리즘/프로그래머스

[프로그래머스 Lv. 2] 다음 큰 숫자 - 비트연산자 사용한 시간 복잡도 O(1) JavaScript 풀이

2022. 12. 8. 14:10
목차
  1. 아이디어:
  2. 상세 설명
  3. 음수 계산법
  4. 비트 곱셈, 나눗셈
  5. 참고 문서

코딩테스트 연습 - 다음 큰 숫자 | 프로그래머스 스쿨 (programmers.co.kr)

 

  • n은 1,000,000 이하의 자연수

풀이

 

 

 

const solution = (n) => {
const rightOne = n & -n;
const nextHigherOneBit = n + rightOne;
const rightOnesPattern = (n ^ nextHigherOneBit) / rightOne >> 2;
return nextHigherOneBit | rightOnesPattern;
};
view raw 다음 큰 숫자.js hosted with ❤ by GitHub

 

Time Complexity: O(1)
Auxiliary Space: O(1)

 

아이디어:

오른쪽 대부분의 비트(LSB, Least Significant Bit)는 왼쪽 대부분의 비트보다 빠르게 변화함
이 아이디어는 x에서 1의 가장 오른쪽 문자열을 찾고 패턴의 가장 왼쪽 비트를 제외한 오른쪽 극단으로 패턴을 이동함.
패턴에서 가장 왼쪽에 있는 비트(누락된 비트)를 x의 왼쪽 부분으로 한 위치씩 이동함

상세 설명

해당 추가 테스트 케이스들을 보자.

더 힌트를 얻어보자면..

여기서 규칙을 발견할 수 있다.


가장 오른쪽에 있는 1 뭉치들에 대해서 일정한 패턴이 보인다.

혹은 논리적으로 생각해도 된다.

저 패턴 원리를 논리적으로 쉽게 이해하려면, 비트 연산을 도미노인 것 처럼 생각하면 도움이 된다.
(개인적으로 2진법 연산 할 때 자리수 변화를 도미노처럼 연상하니까 이해하는 것에 도움이 되었다.)

예를 들어 0110 1110 인 경우
주어진 숫자의 가장 최하단 1 비트 값(0010)만큼 더하면
1 비트 3개가 도미노처럼 쭈루룩 올라가서
0111 0000 이 된다. 그러고 나머지 1 비트 2개는 가장 작은 값, 즉 가장 오른쪽으로 밀어주면 된다.

따라서 당연히 같은 비트 수를 가진 수 중에서 바로 다음 큰 값을 갖게 될 것이다.

 

xxxx0 1111 0000 을 예시로 흐름을 정리해보자.

1. [1 bit 뭉치 중 가장 오른쪽 비트의 값 구하기]

rightOne 기준점 찾기: 오른쪽부터 가장 첫번째 1을 찾는다  | xxx0 1111 0000

→ n & -n (2의 보수이므로) 비트 연산을 취해준다.

→   xxx0 0001 0000

 


2. [왼쪽 비트 패턴 구하기]

1 뭉치 기준 왼쪽 1들과 1 뭉치 내 자리수 올라가는 고립된 1 숫자끼리 합쳐줌

nextHigherOneBit (왼쪽 1 패턴): 주어진 숫자 + rightOne

4. [오른쪽 비트 패턴 구하기]

 XOR 연산을 사용하여 오른쪽 1 bit 뭉치를 분리하고, 올림에 사용된 중복된 1 2개를 제거하고 1번에서 구한 righOne 자리수만큼 bit shift 해줌

 

5. [패턴 합쳐주기]

OR 연산을 사용하여 패턴을 합쳐준다.



더 자세히는 해당 코드에 주석으로 같이 정리해두었다.

 

 

주석 설명 풀이

const solution = (n) => {
// input n = 0000 1111 0000(2) 인 경우
// expected output = 0001 0000 0111(2)
// 헷갈려서 적음: 왼쪽(= left = 큰 자리수) <-> 오른쪽 (= right = 작은 자리수)
/*
* [1 bit 뭉치 중 가장 오른쪽 비트의 값 구하기]
* -n 구하는 법) 1111 0000 1111(2) 보수 취함 -> 1 더함: 따라서 1111 0001 0000(2)
* 따라서 0000 1111 0000(2) & 1111 0001 0000(2)
*/
const rightOne = n & -n; // 결과: 0000 0001 0000
/*
* [왼쪽 비트 패턴 구하기]
* rightOne 과 n을 더하여 n의 오른쪽 1 뭉치들을 조작하는 과정
* 왼쪽 비트 값이 여기서 설정됨 (1 뭉치 기준 왼쪽 1들과 1 뭉치 내 자리수 올라가는 고립된 1 숫자끼리 합쳐줌)
* 0000 1111 0000 + 000 0001 0000 (10진법 덧셈 원리 생각하면 이해 쉬움)
*/
const nextHigherOneBit = n + rightOne; // 결과: 0001 0000 0000
/*
* [오른쪽 비트 패턴 구하기]
* 오른쪽 1 bit 뭉치 분리
* XOR 연산: 비트가 서로 다르면 1, 같으면 0
* 0000 1111 0000 (XOR) 0001 0000 0000
*/
let rightOnesPattern = n ^ nextHigherOneBit; // 결과: 0001 1111 0000
/*
* 나머지 1 bit 뭉치들 왼쪽으로 옮기기 (bit shift 계산 순서 상관 없음)
* [1] 1 bit 뭉치 바로 앞까지 있는 0 싹 다 날려줌
* 0001 1111 0000 / 0000 0001 0000 ( 나누기 2^4 는 << 4 와 동일함)
*/
rightOnesPattern = rightOnesPattern / rightOne; // 결과: 0000 0001 1111
/*
* [2] 1 개수 총합이 같아야 하므로 2만큼 shift 해줌
* 기존 1비트 뭉치(4개)에 자리 올림한 1비트까지 추가된 상태 (XOR 연산) 총 1 개수 5개
* 1 개수 유지해야하므로 1 (자리 올림된 1 개수) + x (오른쪽 패턴 1 개수) = 4
* 따라서 오른쪽 패턴 1 개수 = 3
* 5 - 3 = 2개만큼 shift 해줘야함
* 즉, 올림에 사용된 1이 중복으로 2개 들어가있음!!! 그래서 2개만큼 빼줌
* 0000 0001 1111 >> 2
*/
rightOnesPattern = rightOnesPattern >> 2 // 결과: 0000 0000 0111
// 0001 0000 0000 (OR) 0000 0000 0111 (왼쪽 비트 패턴과 오른쪽 비트 패턴끼리 엮어줌)
const next = nextHigherOneBit | rightOnesPattern; // 0001 0000 0111
return next;
};
view raw 다음 큰 숫자 설명 주석.js hosted with ❤ by GitHub

 

여기서 가장 핵심은 가장 오른쪽에 있는 (가장 작은 자리 수)에 있는 1 뭉치 묶음과, 그 1뭉치에서 가장 오른쪽에 있는 1이다. (rightOne)

 

rightOnesPattern 설명
1. 1 뭉치에 1을 더한 값 (이때 올림이 발생한다: 1111(2) + 1(2) → 1 0000(2) ⇢ 10진수 덧셈의 자리변화를 생각하면 된다.)
2. XOR 연산으로 right 패턴만 분리 (기존에 1뭉치 이후로 존재하는 1은 이때 지워진다)

 


예: 1111(2) XOR 연산 이후 rightOnesPattern

→ 1 1111(2)
→ 이때 올림된 1이 2개만큼 중복되어 들어가있음 (올림된 비트, 올라간 비트)
→ 따라서 2개 비트만큼 빼줘야함

→ 그리고 rightOne 기준으로 비트 이동해준다.
→ 결과 0 0111(2)





 


 

음수 계산법

* 출처: 4번 문서[0]
2의 보수 계산: 비트를 반전시킨 뒤 1을 더하면 됨 (자리수 바뀌는건 10진수 비교해서 생각하면 됨)

예를 들면
6 (0000 0110) ↔ -6 (1111 1010)
비트 반전: 1111 1001
1 더함: 1111 1010

비트 곱셈, 나눗셈

* 출처: 5번 문서[1]
※ 곱셈의 경우

5 * 4 는 5 * 2^2 (^는 제곱의 표현으로 사용함) 로 해석이 가능하고,
5 << 2 로 변환하여 사용이 가능합니다.
또한,
5 * 5 는 5 * ( 4 + 1 ) 의 형태입니다. 따라서, 5 * ( 2^2 + 2^0 ) 이므로,
(5 << 2) + (5 << 1) 의 형태로 변환하여 사용이 가능합니다.


※ 나눗셈의 경우

5 / 4 는 5 / (2 ^ 2) 로 해석이 가능하고
5 >> 2 로 변환하여 사용이 가능합니다.
결과는 2진수 형태는 0 0 0 0 0 0 0 1 이 되고, 10진수 값으로는 1입니다.
이 때, 정수연산의 나눗셈이므로 나머지는 신경쓰지 않습니다.


 

참고 문서

1. 비트 연산자 JavaScript Bitwise (w3schools.com)
2. 로직 설명 Next higher number with same number of set bits - GeeksforGeeks, Next higher number with same number of binary bits set (slideshare.net)
3. 컴퓨터의 음수 표현
2진수의 수와 음수 표현법 [1의 보수와 2의 보수] (tistory.com)
번외. 컴퓨터에서 음수를 표현하는 방법 (hexabrain.net)
4. 비트 나눗셈 Binary Division (이진 나눗셈) (tistory.com) , bit shift(비트 쉬프트)를 이용한 곱셈, 나눗셈 연산 (tistory.com)

저작자표시 (새창열림)

'알고리즘 > 프로그래머스' 카테고리의 다른 글

[프로그래머스 Lv.3] 여행경로: JavaScript 알고리즘 풀이 with DFS (Feat. 얕복 깊복 이슈)  (2) 2024.01.27
[프로그래머스 Lv.2] 영어 끝말잇기 JavaScript 풀이  (0) 2022.12.07
[프로그래머스 Lv 2] 행렬의 곱셈 JavaScript 풀이  (0) 2022.12.05
[프로그래머스 Lv 2] 카펫 JavaScript 풀이  (0) 2022.12.03
[프로그래머스 Lv 2] JadenCase 문자열 만들기 JavaScript 풀이  (0) 2022.12.02
  • 아이디어:
  • 상세 설명
  • 음수 계산법
  • 비트 곱셈, 나눗셈
  • 참고 문서
'알고리즘/프로그래머스' 카테고리의 다른 글
  • [프로그래머스 Lv.3] 여행경로: JavaScript 알고리즘 풀이 with DFS (Feat. 얕복 깊복 이슈)
  • [프로그래머스 Lv.2] 영어 끝말잇기 JavaScript 풀이
  • [프로그래머스 Lv 2] 행렬의 곱셈 JavaScript 풀이
  • [프로그래머스 Lv 2] 카펫 JavaScript 풀이
Dorito
Dorito
"Premature optimization is the root of all evil" - Donald Knuth
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.