LeetCode Week1: Two Sum、Add Two Numbers、Median of Two Sorted Arrays

这一周实现了LeetCode Algorithms中的1、2、4题,分别是Two Sum(easy)、Add Two Numbers(medium)、Median of Two Sorted Arrays(Hard)。

一、Two Sum

题目描述:Given an array of integers, return indices of the two numbers such that they add up to a specific target. You may assume that each input would have exactly one solution, and you may not use the same element twice.

Examples:

Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].

分析:题目是要找到和为目标值的两个数的位置,因为保证了答案只会有一组,难度已经减了很多。直接循环判断哪一个数的值等于目标值减去当前值即可得到两个数的位置,这个是最基础的方法,算法相对也比较简单,两重循环即可解决,算法的复杂度是 O(n2) 。关键部分代码如下。

for(vector<int>::iterator it = nums.begin() ;it!=nums.end();it++){ 
    diff = target - *it;
    j = i+1;
    for(vector<int>::iterator it1 = it+1 ;it1!=nums.end();it1++){
        if(diff == *it1){
            result.insert(result.begin(),i);
            result.insert(result.begin()+1,j);
            return result;
        }
        j +=1;
    }
    i += 1;
}  

通过Submission一看,这种方法跑的Run Time要238ms,实在是太慢了==,所以又有了想要提升的想法。
考虑到了原本参数传递中就有了容器,很多容器都可以直接做查询,而且每次比对vector中的数值时同时还需要考虑其对应位置,这样的情况下用map会比较好,我使用了unordered_map, map以及hash_map,hash_map在没有冲突的情况下运算量是 O(1) ,算是最好的了,但是它并没有被引入标准库了,所以这里不测试它。

vector<int> twoSum(vector<int>& nums, int target) {
    map<int,int>hash;
    vector<int> result;
    int diff = 0;
    for(int i = 0;i < nums.size();i++){ 
        diff = target - nums[i];
        // 寻找已经计算过的数值中有没有与差值相等的
        if(hash.find(diff) != hash.end()){
            result.push_back(hash[diff]);
            result.push_back(i);
            return result;
        }
        // 没有与差值相等的数值,将当前位置值及位置放入hash中备用
        hash[nums[i]] = i;
    }
}

hash变量的类型可以改为unordered_map,相应的头文件也从#include < map > 换为#include < unordered_map > 即可。两种方法都能正确运行并找到结果,通过LeetCode测试两种方法的Run Time 都是9ms,明显优于第一种方法。
通过查阅其他博客,可以了解到unordered_map运行效率比map高,但占用内存也比map大,估计是因为LeetCode的测试用例都比较小,所以运行时看不出太大区别。

二、Add Two Numbers

题目描述:You are given two non-empty linked lists representing two non-negative integers. 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. You may assume the two numbers do not contain any leading zero, except the number 0 itself.
Examples:

Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8

分析:其实这个题考查的主要是对链表的操作,无奈自己本科学的忘得最多的就是链表了,所以这个题目的难点主要就是对链表的基本操作了。

注意几个临界条件:
1. l1与l2的长度不一致,需要计算下一位时l1->next或者l2->next会为空,那么就会出错。利用三元操作符进行判断,如果链表已经为空0,那么不指向next,计算的值也默认为0;

 v1 = ((l1 != NULL)?l1->val:0);
 v2 = ((l2 != NULL)?l2->val:0);
 ……
 l1 = ((l1 != NULL)?l1->next:NULL);
 l2 = ((l2 != NULL)?l2->next:NULL);
  1. 之前没有考虑好最后一位计算时的进位问题,导致对于Input:(5)+(5)的问题出错,得到的结果为0,其实应该是[0,1],这也说明只有进位为0,l1和l2都为空,计算才应该停止。
    具体代码如下:
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        int v1 = 0, v2 = 0;
        int sum = 0, in = 0;
        ListNode* result = new ListNode(0);
        // head指向result的头指针位置
        ListNode* head = result;
        while(l1!=NULL || l2!= NULL || in != 0){
            // 保证每次都有值运算
            v1 = ((l1 != NULL)?l1->val:0);
            v2 = ((l2 != NULL)?l2->val:0);
            sum = v1+v2+in;
            in = sum/10;
            head->next = new ListNode(sum%10);
            head = head->next;
            l1 = ((l1 != NULL)?l1->next:NULL);
            l2 = ((l2 != NULL)?l2->next:NULL);
        }
        return result->next;
    }
};

反思:之前一直认为head=result是指head的值与result的值相等,但是对于链表,这是表示head执行了result的头指针的地址,我们对head进行操作,也会相应的改变result的值,不断的修改head=head->next,使得head指向一下结点的位置,而不是用result=result->next的操作。

三、Median of Two Sorted Arrays

题目描述:There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of the two sorted arrays. The overall run time complexity should be O(log(m+n)) .

Example1

nums1 = [1, 3]
nums2 = [2]
The median is 2.0

Example2

nums1 = [1, 2]
nums2 = [3, 4]
The median is (2 + 3)/2 = 2.5

