涉及的题目有数组中重复的数字、丢失的数字、找到所有数组中消失的数字、缺少的第一个正数、寻找重复数。
这类题目基本上要求常数级别的空间,时间复杂度也只有O(N)。题目解题关键是利用数组本身是一个hash表,然后让数组上的数字回归到对应的下标的位置。
归位算上是指通过swap(arr[index], arr[arr[index]])
,将值arr[index]归位到[arr[index]]这个下标位置处。将元素都归位后,就会出现元素的值就等于其下标。
在实操归位算法时,一般是两重循环。外层循环是遍历数组的所有元素。内层循环则是对index位置不断做swap操作。 之所以内层循环是这样是因为swap操作之后,不是将index值归位到index位置,而是将当前[index]位置上的值归位到arr[index]。可以说swap是将[index]位置上的值归位到本属于它的位置上,而不是为位置[index]找到了值index。 一次swap操作后,[index]位置上的值,可能还是不等于index。此时还得继续swap,将这个新的值再归位。因此是一个循环操作。 什么时候终止内层循环? 一般是swap之后index上的元素值等于index,或者题目的其他限制条件。如果碰到限制条件直接中断内层循环即可,这个中断并不会阻碍元素无法归位。后面的题目会有挺多这类的限制,做题时需要一一找出限制条件。
对于归位题目,需要先思考:假如将所有元素能归位都归位后,得到的情景会是怎么样的? 一般来说, 归位后,数组中所有元素的值都等于其下标。 此时结合题目的条件,那些剩下不能归位或者值不配位的元素往往就是题目所求。
注意:那些重复、越界和小于0的数字,何处安放并不重要,重要的是尽量将能归位的元素都归位。
数组中重复的数字
原题链接。题目如下:
找出数组中重复的数字。
在一个长度为 n 的数组nums里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
有数字重复,那么在归位时会有至少两个元素抢一个位置。当发现这种抢的情景出现就说明此时的元素是重复的。
AC代码如下:
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
for(int i = 0; i < nums.size(); ++i)//对每一个位置上的元素都尝试归位
{
// != 说明还i位置上的元素还没得到归位,需要不断循环直至归位。
while(nums[i] != i)
{
if(nums[i] == nums[nums[i]])//nums[nums[i]]这个地方已经被占用了。那么说明重复了
return nums[i];
//将nums[i]这个值归位到下标为[nums[i]]的位置上。
std::swap(nums[i], nums[nums[i]]);
}
}
return 0;//should not reach
}
};
这道题目主要是用来熟悉归位算法的代码结构。
丢失的数字
原题链接,描述如下:
给定一个包含 [0, n]
中 n
个数的数组 nums
,找出 [0, n]
这个范围内没有出现在数组中的那个数。
示例 1:
输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 2:
输入:nums = [0,1]
输出:2
解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 3:
输入:nums = [9,6,4,2,3,5,7,0,1]
输出:8
解释:n = 9,因为有 9 个数字,所以所有的数字都在范围 [0,9] 内。8 是丢失的数字,因为它没有出现在 nums 中。
不管那么多,先归位。如果没有丢失数字,那么数组上所有元素的值都等于其下标。 此时扫描一遍归位后的数组,看看那个下标的值不等于下标,那么就是所求了。对于例1,归位后得到[0, 1, 3]。扫描一遍数组,发现下标2对应的元素值是3,值不配位,因此2就是所求了。
这道题目比前面一道的复杂之处在于需要考虑下面两种情况:1. 重复数字; 2. 下标会越界。这两种情况都会终止该下标的归位。
对于第一种情况,在归位的时候会出现死循环。比如数组中有两个元素的值为3,第一个3已经归位到下标为3的位置了。此时在归位下标为5的值,并且当前arr[5]等于3。那么应该终止对于下标为5位置的归位。因为经过swap(arr[5], arr[arr[5]])(也就是swap[arr[5], arr[3])之后,arr[5]的值还是3,陷入死循环。 为此,碰到重复元素的需要终止归位。
对于第二种情况,下标越界。比如例1中的第一个元素3,明显是越界的。不能将3归位。
AC代码如下:
class Solution {
public:
int missingNumber(vector<int>& nums) {
for(int i = 0; i < nums.size(); ++i)
{
while(nums[i] != i)
{
//何时终止swap?
//这道题目需要考虑有重复的数字、值等于nums.size()的数字。
//重复的值也就是要判定nums[i]这个下标上的值是否为nums[i]。如果
//等于,那么其实是不能替换的,因为当i这个下标上的值也为nums[i]的时候
//会出现死循环。因为更换之后,没有任何变化。
if(nums[i] >= nums.size() || nums[nums[i]] == nums[i])
break;
std::swap(nums[i], nums[nums[i]]);
}
}
int i = 0;
while(i < nums.size() && nums[i] == i )
++i;
return i;
}
};
找到所有数组中消失的数字
原题链接。题目描述:
给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。
找到所有在 [1, n] 范围之间没有出现在数组中的数字。
您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。
示例:
输入:
[4,3,2,7,8,2,3,1]
输出:
[5,6]
不管那么多,先归位。归位后,如果没有消失的数字,那么数组里面,所有元素的值都等于其下标。 如果某个数字消失了,那么对应下标位置上的值就不会等于该下标。因此找的也是值不配位。
AC的解答和前面的差不多
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
std::vector<int> ret;
//先减一,以免后面混乱。此时所有元素值都在[0, nums.size())之间
std::transform(nums.begin(), nums.end(), nums.begin(), std::bind1st(std::plus<int>(), -1));
for(int i = 0; i < nums.size(); ++i)
{
while(nums[i] != i && nums[i] != nums[nums[i]])
{
std::swap(nums[i], nums[nums[i]]);
}
}
for(int i = 0; i < nums.size(); ++i)
{
if(i != nums[i])
ret.push_back(i+1);//加回1
}
return ret;
}
};
缺失的第一个正数
原题链接,题目描述:
给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。
例子:
示例 1:
输入: [1,2,0]
输出: 3
示例 2:
输入: [3,4,-1,1]
输出: 2
示例 3:
输入: [7,8,9,11,12]
输出: 1
直接归位,那些越界的元素安放何处不重要。 归位后,扫描一遍数组,碰到第一个值不配位的就是所求了。
AC代码如下:
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
nums.push_back(0);//保证0这个下标有值。因为0并不是题目所求。
for(int i = 0; i < nums.size(); ++i)
{
while(nums[i] != i)
{
//终止条件需要考虑全面。
if(nums[i] < 0 || nums[i] >= nums.size() || nums[i] == nums[nums[i]])
break;
std::swap(nums[i], nums[nums[i]]);
}
}
int i = 0;
while(i < nums.size() && nums[i] == i )
++i;
return i;
}
};
第一时间接收最新文章,请关注同名微信公众号: 代码的色彩