leetcode速度才是王道 1.Two Sum

1. Two Sum

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

  题目的意思是给你一个目标数(target),让你从给定的数组中找出两个数nums[x]和nums[y]相加等于target,并返回这两个值的索引x+1和y+1(需要加1是因为题目要求索引不是以0开始的)。

  这个题目的一个难点是target可以是0也可以是负数,这一点题目里没说清楚,在测试用例里面会出现。因为追求速度,所以所有leetcode的代码我都会用C写。

  因为第一次刷leetcode,没啥经验的我一上来就too young too simple的来了个全暴力的搜索法,先固定第一个数,然后从固定的数往后面搜索第二个数,这个原始的暴力破解法时间复杂度接近n2

 1 int* twoSum(int* nums, int numsSize, int target) {
 2     int *indices = (int*)malloc(2 * sizeof (int));
 3     for(int i=0; i<numsSize-1; i++)
 4     {
 5         for(int j=i+1; j<numsSize; j++)
 6         {
 7             if(nums[i] + nums[j] == target)
 8             {
 9                 indices[0] = i;
10                 indices[1] = j;
11                 return indices;
12             }
13         }
14     }
15     return NULL;
16 }

Time Limit Exceeded  被leetcode无情的拒绝了

  于是我开始了新的尝试,最开始想到的是调整外存循环的步长,也就是每次固定两个数据,第二个数据分别和固定的两个数据相加然后和target进行比较,这样做的目的主要是为了减少内存访问的次数,以为第二个数据取一次可以使用两次,时间复杂度依旧接近n2,应该是因为内存遍历次数减少,所以有幸通过。代码如下:

 1 int* twoSum(int* nums, int numsSize, int target) {
 2     int *indices = (int*)malloc(2 * sizeof (int));
 3     for(int i=0; i<numsSize-1; i+=2)
 4     {
 5         if(nums[i] + nums[i+1] == target)
 6         {
 7             indices[0] = i + 1;
 8             indices[1] = i + 2;
 9             return indices;
10         }
11         else
12         {
13             for(int j=i+2; j<numsSize; j++)
14             {
15                 if(nums[i] + nums[j] == target)
16                 {
17                     indices[0] = i + 1;
18                     indices[1] = j + 1;
19                     return indices;
20                 }
21                 else if(nums[i + 1] + nums[j] == target)
22                 {
23                     indices[0] = i + 2;
24                     indices[1] = j + 1;
25                     return indices;
26                 }
27             }
28         }
29     }
30     return NULL;
31 }

Runtime: 224 ms

  这次很荣幸的通过了,不过速度实在是太慢,我尝试过将外层循环的步长改为4,速度有提升,但是没有太想大效果,最好的速度是194ms。

  看到最好的4ms的速度,我依然不想就此罢手,于是我想到了hash,在使用hash的时候,内存的用法有很多比较不错的改进,都会记录在此。

  1 #define LIST_LENGTH 100
  2
  3 typedef struct Item Item;
  4
  5 struct Item
  6 {
  7     int value;
  8     int index;
  9     Item * next;
 10 };
 11
 12 typedef struct List
 13 {
 14     Item *head;
 15     Item *end;
 16 } List;
 17
 18 void insertItem(List *list, Item *item)
 19 {
 20     int key = ( item->value / 2 ) % LIST_LENGTH;
 21
 22     if ( !list[key].head )
 23     {
 24         list[key].head = item;
 25         list[key].end = item;
 26     }
 27     else
 28     {
 29         list[key].end->next = item;
 30         list[key].end = item;
 31     }
 32
 33 }
 34
 35 int searchValue(List *list, int value)
 36 {
 37     int key = (value / 2) % LIST_LENGTH;
 38
 39     Item *p = list[key].head;
 40
 41     while(p)
 42     {
 43         if(value == p->value)
 44         {
 45             return p->index + 1;
 46         }
 47         p = p->next;
 48     }
 49     return 0;
 50 }
 51
 52 void listFree(List *list)
 53 {
 54     for(int i=0; i<LIST_LENGTH; i++)
 55     {
 56
 57         Item *p, *q;
 58         p = list[i].head;
 59
 60         while(p)
 61         {
 62             q = p->next;
 63             free(p);
 64             p = q;
 65         }
 66
 67     }
 68
 69     free(list);
 70 }
 71
 72 int* twoSum(int* nums, int numsSize, int target) {
 73     if(numsSize < 100)
 74     {
 75         return twoSumShort(nums, numsSize, target);
 76     }
 77     int *res = (int*)malloc(2 * sizeof(int));
 78     List *list = (List*)malloc(sizeof(List) * LIST_LENGTH);
 79     memset(list, 0, sizeof(List*) * LIST_LENGTH);
 80     for(int i=0; i<numsSize; i++)
 81     {
 82         Item *item = (Item*)malloc(sizeof(Item));
 83         item->value = nums[i];
 84         item->index = i;
 85         item->next = NULL;
 86         insertItem(list, item);
 87     }
 88
 89     for(int i=0; i<target; i++)
 90     {
 91         int x = searchValue(list, i);
 92         int y = searchValue(list, target - i);
 93
 94         if(x && y)
 95         {
 96             res[0] = x < y ? x : y;
 97             res[1] = x + y - res[0];
 98             break;
 99         }
100     }
101     listFree(list);
102     return res;
103 }

