Monday, August 31, 2015

Leetcode second round: 8/31


这两天身体不舒服,没有按照原定计划完成,算是整理下思绪,希望能更高的效率做题把.

today's mission
1-28

leetcode 数字有关运算题汇总:这类题目在面试中还是出现频率很高的。

2 .    add two numbers
这个题目如果方法选的不好,思路不清晰,做起事是很痛苦的!!!一开始,我想着把 l1 和 l2的数加在一起然后放在l1的值里面,不加任何辅助的指针,但是这样做的坏处是,当L2 比 l1长,l1到达了NULL, l1再也不能放值了,这样的问题如果不用一个辅助指针根本无法处理啊!既然需要用到辅助指针,为什么不想Merge two sorted list那样,用两个辅助指针, dummy head + cur(TAIL), 这样可以非常方便地处理两个list不等长的结果。
时间复杂度: O(n+m)
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        if (l1 == NULL && l2 == NULL) return NULL;
        if (l1 == NULL) return l2;
        if (l2 == NULL) return l1;
        int cnt = 0;
        ListNode* head = new ListNode(0);
        ListNode* cur = head;
        while (l1 != NULL || l2 != NULL) {
            if (l1 != NULL && l2 != NULL) {
                int tmp =  (l1->val + l2->val + cnt) % 10;
                cnt =  (l1->val + l2->val + cnt) / 10;
                l1->val = tmp;
                cur->next = l1;
                cur = l1;
                l1 = l1->next;
                l2 = l2->next;
            } else if (l1 != NULL) {
                int tmp =  (l1->val + cnt) % 10;
                cnt =  (l1->val + cnt) / 10;
                l1->val = tmp;
                cur->next = l1;
                cur = l1;
                l1 = l1->next;
            } else if (l2 != NULL) {
                int tmp =  (l2->val + cnt) % 10;
                cnt =  (l2->val + cnt) / 10;
                l2->val = tmp;
                cur->next = l2;
                cur = l2;
                l2 = l2->next;
            }
        }
        if (cnt == 1) {
            cur->next = new ListNode(1);
        }
        return head->next;
    
    }
};


7.     reverse integer
这个题目要注意一点是, 一个数reverse过程中可能会出现overflow的情况, 对overflow的处理是, 如果是负数,返回 MIN, 如果是正数, 返回MAX,
关键是如何去判断这个数在reverse过程中是否overflow。sum的计算公式是: sum * 10 + n , 要使在计算过程中不越界, sum * 10 + n  <= INT_MAX, 所以这就是是否越界的判断公式。 
class Solution {
public:
    int reverse(int x) {
        int flag = false;
        if (x < 0 ) flag = true;
        x = abs(x);
        int sum = 0;
        while (x) {
            int n = x % 10;
            int c = x / 10;
            if (sum <= (INT_MAX - n) / 10) {
                sum *= 10;
                sum += n;
            } else {
                return 0;
            }
            x = c;
        }
        if (flag) return sum*(-1);
        return sum;
    }
};
8.     string to integer
这个题目虽然简单,但是还是属于特别容易出错的题目, 在开始做之前,应该先跟面试官弄清楚转换的规则是什么, 比如这个题目里面规定的是: 1. 从第一个非空格的char开始转换 2. 如果invalid string , 返回 0, 否则返回具体的值, 注意overflow的情况下返回INT_MIN, INT_MAX . 3. valid string是指+、-、digit, 并且要求, 只能有一个符号位,并且紧跟着数字,其他情况下都是invalid。
这个题目逻辑最清晰的写法是: 将遇到第一个+, -或者digit的情况提出来利用辅助函数 convert来进行计算第一个可能valid的string, 在convert里函数只能处理digit的字符, 其他字符,return 0;

