LeetCode 문제 중 난이도 EasySingle Number이다. 문제 링크는 포스팅의 맨 마지막에 추가하였다. 언어는 Java를 사용했다.



  문제해석

비어있지 않은 정수 배열에서, 하나를 뺀 모든 요소들은 두개씩 들어있다. 하나뿐인 요소를 찾아라.

알고리즘은 선형시간의 복잡도(O(n))를 가져야 한다. 추가적인 메모를 사용하지 않고 가능한가?





가장 먼저 떠오른 알고리즘은 먼저 배열을 정렬 한 뒤, 배열을 처음부터 탐색해 앞이나 뒤 요소와 둘다 같지 않은 요소를 찾는 것이다.예를 들어 [1,1,2,3,3]의 경우 2가 유일한 요소로 앞의 1과도 다르고 뒤의 3과도 다르다. 코드는 다음과 같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
    public int singleNumber(int[] nums) {

        Arrays.sort(nums);
        
        //배열의 길이가 1이거나, 첫번째요소와 두번째요소가 다르면 첫번째요소가 single one
        if(nums.length==1 || nums[0]!=nums[1]) return nums[0];
        
        //앞과 뒤 요소중 둘다 같은게 없으면 single one이므로 출력
        for(int i = 1; i<nums.length-1 ; i++){
            if(nums[i]!=nums[i-1&& nums[i]!=nums[i+1]) return nums[i];
        }
        
        //배열을 다 돌 때까지 못찾으면 배열의 마지막요소가 single one
        return nums[nums.length-1];
    }
}
cs


이 경우에는 추가적인 메모리 사용은 없지만 Java의 Array.sort 렬을 하는데 최대 O(nlogn) 시간이 들므로 문제의 요구사항인 선형시간 O(n)을 초과한다. 



두번째 방법은 비트연산자를 이용하는 것이다. 비트연산자를 잘 모를 경우 아래 포스팅을 참고하자.


2019/03/16 - [프로그래밍문제풀기] - 비트연산자란?





비트연산자중 ^(XOR)을 사용할 것이다. XOR연산자는 양쪽 비트가 서로 다른 경우에 1을, 같은 경우에는 0을 반환한다.



A ^ 0 = A


A ^ A = 0


A ^ B ^ A = A ^ A ^ B = 0 ^ B = B



따라서 배열의 모든 요소로 XOR 연산을 하면 두 개씩 있는 요소들은 서로 상쇄되어 사라지고 딱 하나만 있는 요소를 찾을 수 있다.


1
2
3
4
5
6
7
8
9
10
11
class Solution {
    public int singleNumber(int[] nums) {

        int singleone=0;
        
        for(int i = 0 ; i<nums.length ; i++){
            singleone=singleone^nums[i];
        }

        return singleone;
    }
}
 
cs


위의 코드에서 singleone이라는 정수형을 하나 추가로 사용했지만 이 경우 nums배열이 아무리 커져도 영향을 주지 않기 때문에 공간복잡도는 O(1)이고 추가적인 메모리는 사용하지 않는다고 봐도 된다.




문제 출처 - https://leetcode.com/problems/single-number




비트는 컴퓨터에서 데이터를 나타내는 최소 단위이다. 모든 데이터는 0과 1의 조합으로 구성되는데, 이 0또는 1이 하나의 비트이다. 1개의 비트는 두 가지 상태를 나타낼 수 있으므로 n개의 비트로는 2ⁿ가지의 상태를 나타낼 수 있다.





비트연산자란 비트 패턴이나 개별 비트 조작이 필요한 이진수에 대해 비트 단위의 연산을 실시하기 위해 사용되는 연산자이다. 비트연산자는 산술연산자 + - * / 보다 훨씬 빠르지만 요즘은 컴파일러가 자동으로 비트연산자로 변환해 주기 때문에 코딩하면서 일일히 산술연산자를 비트연산자로 바꿀 필요는 없다. 다만 알아두면 프로그래밍 시에 유용하게 쓰일 일이 많으니 비트연산자는 숙지 하는게 좋겠다. 비트연산자는 대수에서 A + B = B + A 처럼 교환법칙이 성립한다.






&(AND) 양쪽 비트가 모두 1일때만 결과가 1이되고 그렇지않으면 0

예시 : 7&2  → 2 (7=111, 2=10)




|(OR) 두 비트 중 어느 하나라도 1이면 결과가 1이고 모두 0일때만 0

예시 : 4 | 5  → 5 (4=100, 5=101)




^(XOR) 두 비트가 서로 다를때 1 같을때는 0이다

예시 : 3^65(3=11, 6=110)