这个是使用hash的雏形,当然这份代码里面存在很多问题,一下会一一解释

问题1:为了减少搜索的次数,代码line89~100尝试将target分为[0, target], [1, target-1]...这些可能性,然后分别使用hash搜索前后连个值,但是这里漏掉的数组中有负数的可能性,比如说target = 0 = -10000 + 10000,所以不能以target作为搜索的支点。

问题2:在构建hash表的过程中,line80~87重复了numsSize次调用malloc为节点分配内存,因为malloc是很耗时的一个函数,所以这会导致函数运行起来非常慢。

问题3:因为动态分配内存了,所以引入了释放内存的操作ListFree函数,这个是可以想办法避免的

对照以上问题,经过多次尝试后,终于出了一个最快版的,代码如下:

 1 #define LIST_LENGTH 1000
 2
 3 typedef struct Item Item;
 4
 5 struct Item
 6 {
 7     int value;
 8     int index;
 9     Item * next;
10 };
11
12 typedef struct List
13 {
14     Item *head;
15     Item *end;
16 } List;
17
18 void insertItem(List *list, Item *item)
19 {
20     int key = (unsigned int)( item->value) % LIST_LENGTH;
21     if ( !list[key].head )
22     {
23         list[key].head = item;
24         list[key].end = item;
25     }
26     else
27     {
28         list[key].end->next = item;
29         list[key].end = item;
30     }
31
32 }
33
34 int searchValue(List *list, int value)
35 {
36     int key = (unsigned int)(value) % LIST_LENGTH;
37     Item *p = list[key].head;
38
39     while(p)
40     {
41         if(value == p->value)
42         {
43             return p->index + 1;
44         }
45         p = p->next;
46     }
47     return 0;
48 }
49
50 // use hash
51 int* twoSum(int* nums, int numsSize, int target) {
52     int *res = (int*)malloc(2 * sizeof(int));
53     List list[LIST_LENGTH];
54     memset(list, 0, sizeof(List) * LIST_LENGTH);
55     Item item[numsSize];
56     memset(item, 0, sizeof(Item) * numsSize);
57
58     for(int i=0; i<numsSize; i++)
59     {
60         item[i].value = nums[i];
61         item[i].index = i;
62         insertItem(list, &item[i]);
63     }
64
65     for(int i=0; i<numsSize; i++)
66     {
67         int x = i + 1;
68         int y = searchValue(list, target - nums[i]);
69
70         if(x && y && (x ^ y))
71         {
72             res[0] = x < y ? x : y;
73             res[1] = x + y - res[0];
74             break;
75         }
76     }
77
78     return res;
79 }

Runtime: 4 ms

  看这这个4ms,真是激动不已啊,终于站到最前面了,这份代码对前面三个问题都进行处理。

问题1解决方法:

  第一个数据通过nums数组来固定,通过hash来查找第二个相配的的数据 searchValue(list, target - nums[i]);如果不出现hash冲突,那麽此算法的时间复杂度为N,很好的解决了第一个问题

问题2解决办法:对于通过for循环重复为数组中每个节点分配内存的做法可以通过一次性给所有节点分配好内存来改进 Item *item = (Item*)malloc(sizeof(Item) * numsSize); ,这样先将所有的内存分配好,构建hash表的时候,直接通过给指针赋值,让其指向对应的内存就行,这样也方便也释放内存。

问题3解决办法:因为依旧使用了malloc分配堆内存,需要手动释放,所有问题2的解决办法依旧不是最好。因为只需要返回两个index,所有最后选择了使用栈内存(代码line53~56),这样速度够快,也不需要手动释放内存,因此,一个4ms的算法就此诞生啦!

时间: 2024-11-08 11:17:20

leetcode速度才是王道 1.Two Sum的相关文章

leetcode速度才是王道 2. Add Two Numbers

2. Add Two Numbers You are given two linked lists representing two non-negative numbers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list. Input: (2 -> 4 -> 3)

重内容,轻语言,优先的产品才是王道,HTML5不能代表一切

