LeetCode 上第一题和第二题你会讲么?
阿粉最近也在刷 LeetCode 上面的题,因为 LeetCode 的题,很多都是在注重算法的实践上,殊不知,阿粉在前几道题目上就写出了像是垃圾一样的代码。看了其他的代码,瞬间感觉为什么自己没有思考出来这么好的方法呢?
LeetCode 第一题
LeetCode 上面的第一道题,也是入门基础的题,求和。
给定一个数组和一个目标和,从数组中找两个数字相加等于目标和,输出这两个数字的下标。
这个题是什么意思呢?相信大家肯定也都知道,给你个数组,数组里面有一堆的数据,然后再给你一个数,找出数组里面相加等于这个数字的元素,并且把元素位置输出出来。
阿粉看到这个基础题目,心里在想,非常简单呀,直接双层循环,然后计算求和相等就输出就完事了,尴尬了,阿粉写出了一种最暴力的写法,写法如下:
public int[] twoSum(int[] nums, int target) {
//定义返回数组
int []ans=new int[2];
//第一次循环
for(int i=0;i<nums.length;i++){
//第二次循环
for(int j=(i+1);j<nums.length;j++){
if(nums[i]+nums[j]==target){
ans[0]=i;
ans[1]=j;
return ans;
}
}
}
return ans;
}
这是阿粉第一时间想到的方案,阿粉当时也冥思苦想还有没有其他的方案来实现,但是阿粉太low ,没有想到其他的解决方案,于是阿粉就开始所有,然后看看大家都是怎么想的,于是乎,发现了自己的想法确实还和广大网友差距不小呀,确实,阿粉的实现方式没什么错误,因为题目就是求和,功能实现了,但是效率缺是真的低,因为两层 for 循环。
时间复杂度:两层 for 循环,O(n²)
求和方法二
我们第一种方法的关键在于代码的这个地方:
for(int j=(i+1);j<nums.length;j++){
if(nums[i]+nums[j]==target){
这个地方,我们使用的是加法,那么有没有可能使用减法呢?
于是第二种算法就来了
public int[] twoSum(int[] nums, int target) {
for(int i = 0;i < nums.length;i++){
int first = nums[i];
//最后一个数字定义在了这里 代码更加简洁
int lastNum = target - first;
for(int j = i+1;j < nums.length;j++){
if(lastNum == nums[j]){
int[] resArray = {i,j};
return resArray;
}
}
}
return null;
}
阿粉当时就感觉,果然,人外有人,天外有天呀,这不看不知道,算法不单单有这个循环的方式呀,如果不用循环的话,我们能不能有方法来实现这个求和的算法呢?
有,而且还是我们经常见到的,而且也是面试里面会经常被问到的,那就是 HashTablle 了。
我们可以把数组的每个元素保存为 hash 的 key,下标保存为 hash 的 value 。
这样只需判断 sub 在不在 hash 的 key 里就可以了,而此时的时间复杂度仅为 O(1)!
需要注意的地方是,还需判断找到的元素不是当前元素,因为题目里讲一个元素只能用一次。
那么代码是应该怎么实现呢?
public int[] twoSum(int[] nums, int target) {
//为了代码的简洁美观 new的时候泛型就不限制了 对象的类型可以省略
Map<Integer, Integer> hashmap = new HashMap<>();
for (int i = 0; i < nums.length; ++i) {
if (hashmape.containsKey(target - nums[i])) {
return new int[]{hashmap.get(target - nums[i]), i};
}
hashmap.put(nums[i], i);
}
return new int[0];
}
时间复杂度:比解法一少了一个 for 循环,降为 O(n)
空间复杂度:所谓的空间换时间,这里就能体现出来, 开辟了一个 hashtable ,空间复杂度变为 O(n)
题目比较简单,毕竟暴力也可以解决,唯一的亮点就是从时间的复杂度变得稍微缓和了一点 ,对于集合hash的使用,如果是在面试环节,你写出了不一样的操作,相信会亮眼一波。
LeetCode 第二题
如果第一题是基础,阿粉相信第二题,绝对属于那种摸不着头脑的,就像 最近比较火的 “羊了个羊”,第一关秒杀,第二关直接凉透了。
问题如下:
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
这直接从简单难度直线上升到高级难度了,其实这只是一个稍微比简单难度高了一点点的。
图示如下:
链表最左边表示个位数,代表 342 + 465 =807
我们应该怎么思考这个问题:
在看到这道题时,第一想法是逐个遍历链表的节点,将每个节点的数据取出并存储到String类型的数据中,再将这些String类型的字符数据拼接起来反转后再成int类型的数据。但是后来发现这么做是不对的,应该先定义一条新的链表按节点来逐个存储两条链表相加的值,最后将这条新的链表返回。
创建一条新的链表,设置两个头指针current和result,在两条链表相加的过程中只移动current指针,result指针不移动,最后将result返回。两条链表节点的值在相加时会有进位的情况,因此要进位的数与不进位的数(即剩余的数)进行获取和存储,因此再定义用来存储需要进位的int类型的变量total和存储不需要进位的数值的int类型变量remainder,将两个变量的初值均定义为0。
代码如下:
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
// 定义一个链表用来存储两条链表相加的结果,result和current为两个指针
// current为头结点不进行移动,result为最后要输出的链表
ListNode result = null, current = null;
// 定义total变量为l1和l2两条链表相加后需要进位的数据
int total = 0;
// 如果l1和l2两条链表相加后需要进位,remainder变量为个位数的值
// 如果l1和l2两条链表相加后不需要进位,remainder为两数相加后的值
int remainder = 0;
// 只要有一条链表不为空就继续进行遍历
while (l1 != null || l2 != null) {
int num1 = l1 != null ? l1.val : 0;
int num2 = l2 != null ? l2.val : 0;
// 定义sum为两数相加后再与进位数相加后的值
int sum = num1 + num2 + total;
// 对两数相加的结果进行整除,取出进位的值
total = sum / 10;
// 对两数相加的结果对10进行取余,取出不需要进位的值
remainder = sum % 10;
if (result == null) {
result = current = new ListNode(remainder);
} else {
// 将当前不需要进位的值赋值给下一个节点要存储的值
current.next = new ListNode(remainder);
// 将current指针向后移动
current = current.next;
}
// 将l1和l2节点同时向后移动
if (l1 != null) {
l1 = l1.next;
}
if (l2 != null) {
l2 = l2.next;
}
}
if (total > 0) {
current.next = new ListNode(total);
}
return result;
}
说实话阿粉确实是没有想到,后来完全是看了解析答案之后,才能完整的滤通这个代码,你们也是这样么?你也有刷题的习惯么?欢迎大家踊跃回答。
本文转载自公众号:java极客技术