leetcode287:寻找重复数

题目链接

leetcode

题目描述

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

C++ 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include <iostream>
#include <vector>
using namespace std;

void printArray(const vector<int>& arr) {
for (const int& num : arr) {
cout << num << " ";
}
cout << endl;
}

/*
二分查找

cnt[i] 表示 nums 数组中小于等于 i 的数有多少个。
假设重复的数是 target,
[1, target−1] 所有数满足 cnt[i] <=i,
[target, n] 所有数满足 cnt[i] > i,
具有单调性。

时间复杂度:O(nlogn)
其中 n 为数组的长度。
二分查找最多需要 O(logn) 次,
遍历数组求解小于等于 mid 的个数 需要 O(n) 次。
因此,总的时间复杂度为 O(nlogn)。
空间复杂度:O(1)
常数空间存放若干变量。
*/
class Solution_0 {
public:
int findDuplicate(vector<int>& nums) {
int n = nums.size();
int l = 1, r = n - 1, res = -1;
while (l <= r) {
int mid = l + (r - l) / 2;
int cnt = 0;
for (int i = 0; i < n; i ++) {
cnt += nums[i] <= mid;
}
if (cnt <= mid) {
l = mid + 1;
} else {
r = mid - 1;
res = mid;
}
}

return res;
}
};

/*
二进制

将所有数二进制展开按位,考虑如何找出重复的数。
若确定重复数每一位是 1 还是 0,则可以按位还原出重复的数。
考虑第 i 位,
数组中二进制展开后第 i 位为 1 的数有 x 个,
数字 [1,n] 这 n 个数二进制展开后第 i 位为 1 的数有 y 个。
因此,重复的数第 i 位为 1(当且仅当 x > y)。

时间复杂度:O(nlogn)
其中 n 为数组长度,logn 代表了枚举二进制数的位数。
空间复杂度:O(1)
常数空间存放若干变量。
*/
class Solution_1 {
public:
int findDuplicate(vector<int>& nums) {
int n = nums.size(), res = 0;
int bit_max = 31;
while (!(n - 1) >> bit_max) {
bit_max = -1;
}
for (int bit = 0; bit <= bit_max; bit ++) {
int x = 0, y = 0;
for (int i = 0; i < n; i ++) {
if (nums[i] & (1 << bit)) {
x += 1;
}
if (i >= 1 && (i & (1 << bit))) {
y += 1;
}
}
if (x > y) {
res |= 1 << bit;
}
}

return res;
}
};

/*
快慢指针

对 nums 数组建图,每个位置 i 连一条边 i 到 nums[i]。
由于存在的重复的数字 target, target 这个位置一定有两条指向它的边,
所以这张图一定存在环,找到的 target 就是这个环的入口,该问题等价于环形链表。

时间复杂度:O(n)
其中 n 为数组长度。
空间复杂度:O(1)
常数空间存放若干变量。
*/
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int slow = 0, fast = 0;
while (true) {
slow = nums[slow];
fast = nums[nums[fast]];
if (slow == fast) break;
}

slow = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}

return slow;
}
};

int main() {
Solution solution;
vector<vector<int>> test_cases = {
{1,3,4,2,2},
{3,1,3,4,2},
{3,3,3,3,3}
};

for (auto & nums : test_cases) {
cout << "Input: ";
printArray(nums);
cout << "Output: " << solution.findDuplicate(nums) << endl;
}

return 0;
}

Golang 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package main

import (
"fmt"
)

/*
快慢指针法:
将数组视为一个链表,其中每个元素的值指向下一个节点。
使用快慢指针找到链表中的环的入口点,即重复的数字。
快指针每次移动两步,慢指针每次移动一步,最终两者会在环内相遇。
从头开始,一个指针从头开始,一个指针从相遇点开始,
两者以相同速度移动,最终会在环的入口点相遇。

时间复杂度:O(n)
其中 n 是数组的长度。
空间复杂度:O(1)
只使用了常数级别的额外空间。
*/
// findDuplicate 使用快慢指针法找到数组中的重复数字
func findDuplicate(nums []int) int {
// 初始化快慢指针
slow, fast := 0, 0

// 快慢指针相遇
for {
slow = nums[slow]
fast = nums[nums[fast]]
if slow == fast {
break
}
}

// 找到入环点
slow = 0
for slow != fast {
slow = nums[slow]
fast = nums[fast]
}

return slow
}

func main() {
// 测试用例
testCases := []struct {
nums []int
expected int
}{
{
nums: []int{1, 3, 4, 2, 2},
expected: 2,
},
{
nums: []int{3, 1, 3, 4, 2},
expected: 3,
},
{
nums: []int{3, 3, 3, 3, 3},
expected: 3,
},
}

for i, tc := range testCases {
result := findDuplicate(tc.nums)
fmt.Printf("Test Case %d, Input: nums = %v\n", i+1, tc.nums)

if result == tc.expected {
fmt.Printf("Test Case %d, Output: %d, PASS\n", i+1, result)
} else {
fmt.Printf("Test Case %d, Output: %d, FAIL (Expected: %d)\n", i+1, result, tc.expected)
}
}
}

leetcode287:寻找重复数
https://lcf163.github.io/2024/05/06/leetcode287:寻找重复数/
作者
乘风的小站
发布于
2024年5月6日
许可协议