分析:这个题如果没有算法复杂度要 O(log(n+m)) ,那么直接把两个vector的内容合在一起,做一个排序,再返回中位数的值即可。但是加了约束后,就需要考虑复杂度的问题了。

因为复杂度是带 log 的参数,自然而然就想到了使用分而治之的想法,最开始想的是寻找两个vector中的中位数的方法,方法虽然可行但是会很受vector中数值个数的影响,需要考虑奇数偶数序列的多种情况,所以这个方法也放弃了。

最后决定使用的是寻找第 k 个数的方法,即寻找第 k(=m+n2) 个数, m n 分别是两个序列的长度,两个序列都是有序的,假设我们的两个序列A和B,其中的元素分别如下:
LeetCode Week1: Two Sum、Add Two Numbers、Median of Two Sorted Arrays_第1张图片
如果k是奇数,比如11,那么将两个数组合起来的第 6=(112+1) 个数,那么这6个数要么一部分来自于序列A,一部分来自于序列B,要么全来自于一个序列,假设两个序列的长度足够长,那么我们的操作如下:

  1. 我们先做调整,使得A为长度较小的序列,B为长度较大的序列;
  2. 选取A中的第 k2 个元素(如果A的长度小于 12 ,那么就直接选取A的第 m 个元素,这里假设先假设是 k2B (k-\frac{k}{2})$个元素;
  3. 比较 ak2 bk2
    • ak2 = bk2 ,那么第 k 个元素就是这两个元素中随意一个;
    • ak2 > bk2 ,那么 b1 bk21 的元素肯定在前k个元素中,肯定比第 k 个元素小,可以把它们去除掉,减少选择的数目。这样需要判断的个数就剩下 (kk/2) 个了;
    • ak2 < bk2 ,操作同上,去除 a1 ak21 的元素。
  4. 重复上述操作,当到达下述边界条件时,即可得到第 k 个元素
    • 如果短序列A已经空了,那么第 k 个元素一定不在A中,而是序列B中的 bk1
    • 如果已经去除了 k1 个元素两个序列都还未空,那么两个序列中较小的零元素就是第 k 个元素。

需要注意的是,如果总的序列的长度为偶数,那么需要找到的中位数为两位,那么还需要找到第 k+1 个数,最后求出均值,求第 k 个数的方法同上。

鉴于每一次都是调用前一次的方法,递归方法可以用来实现本次实验,因为上述的讨论中都是直接考虑了第几个数,但是序列的计数是从0开始的,所以在编码的时候需要-1。

程序的相关代码如下:

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int s1 = nums1.size(), s2 = nums2.size();
        if ((s1 +s2)%2==0){
            // 如果序列总长度为偶数,找到两个数,其均值为中位数
            vector<int>n1,n2;
            n1 = nums1,n2 = nums2;
            return (findkth(nums1,nums2,(s1+s2)/2+1)+findkth(n1,n2,(s1+s2)/2))/2;
        }
        else
            return findkth(nums1,nums2,(s1+s2)/2+1);
    }
    double findkth(vector<int>& nums1, vector<int>& nums2,int k){
    // 保证前一个序列一直是短序列
        if(nums1.size() > nums2.size())
            return findkth(nums2,nums1,k);

    // 第k个数不在nums1中
        if(nums1.size()==0)
        return nums2[k-1];

    // 两个序列中比第k个数小的数都已经去除
    // 两个序列中较小的首元素就是第k个数
    if(k == 1)
        return ((nums2[0]>nums1[0])?nums1[0]:nums2[0]);

    int k_2,len1,ns1,ns2;
    len1 = nums1.size();
    k_2 = k/2, ns1 = ((k_2>len1)?len1:k_2), ns2 = k - ns1;
    if(nums1[ns1-1] < nums2[ns2-1]){
        // 去除前k个数中的ns1个数,减少对比工作量
        nums1.erase(nums1.begin(),nums1.begin()+ns1;
        k -= ns1;
    }
    else if(nums1[ns1-1] > nums2[ns2-1]){
        nums2.erase(nums2.begin()+0,nums2.begin()+ns2);
        k -= ns2;
    }
    else
        return nums1[ns1-1];

    return findkth(nums1,nums2,k);
    }
};

反思:这次的实验理清了思路之后其实就能很快实现了,但是粗心的我还是会在判别最终的临界条件时出错。最后答案一直出错的问题是在传参上,因为引用传参直接就修改了两个vector的值,对于总序列长度为偶数需要两次调用findkth()函数时第二次调用的函数会直接使用上一次已经被去除了一部分元素的nums1和nums2,造成了计算结果的错误。后来我比较偷懒的多声明了两个变量n1和n2,来解决这个问题,其实还可以直接传vector的起始位置来避免使用erase,因为时间关系这里就不多做叙述了。

感觉太久没刷题了生疏了好多== 但是刷题的感觉很爽啊,虽然中途卡了好久。立个小小的flag,以后要每天刷一题^^

你可能感兴趣的:(LeetCode)