给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。api
示例
输入: [1,3,4,2,2]
输出: 2
输入: [3,1,3,4,2]
输出: 3
给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。数组
先用hash表来解决函数
var fn = function(nums, target) { const map = {}; for(let i=0; i<nums.length; ++i){ if(map[nums[i]]) return nums[i] map[nums[i]] = 1 } };
很简单,完美解决,还有其余方式吗
以输入[1,3,4,2,2]为例,咱们最终获得的map是指针
{ 1:1, 2:1, 3:1, 4:1 }
能够看到这个对象的 keys 是 [1,2,3,4], 就是数组下标+1。也就是说 咱们能够把数组下标看成对象的key,来构建这个hashmap。从而把空间复杂度从 O(n) 降到 O(1)。code
具体解题思路:循环,若是nums[i]!== i + 1
, 把 nums[i]
放到下标为 nums[i]- 1
的位置,而后把nums[i] - 1
位置上本来的数 a
,放到下标为a - 1
,并以此循环循环,若是nums[i] === nums[nums[i] - 1]
则说明这个数出现了两次(被放置到正确的位置过,或者本来就存在)。文字可能说的不清楚,看代码对象
var findDuplicate = function(nums) { for(let i = 0, len = nums.length; i < len; ++i) { // 若是当前位置不是i + 1, 进循环去换 while(nums[i] !== i + 1) { if(nums[nums[i] - 1] === nums[i]) { return nums[i] } let temp = nums[nums[i] - 1] nums[nums[i] - 1] = nums[i] nums[i] = temp } } };
这样咱们使用本来的数组nums来代替map,使空间复杂度降成 O(1)。
须要注意的是,时间复杂度并无由于两层循环增长为 O(n^2),由于目标语句(交换数值的部分)执行的次数依然是n,因此时间复杂度依然是 O(n)。
再次重申,这道题的题目要求不能修改原数组,这种解法是不符合要求的,不过由于忽略这个条件的话比较合适举例。下面看一道正统的原地hash题目。排序
41缺失的第一个整数:leetcode
给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。rem
示例
输入: [1,2,0]
输出: 3
输入: [3,4,-1,1]
输出: 2
输入: [7,8,9,11,12]
输出: 1
题目给的提示是你的算法的时间复杂度应为O(n),而且只能使用常数级别的额外空间。完美符合。
要注意的是,以[7,8,9,11,12]为例,咱们不能为了把7放到数组第(7-1)位,而改变数组大小,由于数组length是5那么缺失的数只有多是1-6,那么大于数组length或者小于0的数就不须要管了,置为0就能够了。代码以下。
var firstMissingPositive = function(nums) { let len = nums.length for(let i = 0; i < len; ++i) { while(nums[i] !== i + 1 && nums[i] > 0) { if(nums[nums[i] - 1] === nums[i] || nums[i] > nums.length) { nums[i] = 0 } else { let mid = nums[nums[i] - 1] nums[nums[i] - 1] = nums[i] nums[i] = mid } } } for(let i = 0; i < nums.length; ++i) { if(!(nums[i] > 0)) { return i + 1 } } return (nums[nums.length - 1] || 0) + 1 };
给定一个增序排列数组 nums ,你须要在 原地 删除重复出现的元素,使得每一个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。示例
输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新长度 length = 5, 而且原数组的前五个元素被修改成 1, 1, 2, 2, 3 。 你不须要考虑数组中超出新长度后面的元素。输入:nums = [0,0,1,1,1,1,2,3,3]
输出:7, nums = [0,0,1,1,2,3,3]
解释:函数应返回新长度 length = 7, 而且原数组的前五个元素被修改成 0, 0, 1, 1, 2, 3, 3 。 你不须要考虑数组中超出新长度后面的元素。
先说一下我一开始的思路:两个指针,一个begin指向第一个数,end向后找到第一个与begin不一样的数,若是两个指针之间距离大于2, 说明出现了两次以上的相同数字,使用splice删除,而后更新begin和end的位置继续。
var removeDuplicates = function(nums) { for(let begin = end = 0; begin < nums.length; ++end) { if(nums[begin] !== nums[end]) { if(end - begin > 2) { nums.splice(begin, end - begin - 2) begin = end = begin + 2 } else { begin = end } } } return nums.length };
我觉得复杂度是O(n),可是忽略了调用splice这个api自己的复杂度,若是认为splice是O(n),那么最终复杂度应该是O(n^2)。
而后就是原地hash的解法,不过此次不能单纯的用数组下标来构建了,须要借用一个指针来更新,因此仍是两个指针,一个指针begin更新结果,一个指针 i 配合 i-1 向前推动。
若是nums[i] !== nums[i-1]
说明出现了新的数字,把double置为false,更新nums[begin]
若是nums[i] === nums[i-1]
且 double=false
说明第一次出现相同的数字,更新nums[begin],把double置为true,意为已经有两个相同的数字了,后面不用理:
var removeDuplicates = function(nums) { if(nums.length <= 2) return nums.length; var begin = 1, doubled = false; for(var i = 1; i < nums.length; i++) { if(nums[i] !== nums[i-1]) { doubled = false; nums[begin++] = nums[i]; } else if(!doubled) { doubled = true; nums[begin++] = nums[i]; } } nums.splice(begin); return begin; };