class Solution {
private:
    int convert(string str, int idx, bool positive) {
        int rst = 0;
        for (int i = idx; i < str.size(); i++) {
            if (isdigit(str[i])) {
                if (rst <= (INT_MAX - (str[i] - '0')) / 10) {
                    rst *= 10;
                    rst += str[i] - '0';
                } else {
                    rst = INT_MAX;
                    return positive ? rst : INT_MIN;
                }
            } else {
               break;
            }
        }
        return positive ? rst : -rst;
    }
public:
    int myAtoi(string str) {
        if (str.size() == 0) return 0;
        int rst = 0;
        bool positive = true;
        for (int i = 0; i < str.size(); i++) {
            if (str[i] == ' ') {   //trim space
                continue;
            } else if (str[i] == '+') {
                positive = true;
                rst = convert(str, i+1, positive);
                break;
            } else if (str[i] == '-') {
                positive = false;
                rst = convert(str, i+1, positive);
                break;
            } else if (isdigit(str[i])) {
                rst = convert(str, i, positive);
                break;
            } else {
                break;
            }
        }
        return rst;
    }
};
9.     palindrome number
这个题目也是依据palindrome number的思想, 对数的首尾数字进行判断, 唯一一个需要注意的地方是, 在计算过程中count可能溢出,因为count在后面 count = count/10的操作, 可能会造成溢出, eg: x = 1000000001, 10位数,*10, 11位数,就溢出了。一般机器能handle的最大int值 2147483647,10位, 11位的数就直接溢出了。
class Solution {
private:
    bool helper(int x, long count) {
        while (count > 1) {
            int last = x % 10;
            int first = x/count;
            if (last != first) return false;
            x = (x % count)/10;
            count = count/100;
        }
        return true;
    }
public:
    bool isPalindrome(int x) {
        if (x < 0) return false;
        long count = 1;
        int tmp = x;
        while (tmp) {
            tmp = tmp/10;
            count = count * 10;
        }
        count /= 10;
        return helper(x, count);
    }
};

12.   Interger to Roman

13. Roman to Integer

29: divide two integers
这个题目的算法还是记住比较好,用的其实是 a/b -> a = b * 2 ^n + b * 2^(n-1) +.....+ b* 2^0;
每次通过移位操作计算n, 但是要注意INT_MIN如果直接利用abs转换成负数的话,会造成溢出,所以我们需要先把INT_MIN --> long long的范围内再进行转换成正数。还要注意corner case的判断: 当 dividend 是INT_MIN, divisor 是 -1的时候, 会造成溢出,直接返回INT_MAX;

43.  multiply strings

每次做这种大数乘的题目就很慌张,有点怕,不太会做这种数学题,但是大数乘的题目在面试中还是经常考到的,不会做也要学会多做做就不怕了。
这个题目由于数字都是用string表示的,所以最好先把这些string都reverse一下,然后需要一个额外的空间来存储乘法相对应位的累加结果,然后在对这些累加的结果进行处理,比如需要往前进位啥的,最后由于一开始开辟的是最大的可能空间,如果最后不需要那么多的话,会有前导0的出现, 所以最后我们需要去掉前导0。 用到了string erase的函数。

class Solution {
public:
    string multiply(string num1, string num2) {
        string rst = "";
        if (num1.size() == 0 || num2.size() == 0) return rst;
        //reverse two strings
        reverse(num1.begin(), num1.end());
        reverse(num2.begin(), num2.end());
        //get sum in each position
        int M[num1.size() + num2.size()] = {0};
        for (int i = 0; i < num1.size(); i++) {
            for (int j = 0; j < num2.size(); j++) {
                M[i+j] += (num1[i] - '0') * (num2[j]-'0');
            }
        }
        //dealing with these sums
        for (int i = 0; i < num1.size() + num2.size(); i++) {
            int digit = M[i] % 10;
            int cnt = M[i]/10;
            if (i+1 < num1.size()+num2.size()) {
                M[i+1] += cnt;
            }
            char tmp = (digit + '0');
            rst += tmp;
        }
        reverse(rst.begin(), rst.end());
        int i = 0;
        while (i < rst.size()-1 && rst[i] == '0') i++;
        rst.erase(0,i);
        return rst;
    }
};

67. add  binary

1. 2 SUM

2sum 有很多变形的,比如: 有的题目要求找出一对具体的两个元素,有的题目要求找出一对Index, 有的题目要求找出所有的配对的元素,有的题目要求找出所有配对的Index, 
如果要求找出所有配对的Index, 最好是不要对之前的元素排序,直接用hashmap的方法来做, 因为排了序之后Index的顺序都改变了。 如果是要求找出具体的元素,两种方法都是可以做的,看面试官要求是什么。

