LeetCode刷题(六)-----数组-------easy部分(Java、C++)
189. 旋转数组
给定一个数组,将数组中的元素向右移动k个位置,其中k是非负数。
说明:
• 尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
• 要求使用空间复杂度为O(1)的原地算法。
这题直接将尾部元素插入到数组的头部这种做法会超时。
思路一:
方法 1:暴力
最简单的方法是旋转 k 次,每次将数组旋转 1 个元素。
复杂度分析
时间复杂度:O(n)。将数字放到新的数组中需要一遍遍历,另一边来把新数组的元素拷贝回原数组。
空间复杂度:O(n)。另一个数组需要原数组长度的空间。
方法 3:使用环状替换
算法
如果我们直接把每一个数字放到它最后的位置,但这样的后果是遗失原来的元素。因此,我们需要把被替换的数字保存在变量temp里面。然后,我们将被替换数字(temp)放到它正确的位置,并继续这个过程 n次,n是数组的长度。这是因为我们需要将数组里所有的元素都移动。但是,这种方法可能会有个问题,如果 n%k==0,其中 k=k%n(因为如果 k大于n,移动k次实际上相当于移动 k%n次)。这种情况下,我们会发现在没有遍历所有数字的情况下回到出发数字。此时,我们应该从下一个数字开始再重复相同的过程。
方法 4:使用反转
算法:
这个方法基于这个事实:当我们旋转数组k次,k%n个尾部元素会被移动到头部,剩下的元素会被向后移动。在这个方法中,我们首先将所有元素反转。然后反转前k个元素,再反转后面n−k个元素,就能得到想要的结果。
假设n=7且k=3。
复杂度分析
• 时间复杂度:O(n)。 n个元素被反转了总共 3 次。
• 空间复杂度:O(1)。 没有使用额外的空间。
链接:https://leetcode-cn.com/problems/rotate-array/solution/xuan-zhuan-shu-zu-by-leetcode/
思路2:
1、新建一个临时数组temp,让数组后k个元素依次放入,被放入temp数组中的值从nums中删除
2、利用双指针方法,让temp倒序排列
3、nums剩余值放入temp后面
链接:https://leetcode-cn.com/problems/rotate-array/solution/li-yong-zi-dai-han-shu-by-hu-xiao-rui/
思路3:
用一个数组保留后k位数组,将前面得数组向后移动k位,再写入前面得数组。
链接:https://leetcode-cn.com/problems/rotate-array/solution/xuan-zhuan-shu-zu-by-gpe3dbjds1/
我的:
class Solution {
public:
void rotate(vector<int>& nums, int k)
{
vector<int> a(nums.size());
for(int i=0;i<nums.size();i++)
{
a[(i+k)%nums.size()] = nums[i];
}
for(int i=0;i<nums.size();i++)
{
nums[i]=a[i];
}
}
};
217. 存在重复元素
给定一个整数数组,判断是否存在重复元素。
如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。
我的方法超出了时间限制:
思路一:
方法一:朴素线性查找 【超时】
直觉:对于一个有 n个整数的数组,一共有C(n,2) = (n(n+1))/2对整数。因此,我们可以对所有的(n(n+1))/2 对进行检测,看它们是否相同。
算法:
为了实现这个思路,我们使用线性查找算法,这是最简单的查找算法。线性查找是一种检查特定值是否在列表中的算法,做法是依次逐个检查列表中的元素,直到找到满足的元素。
对于本问题,我们循环遍历全部 n个数。对于第 i个整数 nums[i],我们对前 i-1 个整数查找 nums[i] 的重复值。若找到,则返回 True; 否则继续。在程序最后,返回 False。
为了证明算法的正确性,我们定义了循环不变式。循环不变式是指在每次迭代前和后均保持不变的性质。了解循环不变式对理解循环的意义十分重要。下面就是循环不变式:
在下一次搜索之前,搜索过的整数中没有重复的整数。
循环不变式在循环之前为真,因为还没有搜索过的整数。每次循环,我们查找当前元素的任何可能重复。如果发现重复项,则函数返回 True 退出;如果没有发现,则不变式仍然成立。
因此,如果循环结束,循环不变式说明全部 n个整数中不存在重复元素。
复杂度分析
时间复杂度 : O(n^2)。最坏的情况下,需要检查 \frac{n(n+1)}{2} 对整数。因此,时间复杂度为 O(n^2)。
空间复杂度 : O(1)。只使用了常数额外空间。
注意
本方法在Leetcode上会超时。一般而言,如果一个算法的时间复杂度为 O(n^2),它最多能处理 n大约为 10^4的数据。当 n接近 10^5时就会超时。
方法二:排序 【通过】
直觉:如果存在重复元素,排序后它们应该相邻。
算法
本方法使用排序算法。由于比较排序算法,如堆排序,可以在最坏情况下具有O(nlogn) 的时间复杂度。因此,排序经常是很好的预处理方法。排序之后,我们可以扫描已排序的数组,以查找是否有任何连续的重复元素。
复杂度分析
时间复杂度 : O(nlogn)。
排序的复杂度是 O(nlogn),扫描的复杂度是 O(n)。整个算法主要由排序过程决定,因此是 O(nlogn)。
空间复杂度 : O(1)。
这取决于具体的排序算法实现,通常而言,使用 堆排序 的话,是 O(1)。
注意
此处的算法实现对原始数组进行排序,修改了原始数组。通常,除非调用方清楚输入数据将被修改,否则不应该随意修改输入数据。可以先复制 nums,然后对副本进行操作。
方法三:哈希表 【通过】
直觉:利用支持快速搜索和插入操作的动态数据结构。
算法:从方法一中我们知道,对无序数组的查找操作的时间复杂度为 O(n),而我们会重复调用查找操作。因此,使用搜索时间更快的数据结构将加快整个算法的速度。
有许多数据结构常用作动态集合,如二进制搜索树和哈希表。这里我们需要的操作是 search 和 insert。对于平衡二叉搜索树(Java 中的 TreeSet 或 TreeMap),search 和 insert 的时间复杂度均为O(logn)。对于哈希表(Java 中的HashSet或HashMap),search和insert的平均时间复杂度为 O(1)。因此,通过使用哈希表,我们可以达到在线性时间复杂度解决问题。
复杂度分析
时间复杂度 : O(n)。
search() 和 insert() 各自使用 n次,每个操作耗费常数时间。
空间复杂度 : O(n)。哈希表占用的空间与元素数量是线性关系。
注意:
对于一些特定的n不太大的测试样例,本方法的运行速度可能会比方法二更慢。这是因为哈希表在维护其属性时有一些开销。要注意,程序的实际运行表现和 Big-O 符号表示可能有所不同。Big-O 只是告诉我们在 充分 大的输入下,算法的相对快慢。因此,在 n不够大的情况下,O(n)的算法也可以比 O(nlogn)的更慢。
链接:https://leetcode-cn.com/problems/contains-duplicate/solution/cun-zai-zhong-fu-yuan-su-by-leetcode/
思路二:
思路三:(哈希表)
思路:此题方法有两种(哈希表,排序),如果用排序的话推荐大家自己手写一下快排等其他排序方法,再不熟悉怎么写排序方法前尽量不要直接调用,毕竟大家来是为了提高,而不是单纯为了刷题的。我还是倾向用哈希表的方式,此题一读就是一道典型的哈希映射的方式,即每个数字对应一个出现次数,所有我们只需要创建一个哈希表然后每遍历一个数字就去里面找是否已经映射过了,如果映射过了(即已经添加进map中了)就返回true(因为当前数字还没有加进去,如果加进去就是至少两次了)。如果没有映射过就添加进去即可。此题数据集有些问题哈希竟然比排序慢,那岂不是废了空间又废了时间了。
我的提交:
class Solution {
public:
bool containsDuplicate(vector<int>& nums)
{
map<int,int> map;
for(int i=0;i<nums.size();i++)
{
if(map.count(nums[i]) != 0)
return true;
else
map[nums[i]]++;
}
return false;
}
};