这是我参与8月更文挑战的第26天,活动详情查看:8月更文挑战
这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。
167. 寻找重复数 (find-the-duplicate-number)
标签
- 中等
题目
给定一个包含 n + 1
个整数的数组 nums
,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数
。
假设 nums 只有一个重复
的整数 ,找出 这个重复的数 。
你设计的解决方案必须不修改数组 nums 且只用常量级 O(1)
的额外空间。
示例 1
输入: nums = [1,3,4,2,2]
输出: 2
示例 2
输入: nums = [3,1,3,4,2]
输出: 3
示例 3
输入: nums = [1,1]
输出: 1
基本思路
看上去简单,但是其实还是要想想,怎么用 O(1) 空间解决。
暴力
任何题都有暴力解,我们最先思考暴力解其实是为了打开思路
找重复,很简单啊,遍历,找后面有没有重复的, js 中 api indexOf
和 lastIndexOf
看看一不一样就行了。代码就不写了。
二分法
这个不是排序数组,为什么可以二分 ?
我们读题,有 n+1
个数 其数字都在 1 到 n
之间, 说明这些数的取值范围有区间限定, [1..n]
- 我们设定中间数
mid = (1 + n) / 2
把这个值域区间分成左右两份[1, mid]
[mid + 1, n]
- 重复的数字要么落在这两个区间之一,我们要怎么把它逼出来
- 遍历数组,把
小于等于 mid 的数量统计为 count
- 如果
count <= mid
, 说明对上了,前面左边区间没有重复的,重复数在[mid + 1, n]
中 - 反之,就在前面区间
[1, mid]
有重复
- 如果
- 接着就是继续二分上轮选中的区间了,思想其实跟二分没区别, 直到找到该数
写法实现
二分法
var findDuplicate = function(nums) {
let l = 1,
r = nums.length - 1
const numslessOrEqMid = (mid) => {
return nums.filter(it => it <= mid).length
};
while (l < r) {
let mid = Math.floor((l + r) / 2)
// 小于等于 mid 的数字个数
let count = numslessOrEqMid(mid)
if (count <= mid) {
// 重复在右边区间 [mid + 1, n]
l = mid + 1
} else {
// 重复在左边区间 [1, mid]
r = mid
}
}
return l
};
let nums = [1, 3, 4, 2, 2]
console.log(findDuplicate(nums))
快慢指针
其实我们就可以把这个数组想象成链表,一个连着一个,关键就在于如果有重复,就会连回来形成环, 我们找到入环口,就是找到了重复元素。
之前我们也写过 求入环点方法可以辅助看下
环形链表搞懂了,代码就非常清晰了。
写法实现
var findDuplicate = function(nums) {
let slow = 0,
fast = 0;
// 因为必然有重复,直接循环让找到他们交界点
// do..while 是为了让两个从 0开始的先走一步错开
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
// 然后重新找一个指针, 同步跟slow走就行
let preOrder = 0;
while (preOrder != slow) {
preOrder = nums[preOrder];
slow = nums[slow];
}
return preOrder;
};
let nums = [1, 3, 4, 2, 2]
console.log(findDuplicate(nums))
另外向大家着重推荐下这个系列的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列
今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 点击此处交个朋友
Or 搜索我的微信号infinity_9368
,可以聊天说地
加我暗号 "天王盖地虎" 下一句的英文
,验证消息请发给我
presious tower shock the rever monster
,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