15.  3 SUM
3sum 这个题目还是有很多陷阱,两种方法:hashmap/sort
sort : 因为题目里面是有duplicates elements的,所以在每一步移动一个index的时候,都必须要先看新的index指向的元素是否与之前的元素相等。在去duplicate的时候要注意,先移动index,再去处理duplicate, 防止没有duplicate的时候,没有移动任何Index, 造成死循环。
hashmap: 因为这个题目里面含有重复元素, 所以利用hashmap不太好处理duplicate, 而且不管用hashmap还是sort,时间复杂度最高还是O(n^2), 排序不会影响时间复杂度。
所以 对于含有duplicates的题目,还是用sort的方法比较好。

16. 3SUM closest
这个题目和3 sum的解法是相同,不同的是每次每次移动任何一个指针,都要计算一下当前的差值,并且维护一个最小差值,最后那个最小差值对应的sum就是结果。
要想提高程序的运行速度,我们需要把重复的运算和操作合并,使其只运行一次。

18. 4 SUM  --> K SUM
如果4 SUM继续利用3 SUM的方法进行的话, 时间复杂度是O(n^3)
但是联想从2SUM --> K SUM的推广的方法,我们是可以利用2SUM来解的, 我们可以先得到所有2 SUMpair, 并且存在hashmap中,然后再进行2 sum, 时间复杂度是O(n^2), 算法的思想很简单,但是由于可能有重复的pair出现,所以有很多的case 需要考虑。

3. longest substring without repeating character
这个题目是string里面 sliding window + contain duplicates的结合, 一开始想到 用 unordered_set 来处理发现duplicates的情况,后来有注意到unordered_set作用的对象是char, 对于 256 ASCII字符来说可以使用 256 size的array 来代替 set。这样可以提高set里面erase的复杂度。 时间复杂度: O(n * 2)
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if (s.size() == 0) return 0;
        //unordered_set<char> iset;
        int iset[256] = {0};
        int left = 0;
        int right = 0;
        int gmax = 0;
        for (; right < s.size(); right++) {
            if (iset[s[right]] == 0) {
                iset[s[right]]++;
                gmax = max(gmax, right-left+1);
            } else {
                while (s[left] != s[right]) {
                    iset[s[left]] = 0;
                    left++;
                }
                left++;
            }
        }
        return gmax;
    }
};
4. median of two sorted array

5. longest palindrome substring
M1: use 2D dp, check if palindrome and update the maximum length
只是这种简单的dp每次都没有一次性写对,而且每次都是把j == i+1 写成了 j = i+1, 每次还看半天都不能发现的bug !!!!
class Solution {
public:
    string longestPalindrome(string s) {
        if (s.size() == 0) return s;
        bool M[s.size()][s.size()];
        int gmax = 0;
        int left = 0;
        for (int i = s.size()-1; i >= 0; i--) {
            for (int j = i; j < s.size(); j++) {
                if ((j == i) || ((s[i] == s[j]) && (j == i+1 || M[i+1][j-1]))) {
                    M[i][j] = true;
                    if (gmax < j-i+1) {
                        gmax = j - i +1;
                        left = i;
                    }
                } else {
                    M[i][j] = false;
                }
            }
        }
        return s.substr(left, gmax);
    }
};
时间复杂度: O(n^2) 空间复杂度: O(n^2)
M2:专门解决 longest palindrome string 问题的一个算法是 : Manacher's algorithm, 时间复杂度O(n), 非常巧妙的一个算法,

6. zigzag conversion
纯粹的数学题


10.  regular expression matching



11. container with most water
这个题目感觉是简化版的trapping rain water, 以线作为板子,只需要找到两根线和x轴组成的区域面积最大, 也是histogram 板子,短板原理, 两个pointer, 分别指向首尾两端, 谁短移动谁,并且维护最大面积, 这个题目这样其实就是每次都计算出每块短板能组成的最大面积。

follow up:  42. trapping rain water
trapping rain water 比 container with most water处理上要难一些,但是两题的思路是相同的,都是利用两个指针指向两端,然后移动短板,这个题目在移动短板的时候进行的处理操作是, 对每一个短板计算能储存水的量, 这个短板能储存水的量 = maxLeft - height[left] or  maxRight - height[right], 需要维护三个值,一个是左边板子的最大高度, 右边板子的最大高度, 以及sum。