~ 모든 비트 값을 반대로 만든다

예시 : int num1=17, int num2=~num1; → num2=-18

num1=00000000 00000000 00000000 00010001

num2=11111111 11111111 11111111 11101110





아래 포스팅은 비트연산자를 이용한 프로그래밍 문제 해결의 예시이다.


2019/03/16 - [프로그래밍문제풀기] - [코딩연습] Single Number 하나뿐인 숫자 찾기








LeetCode 문제 중 난이도 EasySearch Insert Position이다. 문제 링크는 포스팅의 맨 마지막에 추가하였다. 언어는 Java를 사용했다.




  문제해석

정렬되어 있는 배열과 목표값을 입력받아 목표값이 있는 위치의 인덱스를 구하여라. 목표값이 배열안에 존재하지 않을때에는 정렬 순서를 고려하여 목표값이 있어야할 위치의 인덱스를 구하여라.

Example 1
Input: [1,3,5,6], 5 ▶ Output: 2

Example 2 :
Input: [1,3,5,6], 2 ▶ Output: 1

Example 3: 
Input: [1,3,5,6], 7 ▶ Output: 4

Example 4:
Input: [1,3,5,6], 0  Output: 0





이 문제의 가장 쉬운 해결법은 배열을 처음부터 탐색하는 것이다. 목표값과 배열을 비교하여 배열의 값이 목표값보다 작을때는 넘어가다가 같거나 커지는 순간 해당 인덱스를 리턴한다. 배열을 전부 다 돌 때까지 리턴하지 않으면 배열에 저장된 모든 값이 목표값보다 작다는 뜻이므로 배열의 길이를 리턴하면 된다.

1
2
3
4
5
6
7
8
9
10
class Solution {
    public int searchInsert(int[] nums, int target) {
        for(int i=0 ; i<nums.length ; i++){
            if(nums[i]>=target){
                return i;
            }
        }
        return nums.length;
    }
}
cs


시간복잡도는 O(n), 공간복잡도는 O(1)로 나쁘지 않아 보이지만 이진탐색을 이용하여 속도를 좀 더 빠르게 개선 해보자.



이진탐색이라고 하니 왠지 엄청 복잡하고 어려울 것 같지만 우리는 이미 실생활에서 이진탐색을 쓰고 있다. 술자리에서 소주병 뚜껑에 있는 숫자를 맞추는 업앤다운 술게임을 생각해보자. 술래는 뚜껑에 써있는 숫자(1부터 50사이)를 사람들이 부르는 값과 비교해서 업인지 다운인지 알려준다. 한 명씩 돌아가면서 숫자를 부르고 한 바퀴 안에 숫자를 맞추면 술래가 술을 마신다. 이 때 숫자를 빨리 맞추고 싶다면 당연히 1부터 하나씩 비교하지 않는다. 맨 처음에 25를 부르면 업이냐 다운이냐에 따라 앞의 24개(1~24) 또는 뒤의 25개(26~50) 숫자들을 바로 제외할 수 있다. 이런식으로 남은 숫자들 중 중앙값을 부르고 반씩 버리면서 후보를 줄여 나간다.




이진탐색이 바로 그런 원리이다. 정렬되어 있는 배열에서 목표값을 찾기 위해 중앙의 값과 목표값을 비교한다. 코드로는 다음과 같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
    public int searchInsert(int[] nums, int target) {
        int first = 0;
        int last = nums.length-1;
        
        while (first<=last) {
            int mid = (first+last)/2;
            if (nums[mid]==target){
                return mid;
            }else if(nums[mid]<target){
                first=mid+1;
            } else {
                last=mid-1;
            }
        }
        return first;
    }
}
cs


맨 처음에는 배열의 첫 인덱스인 0first이고 마지막 인덱스인 배열의길이-1last일 것이다. 중앙값(mid)을 구해 타겟값이 중간값과 일치하는지, 작거나 큰지 따져서 나머지 반을 버리고 또 중앙값을 찾는다. 이렇게 하면 시간복잡도를 O(log₂n)까지 줄일 수 있다. 마지막에 first가 last보다 크면 first를 리턴하는 부분은 배열에 목표값이 존재하지 않을 경우에 해당한다. 이 부분은 말로 설명하기보다는 직접 숫자를 넣어서 따져보는 것이 이해가 더 쉽다. 맨 위의 Example 2의 경우에 first, last, mid가 어떻게 바뀌는지 따져 보길 바란다.


참고로 int는 소수점이 있는 숫자는 소수점을 버림한다. 예를 들어 (2+5)/2는 3이 나온다.




문제 출처 - https://leetcode.com/problems/search-insert-position

+ Recent posts