两月余前,关于FACEBOOK放弃HTML5而改用原生的事被炒的沸沸扬扬,一时间HTML5的处境下滑冰点,归其原因是"慢".Webkit的解析过程先后需要经过解析.建立DOM树.获取对应资源.布局.建立渲染树.绘图到展示.这跟传统的商品流通路径非常相似:工厂.品牌公司.总代理.经销商.卖场.消费者.由于环节太多层层加价,产品到达消费者手里往往价格居高不下.而HTML5的代价同样是用户为使用产品而付出的时间成本.原生就好比F2C,用户直接从"工厂"即本机获取产品/服务

技术才是王道、程序员修炼必备武器

信息技术的发展时间虽然不长,但其爆炸式的发展速度使信息技术迅速覆盖社会和人类生活的各个角落.程序员们是这场信息化浪潮的见证者之一,更是其中的主要参与者,这是时代赋予每个程序员的机会和责任. 信息技术的更新速度是惊人的,程序员的职业生涯则是一个要求不断学习的过程,永远不能固步自封.本人在工作期间曾看见过很多程序员只要有闲暇时间就?浏览一些没有太大作用的网页,在网上聊天,打游戏,浪费了大量的时间,十分不可取.而另外一种情况是,IT技术的日新月异使很多程序员眼花缭乱,什么都想?学,却又不知从何学起,今

leetcode第一刷_Binary Tree Maximum Path Sum

这是道好题. 题目指明了路径的起点和重点是任意的,可以是一个节点可以是包含父节点和左右子树的路径.问题的关键是,这个左右子树返回的不能是整个树的最大和,而只能是包含了这个子树根节点的一条左路径或者右路径.也不知道这么说是否明白,画图说明是在太麻烦了.. 这么说吧,题目虽然对路径的条件没有限制,但是路径还是要求的,如果直接返回左右子树的结果,那么这个结果可能是来自于一个叶子节点,也可能是整个子树,什么都有可能,当这个结果实际上是由不包含子树的根节点计算出来的,那么接到上一层的时候,用了这个结果,而

SQL Server Profiler使用教程,通俗易懂才是王道

SQL Server Profiler使用教程,通俗易懂才是王道 做开发,平时难免和数据库打交道,特别是写存储过程,对于我们这些不常写SQL的人来说是一件极其痛苦的事,每次写完运行总是有错,如果用的是本地数据库的话还好,可以在本机调试SQL,那如果在数据库在服务器上面,调试被禁用,那就悲剧了~ 最近,由于工作需求,写一个存储过程,有几十个参数,数据库在服务器上面,写完了,测试完查询语句没有问题,但是到了项目里面查询时就出错,很是气人!想到了用SQL Profiler这个工具,打开选择默认设置,开

leetcode 刷题之路 66 Path Sum II

Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given sum. For example: Given the below binary tree and sum = 22, 5 / 4 8 / / 11 13 4 / \ / 7 2 5 1 return [ [5,4,11,2], [5,8,4,5] ] 给定一个二叉树和数字sum,输出二叉树中从根节点到

现金流才是王道,中芯国际行情爆发的三大筹码

你已经错过了舜宇光学(123.8, -7.50, -5.71%)(02382)低吸的机会,如今含泪看着其二十几倍的市净率望而却步,而现在你还要错过中芯国际(8.16, -0.47, -5.45%)(00981)吗? 智通财经观察到,今年以来,中芯国际的股价进行了较大幅度的局部调整,截止2017年8月11日,调整幅度达41%,不过近期该公司股价有触底回升的趋势,近半个月涨幅23.2%,已经成功突破120日均线价位,欲突破前技术压力位9.4港元,露出明显的圆弧形态. 实际上,近几年中芯国际的业绩表现

原生开发才是王道

之前很多朋友问我这么一个问题,说刚做 Android 开发不久,但是市面上被 H5,React Native 等这些移动趋势冲击的不得不考虑要不要转头学习 H5 或者 RN ,跟上时代以后才能有更好的发展,今天我就写篇文章给大家详细解惑下,并且说下我的看法. Native App Native 开发也即原生开发,如果你是做 Android 开发,那么大部分都是用 Java 语言来编写的,如果你是做 iOS 开发,则是使用 Objecttive C 或者 Swift 来进行编写的,这些都是官方的标

总编下午茶:技术创新才是王道

由于WOT活动和其他的一些选题,老杨近年来接触了不少技术创业者.这其中有一群人很有特点:他们系出名门--曾经在全球级别的互联网企业承担技术方面骨干,然后回到中国进行创业.这其中包括前LinkedIn 商业分析部门高级总监张溪梦.前Google总监周杰.前Facebook技术专家覃超等.但给老杨一个非常深刻的印象就是,虽然大家都到了"大叔"的年龄,但技术本色却基本上没有什么变化.这一点,在日志易的陈军身上似乎更加明显. 十几年码农缘何走上技术创业之路 和陈军约在了下午两点,老杨比预定时间