Leetcode的第一题:1. 两数之和 - 力扣(LeetCode)
解法I:暴力遍历
这道题可以看作三数之和,四数之和类题目的基础,题意要求从数组中寻找和为目标值的两个数。
首先想到的肯定是暴力遍历,时间复杂度是O(N^2),空间复杂度O(1);
附上代码(ACM模式):
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int target,num=0;
cin>>target>>num;
vector<int> nums(num,0);
for(int i=0;i<num;i++) cin>>nums[i];
for(int i=0;i<num-1;i++){
for(int j=i+1;j<num;j++){
if(nums[i]+nums[j]==target){
cout<<i<<' '<<j<<endl;
return 0;
}
}
}
return 0;
}
注意点:
1. 使用vector数组相较于使用基础数组,当数据量较大的时候,会显著增加时间复杂度,对于ACM模式的题目,可以针对给出的数据集范围,可以设置基础数组。
2.需要注意两次遍历的终点,对于遍历的第一层,其终点应该是数组的倒数第二个数,否则由于j=i+1,当i能够到达最后一个数的时候,j显然超限。
解法II:哈希搜值
显然,遍历搜值是暴力遍历时间复杂度增加的原因,而想要降低搜值的时间复杂度,自然就会想到哈希搜值的方法,因为在哈希表或哈希集合中的搜值都是O(1)的时间复杂度。
首先明确是使用unordered_map还是unordered_set,由于这道题最后需要输出的是对应元素的下表,因此需要在比对值和的同时记录下标,因此使用键值对模式的unordered_map;
第二点,最容易的想法是将数组中的数全部转移到哈希表中,然后第一个数还是暴力遍历,然后第二个数就可以在哈希表中直接按地址搜值,但是需要注意的是这样会导致选择的两个数可能重复,这显示是不符合题意的。
因此需要使用有点类似于动态规划思路(应该能稍微沾点边)的意味:只使用一个变量i进行一次遍历,这也是我们的初衷,当在哈希表中能够找到target-nums[i]的时候就说明找到了目标组合,当找不到这样的数的时候,就将遍历到的数加入哈希表中。
附上代码:
#include<iostream>
#include<unordered_map>
using namespace std;
int main()
{
int nums[4]={2,7,11,15};
int target=0;
cin>>target;
unordered_map<int,int> value;
for(int i=0;i<4;i++){
auto it=value.find(target-nums[i]);
if(it!=value.end()){
cout<<it->second<<' '<<i;
return 0;
}
value[nums[i]]=i;
}
return 0;
}
注意点:
1.vector的部分与上一个方法相同
2.对于哈希表的查值需要熟悉C++中哈希表的类方法相关操作。
第二道题:4. 寻找两个正序数组的中位数 - 力扣(LeetCode)
解法I:常规遍历
这道题的难点在于时间复杂度要求是小于log(m+n)。
首先先忽视这个运行时间上的要求,如果用常规思路应该怎么写?
最容易想到的肯定是使用双指针从两个数组的头部开始遍历,直到遍历到下标和为(m+n+1)/2 的位置,当然还是要分数组中元素个数和为奇数还是偶数分情况讨论。这种方法必然会遍历(m+n+1)/2次,也就是说时间复杂度是O(M+N)级别的。
同时这种方法也有许多小坑,举几个例子:
eg1:数组1: 1,2,3,4,5; 数组2: 6,7,8,9,10
在上述情况中显然数组1会遍历到5的位置,而数组2实际上还没有开始遍历,p2依然停留在0。
而同时这个数组又因为数字数目和是偶数,而显然要采用中间两数取平均值的问题,那么下一个数到底是从数组1取,还是数组2取?显然这里第一次写的时候很容易会涉及到数组超限问题,需要特殊设计处理。
附上代码:
#include<iostream>
using namespace std;
int main()
{
int num1[1000]={0};
int num2[1000]={0};
int m,n=0;
cin>>m>>n;
for(int i=0;i<m;i++) cin>>num1[i];
for(int j=0;j<n;j++) cin>>num2[j];
int p1=0;
int p2=0;
int total=(m+n+1)/2;
while(p1+p2<total){
if(num1[p1]<num2[p2]) p1++;
else p2++;
}
int left1max=p1==0?INT_MIN:num1[p1-1];
int right1min=p1==m?INT_MAX:num1[p1];
int left2max=p2==0?INT_MIN:num2[p2-1];
int right2min=p2==n?INT_MAX:num2[p2];
double result=0;
if((m+n)%2==1) result=max(left1max,left2max);
else result=double((max(left1max,left2max)+min(right1min,right2min))/2.0);
cout<<result<<endl;
return 0;
}
解法II:找出两个数组之间的关联,作一次二分搜索
这个时间复杂度的形式中含有log,又注意到是要寻找数组的中位数相关衍生,会非常自然的想到数组排序的一种算法,二分搜索。但是这道题有两个数组,很显然是不能直接进行操作的。
但是我们需要思考一个问题:既然最终的时间复杂度是log(m+n),那么显然其遍历的主要形式就是一次二分搜索,上面也说了有两个数组,但是如果可以经过操作,使得可以同时操作两个数组呢?
根据中位数的定义,我们知道显然中位数将所有的数划分成了两个区域,而左半边区域的数字个数和是一个定值,也就是说当我们确定了左边区域需要放多少第一个数组中的数,那么左边区域中第二个数组中的数的数目也就同时确定了。
这样我们只需要将两个数目做关联,就可以在二分搜索第一个数组分界线的同时将第二个数组中的分界线也找到。这样就将二分搜索应用到了两个数组中。
同样需要注意处理上面常规遍历所举的例子中的超限问题。
附上代码:
#include<iostream>
using namespace std;
int main()
{
int num1[1000]={0};
int num2[1000]={0};
int m,n=0;
cin>>m>>n;
if(m<n){
for(int i=0;i<m;i++) cin>>num1[i];
for(int j=0;j<n;j++) cin>>num2[j];
}
else{
for(int i=0;i<n;i++) cin>>num2[i];
for(int j=0;j<m;j++) cin>>num1[j];
swap(m,n);
}
int left=0,right=m;
int totalleft=(m+n+1)/2;
while(left<right){
int i=left+(right-left+1)/2;
int j=totalleft-i;
if(num1[i-1]>num2[j]) right=i-1;
else left=i;
}
int i=left;
int j=totalleft-i;
int left1max=i==0?INT_MIN:num1[i-1];
int right1min=i==m?INT_MAX:num1[i];
int left2max=j==0?INT_MIN:num2[j-1];
int right2min=j==n?INT_MAX:num2[j];
double result=0;
if((m+n)%2==1) result=max(left1max,left2max);
else result=double((max(left1max,left2max)+min(right1min,right2min))/2.0);
cout<<result<<endl;
return 0;
}
现在回想一下两种方法,做的其实都是一件事,就是在两个数组中分别寻找一根分界线,分界线左边就是左边区域,右边就是右半区域,然后再根据数组个数和是奇数还是偶数对分界线处的数进行处理。
还有一个需要注意的点就是,最后取两数中间值的时候需要除以2.0,否则无法得到浮点数double型。

被折叠的 条评论
为什么被折叠?



