LeetCode Q1

各位大牛和计算机爱好者们,大家好,此文是本人人生中第一篇技术类文章,因而写的不好或有问题还望大家包含和给出建议。先自我介绍一下,本人现读硕士研究生二年级,研究方向监控视频中的行人和事件检测,本科就读信息与计算科学专业,属于初入计算机界的菜鸟。由于平时在做的都是视频分析、图像处理等科研类的活,故openCV相关的代码写的比较多一些,且更多地是照搬或改进别人的代码,而没什么机会系统地学习和掌握算法原理这些以后面试和工作中必备的知识和技能,也缺乏强悍的代码能力。

犹记得本科一位一直让我觉得受益匪浅的老师曾经说过,理论算法和实际代码就像武林中人修炼的内功和外功,缺一不可,相辅相成。因此,为了以后更好地工作和实现自我价值,也为了更像一个IT男,我决定开始建立和健全我的技术博客,将我在计算机之路上磨练的所见所闻所感记录下来,以支撑和鼓励我能够越走越远,想必以后看到想起来也是会笑的吧。哈哈~

下面就从刷LeetCode的题开始(我选择修炼的是C++语言)。

这个分类专门是为了内外兼修而准备的心灵鸡汤,一方面是警醒自己以后不要再犯同样的错误,积累编码经验,记录自己的成长历程,另一方面也是为了和大家分享自己的想法,交流心得,如果能够利人利己,我就算没浪费时间。当然,如果有朋友觉得我写的太烂,可善意提醒或者不看就是,请勿喷。废话少说,下面开始吧。

————————————————————————————————分割线—————————————————————————————————————————

LeetCode的第一题是求一个数组中和为某个值的两个数是否存在,原题如下:


Given an array of integers, find two numbers such that they add up to a specific target number.

The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based.

You may assume that each input would have exactly one solution.

Input: numbers={2, 7, 11, 15}, target=9
Output: index1=1, index2=2


分析此题,第一个想到的解法就是穷举,即从数组首项到末项,对每一项,寻找后面的项使得两两加和等于目标值。显然,此法时间复杂度较高,为O(n²),但空间复杂度却很低为O(1),因此,自然是通不过的。此法代码很好写,这里就不贴了。

分析发现,穷举法存在很多没有必要的计算项,比如对于数组中两个数都小于目标值一半的情况,显然没有必要计算,因此,结合算法课上学到的分治思想,我想到了将问题一分为二,将原问题化为寻找一个小于目标值一半的数和一个大于目标值一半的数,使得他们的和为目标值,而且仔细分析,发现这里并不存在没必要的计算,剩下的就是优化寻找算法的问题。于是,我写好了版本1.0的代码,如下:

#include "stdafx.h"
#include <vector>
#include <iostream>


#define LENGTH 3


using namespace std;


void quickSort(vector<vector<int>>& nums,int start, int end){
if(start<end){
int x=nums[start][0];
int x_index=nums[start][1];
int i=start,j=end;
while(i<j){
while(i<j && nums[j][0]>x)j--;
if(i<j){nums[i][0]=nums[j][0];nums[i][1]=nums[j][1];i++;}
while(i<j && nums[i][0]<x)i++;
if(i<j){nums[j][0]=nums[i][0];nums[j][1]=nums[i][1];j--;}
}
nums[i][0]=x;
nums[i][1]=x_index;
quickSort(nums,start,i-1);
quickSort(nums,i+1,end);
}
}


int binarySearch(vector<vector<int>> nums, int start, int end, int target){
if(start==end){
if(nums[start][0]!=target)return 0;
else return nums[start][1];
}
int bound=(start+end)/2;
if(nums[bound][0]==target)return nums[bound][1];
else if(target<nums[bound][0])return binarySearch(nums,0,bound-1,target);
else return binarySearch(nums,bound+1,end,target);
}


vector<int> twoSum(vector<int>& nums, int target) {


vector<int> index(2);
index[0]=1;
int s=target/2;
vector<vector<int>> nums1(nums.size());//存放小于目标值一半的元素及原来的位置
vector<vector<int>> nums2(nums.size());//存放大于目标值一半的元素及原来的位置
int j=0,k=0;//分别记录两个子问题元素个数

for(int i=0;i<nums.size();i++){
//先将初始数组分为两个子数组

//鉴于我对vector用法的不了解,这里我纠结了好长时间怎么给vector类型的变量赋值。vector虽然类似数组类型,但直接赋值会报错的,这里类似链表的思想,网上也有很多其他方法。

if(nums[i]<s){
vector<int>::iterator iter1=nums1[j].end();
nums1[j].insert(iter1,nums[i]);
iter1=nums1[j].end();
nums1[j].insert(iter1,i);
iter1=nums1[j].end();
j++;
}
else {
vector<int>::iterator iter2=nums2[k].end();
nums2[k].insert(iter2,nums[i]);
iter2=nums2[k].end();
nums2[k].insert(iter2,i);
iter2=nums2[k].end();
k++;
}
}

//对元素较少的那个分组开始寻找,将另一个组排序,对其每一个元素在另一个组中二分查找和为目标值的元素
if(j<k){
quickSort(nums2,0,k-1);
for(int i=0;i<j;i++){
index[1]=binarySearch(nums2,0,k-1,target-nums1[i][0])+1;
if(index[1]!=0){index[0]=nums1[i][1]+1;cout<<"index1="<<index[0]<<",index2="<<index[1]<<"\n"<<endl;return index;}
}
if(index[1]==0){cout<<"No Solution!\n"<<endl;return index;}
}
else {
quickSort(nums1,0,j-1);
for(int i=0;i<k;i++){
index[1]=binarySearch(nums1,0,j-1,target-nums2[i][0])+1;
if(index[1]!=0){index[0]=nums2[i][1]+1;cout<<"index1="<<index[0]<<",index2="<<index[1]<<"\n"<<endl;return index;}
}
if(index[1]==0){cout<<"No Solution!\n"<<endl;return index;}
}
}


