React

287. Find the Duplicate Number https://leetcode.com/problems/find-the-duplicate-number/

Medium
Given an array of integers nums containing n + 1 integers where each integer is in the range [1, n] inclusive.
There is only one repeated number in nums, return this repeated number.
You must solve the problem without modifying the array nums and uses only constant extra space.
Example 1:
Input: nums = [1,3,4,2,2] Output: 2
Plain Text
복사
Example 2:
Input: nums = [3,1,3,4,2] Output: 3
Plain Text
복사
Example 3:
Input: nums = [1,1] Output: 1
Plain Text
복사
Example 4:
Input: nums = [1,1,2] Output: 1
Plain Text
복사
Constraints:
1 <= n <= 105
nums.length == n + 1
1 <= nums[i] <= n
All the integers in nums appear only once except for precisely one integer which appears two or more times.
Follow up:
How can we prove that at least one duplicate number must exist in nums?
Can you solve the problem in linear runtime complexity?

My solution

숫자 안에 반복되는 숫자가 하나만 있습니다. 이 반복되는 숫자를 반환하십시오.
배열 인덱스를 수정하지 않고 일정한 추가 공간만 사용하여 문제를 해결해야 합니다.
소팅한 배열을 투포인터로 돌면서 mid를 업데이트하고 찾으려고 했다. mid가 indexOf(nums[mid])한 값과 다르거나 lastIndexOf(nums[mid])한 값과 다르거나, 둘다 다르거나 한 경우(왜냐하면 중복된 숫자는 2개 이상 나올수도 있으므로 둘다 다를 수 있다.) 이때의 nums[mid]를 리턴하려고 했다. 하지만 계속해서 틀리는 케이스가 나왔다.
test('TC9', () => { expect(findDuplicate([14, 16, 12, 1, 16, 17, 6, 8, 5, 19, 16, 13, 16, 3, 11, 16, 4, 16, 9, 7])).toStrictEqual(16); });
JavaScript
복사
그 이유는 다음 범위를 rt와 lt를 변경해서 지정해주어야 하는데 중복된 숫자들이 어디에 존재하는지 알 수 있는 조건이 없었다. 그래서 무작위로 rt와 lt를 변경해주는 방식이 되면 중복된 숫자들이 앞쪽에 몰려있는데도 lt와 rt가 뒷쪽으로 범위를 잡아서 해당 범위를 찾고 끝내버리는 문제였다.
그래서 다른 방식을 찾았다.
for (const x of nums) { idx = Math.abs(x); if (nums[idx] < 0) return idx; nums[idx] *= -1; }
JavaScript
복사
이와같이 배열을 돌면서 그 요소 하나하나를 인덱스로 사용했다. 이 인덱스를 다시 배열에 던져주면 이 값이 음수가 나올 수도 있는데, 이것은 이미 방문한 값임을 뜻했다. 따라서 nums[idx]는 매번 방문했으니 -1을 곱해서 배열 값 자체를 수정해주는 방식으로 공간복잡도를 줄일 수 있었다. 이렇게 배열의 내부값을 조금만 수정해주는 방식으로도 플래그의 효과를 볼 수 있다는 것을 다시한번 상기했다.
idx를 잡아서 배열에 그 idx에 대해 접근하는 부분은 이해하기 쉽지 않았다. Math.abs(x)를 한 이유는 nums의 값들을 방문할 때마다 음수화하고 있기 때문이다. 그리고 실제로 그 값을 인덱스로 사용해서 다른 값을 접근하는 것이기 때문에 해당 값이 음수여도 사용하는 것은 옳다.
또한 이렇게 인덱스 대용으로 배열의 요소를 사용할 수 있는 이유는 주어진 조건이 [1,n] 범위의 배열이기 때문이다. 이것이 연속적으로 주어진 배열이라면 해당 값들을 인덱스로 써서 접근하는 것은 for문으로 nums의 최댓값을 가지고 범위를 줘서 하나하나 찾아가는 것과 다를 바 없지만 더 깔끔하고 고차함수 배열 메서드를 사용할 수도 있어서 좋다고 생각된다.
또한 깔끔하게도 [1,1]과 같은 것도 처음에 1로서 인덱스 1을 방문해 -1로 만들어버리면 다음 방문도 인덱스 1을 방문하게 되어 음수이므로 답을 반환할 수 있다.
놀라운 것은 sort()를 하지 않았는데도 정상적으로 동작한다는 것이다.
왜냐?
결국엔 1~n까지를 방문하게 되어있는데 그걸 차례대로 1~n까지 방문하느냐 아니냐의 차이일 뿐이다. 하나하나 다 확인을 할 것이고 해당 값(인덱스)에 대한 배열 내의 값이 음수가 되어 있는 것을 발견하면 ok다.
함수형으론 아래와 같지만 좀더 느리다.
const findDuplicate = function (nums) { return nums.reduce((acc, curr) => { const idx = Math.abs(curr); if (nums[idx] < 0) return idx; nums[idx] *= -1; return acc; }, 0); };
JavaScript
복사

소스코드

/** * @param {number[]} nums * @return {number} */ const findDuplicate = function (nums) { let idx; for (const x of nums) { idx = Math.abs(x); if (nums[idx] < 0) return idx; nums[idx] *= -1; } }; test('TC1', () => { expect(findDuplicate([1, 3, 4, 2, 2])).toStrictEqual(2); }); // test('TC2', () => { // expect(findDuplicate([3, 1, 3, 4, 2])).toStrictEqual(3); // }); // test('TC3', () => { // expect(findDuplicate([1, 1])).toStrictEqual(1); // }); // test('TC4', () => { // expect(findDuplicate([1, 1, 2])).toStrictEqual(1); // }); // test('TC5', () => { // expect(findDuplicate([1, 2, 2])).toStrictEqual(2); // }); // test('TC6', () => { // expect(findDuplicate([4, 3, 1, 4, 2])).toStrictEqual(4); // }); // test('TC7', () => { // expect(findDuplicate([1, 5, 5, 6, 2, 8, 4, 7, 5, 5])).toStrictEqual(5); // }); // test('TC8', () => { // expect(findDuplicate([4, 4, 17, 15, 2, 1, 19, 11, 12, 13, 3, 18, 4, 4, 5, 9, 7, 14, 4, 16])).toStrictEqual(4); // }); // test('TC9', () => { // expect(findDuplicate([14, 16, 12, 1, 16, 17, 6, 8, 5, 19, 16, 13, 16, 3, 11, 16, 4, 16, 9, 7])).toStrictEqual(16); // });
JavaScript
복사