14: longest common prefix

这个题目求公共前缀, 以第一个string为基准,一个字符一个字符地比较其他字符串相应位置的字符,如果都相同,那么说明找到了一个common char, 加在 rst的尾部。

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        string rst = "";
        if (strs.size() == 0) return rst;
        if (strs.size() == 1) return strs[0];
        for (int i = 0; i < strs[0].size(); i++) {
            for (int j = 1; j < strs.size(); j++) {
                if (i >= strs[j].size() || strs[0][i] != strs[j][i]) return rst;
            }
            rst += strs[0][i];
        }
        return rst;
    }
};


17: letter combinations of a phone number
这个题目一看输出所有的结果,肯定是dfs来解题, dfs注意树的层数, 每一层什么含义和每一层的分支。
eg: 23 :
position0 :               a    b    c
                                /|\   /|\   /|\
position 1:             def  def......

19. remove nth node from linked list
这个题目很简单,但是还是写了两遍才写对。 要注意两点,首先这个题目可能head会被remove, 所以保险起见,我们加一个dummy node, 其次, 当找到了要删的节点位置后, pre->next = slow->next, 这个是逻辑公式,每次写都认为是 pre->next = fast, 其实这个逻辑式一看就是错的,因为fast并不是就是在slow之后啊, 很简单的例子,比如 n= 5, 那么fast 和 slow 相隔就是5个node的距离了。
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if (head == NULL) return NULL;
        ListNode* slow = head;
        ListNode* fast = head;
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* pre = dummy;
        while (n > 0) {
            n--;
            fast =fast->next;
        }
        while (fast) {
            fast = fast->next;
            pre = slow;
            slow = slow->next;
        }
        pre->next = slow->next;
        return dummy->next;
    }
};

20: valid parentheses
这个题目判断括号对是否匹配, 用一个stack去匹配。
对这个stack进行push, pop只有两种情况, 当 stack不为空, 并且stack的 top 和 char c 可以匹配的时候,可以从stack里pop出一个元素,其他情况下,都把元素push到stack中。

21. merge two sorted list
23.  merge k sorted list
merge two sorted list 现在可以很熟练地写出来,但是merge k sorted list 才是重点, 这个题目从 2 -->k的扩展,体现了两种基本的思想: M1 : use  priority_queue to handle k object, sort, initial states and expand rules and do it in a breadth first search way.  M2: divide and conquer, iteratively apply 2 way method to get the k way method. you need to think the rules to get k. (DIVIDE AND CONQUER is generally used in k way questions and always works).
time complexity: O(n * k * logn) because every list node is pushed and popped from priority_queue , space complexity: M1:O(n*k) M2: O(1)

22. generate parentheses
利用dfs 就可以解决, 很简单。
24. swap nodes in pair
这个题目属于比较复杂的list里面的指针操作了,在进行比较复杂的指针操作的时候要注意, 必要的时候一定要记得断掉已经不存在的链接,或者无效的链接,否则,list会进入一个cycle, 比如这个题目里面每次 pre->next = NULL, cur node 和 next node之间的这个链接应该不再存在。要注意区分偶数个和奇数个nodes的情况。

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* pre = dummy;
        ListNode* cur = head;
        while (cur && cur->next) {
            ListNode* next = cur->next;
            ListNode* nnext = next->next;
            next->next = cur;
            pre->next = next;
            pre = cur;
            pre->next = NULL;
            cur = nnext;
        }
        if (cur) {
            pre->next = cur;
        }
        return dummy->next;
    }
};
25. reverse nodes in k-group

这个题目是上一个的2 --> k的拓展, 对于这个题目采取的做法是在2上进行修改, 其实整体上指针的操作逻辑是没有改变的,1. 指针操作还是需要小心,可以避免区别奇数和偶数的情况,可以统一处理。 2. tail 在
这个题目还是有很多细节要注意,一遍不容易写对。

26. remove duplicates in sorted array
27.  remove element
这两个题目都是属于array的操作,也可以应该在string 的操作上,
28. implement strStr()





No comments:

Post a Comment