int _tmain(int argc, _TCHAR* argv[])
{

//准备测试数据
int number[LENGTH]={3,2,4};
vector<int> nums;
vector<int>::iterator iter=nums.end();
for(int i=0;i<LENGTH;i++){nums.insert(iter,number[i]);iter=nums.end();}
int target=6;
vector<int> index;
index=twoSum(nums,target);
return 0;
}

注:这里的快速排序、二分查找算法都是常用算法,我就不加注释或单独解释了,优快云上都有或者直接百度。
将此程序上传,通过测试了,但是显示超时。纠结了好久,这个算法的时间复杂度已经为O(nlogn)了,算法课老师也说这是比较好的算法了,难道还有比这更快的?通过在百度上搜索各种答案,我发现问题出在排序的过程中,我自己写的快排算法是普通的原始代码,并没经过改进,而C++的标准库却早已将改进的快排算法封装进了,改进的算法时间复杂度要小于O(nlogn),因而优于上述算法。另外,对于分治思想的应用还有更简单的变形,不过不影响时间复杂度罢了,变形的原理省去了二分查找的过程和创建分治数组的多余,直接从两头分别向中间遍历,寻找和为目标值的数即可。具体代码如下:

vector<int> twoSum(vector<int>& nums, int target) {
	//初始化
	vector<int> index(2);
	index[0] = 0;index[1] = 0;
	vector<int> new_nums;
	for(int i=0;i<nums.size();i++)new_nums.push_back(nums[i]);
	
	sort(new_nums.begin(),new_nums.end());
	int index_left=0,index_right=new_nums.size()-1;
	while(index_left<index_right){
		if(new_nums[index_left] + new_nums[index_right] == target){
			for(int i=0;i<nums.size();i++){
				if(nums[i] == new_nums[index_left] || nums[i] == new_nums[index_right]){
					if(index[0]==0){index[0] = i+1;continue;}
					if(index[1]==0){index[1] = i+1;continue;}
				}
			}
			return index;
		}
		else if(new_nums[index_left] + new_nums[index_right]  < target){
			index_left++;
			continue;
			}
		else {
			index_right--;
			continue;
		}
	}
	cout<<"There are no such two numbers!\n"<<endl;
	return index;
}
终于。。。通过了LeetCode的审查,我真是长舒一口气啊。。果然,以前写代码太粗糙,只追求实现功能而忽略随之产生的代价带来的习惯很不好,以后一定要好好加油,写出漂亮精炼的代码来。

当然,该算法并不是最优的,最优算法是连排序都不需要,直接建立数组元素下标和值的hash map,利用hash map查询近乎O(1)的时间代价大大加快了算法速度。由于我对hash map的使用不熟,因此参考了http://www.aichengxu.com/view/42358的那篇文章(文中给出了该题的多种解法,有些与本文前述几种算法思路类似),具体代码如下:

vector<int> twoSum(vector<int>& nums, int target) {

	//初始化
	vector<int> index(2);
	index[0] = 0;index[1] = 0;
	hash_map<int, int> numsHash;
	for(int i=0;i<nums.size();i++)numsHash[nums[i]] = i;//确定数-索引的键-值对

	if(nums.size()<2){cout<<"There are no such two numbers!\n"<<endl;return index;}

	hash_map<int, int>::iterator it;
	for(int i=0;i<nums.size();i++){
		if((it = numsHash.find(target - nums[i]))!=numsHash.end()){
			if(i == it->second)continue;
			index[0] = i+1;
			index[1] = it->second+1;
			cout<<"index1="<<index[0]<<",index2="<<index[1]<<endl;
			return index;
		}
	}
	cout<<"There are no such two numbers!\n"<<endl;
	return index;
}
算法中构造数组中元素和下标的hash map,只需O(n)的时间复杂度,而后文遍历数组中每个元素也只需O(n)的时间代价,故总时间代价为O(n),所以此法较前几者都好。

总结:

1.搜索类算法设计的思路就是由繁至简,优化过程就是不断地减少搜索空间的范围和遍历过程的冗余;

2.对于无法继续优化时间代价的算法,无非就是利用空间代价换取时间代价;

3.C++的很多封装算法和数据结构都经过了优化,很好用,在以后的训练中要多加积累。


以下是本题结果的提交记录截图。。。算法菜鸟的修炼之路任重而道远啊。。。给自己加油加油!!!



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值