leetcode刷题笔录-7

存储IP地址

  • 给出一个字符串,仅包含数字位,返回可能的IP地址组合。比如给出字符串"25525511135",返回["255.255.11.135", "255.255.111.35"],顺序是无关的。
  • 思路:关于IP地址的规则:如果字符串的长度大于12或者小于4,都不是合法的IP地址,IP地址共有4个字段,每一个字段都是0~255之间的整数,但是在字符串中,不可以出现诸如"255.025.100.0"这种地址,因为第二字段"025"的"0"会被省去,但是最后一位"0"却是允许的。仔细地考虑这些规则,建立一个函数,判断一个字符串是否是合法的num位IP地址,然后:s[1...n]间所有合法的4位IP地址就是:s[1]是合法的,加上s[2...n]构成的所有合法的3位IP地址……以此类推。
  • 实现:
  • class Solution {
    public:
        vector<string> restoreIpAddresses(string s) {
            return ipAddresses(s, 0, 4);
        }
    
        vector<string> ipAddresses(const string& s, int start, int num){
            int n = s.length() - start;
            vector<string> rslt;
            if(n > num*3 || n < num){
                return rslt;
            }
            if(num == 1){
                string ts = s.substr(start);
                if(validNum(ts)){
                    rslt.push_back(ts);
                }
            }
            else{ // num=2,3,4    
                int m = 3>n ? n : 3;
                for(int i=1; i<=m; i++){
                    string ts = s.substr(start, i);
                    vector<string> fss = ipAddresses(s, start+i, num-1);
    
                    if(validNum(ts)){
                        for(int j=0; j<fss.size(); j++){
                            rslt.push_back(ts+"."+fss[j]);
                        }                    
                    }
                }
                return rslt;
            }
        }
    
        bool validNum(const string& s){
            if(s.length() == 0){
                return false;
            }
            if(s[0]=='0' && s.length()!=1){
                return false;
            }
            
            stringstream ss;
            ss << s;
            int fld;
            ss >> fld;
            if(fld <= 255 && fld >= 0){
                return true;
            }
            else{
                return false;
            }        
        }
    };

解码

  • 某种消息包含了A-Z的字符,该消息使用了以下密码表加密:
    'A' -> 1
    'B' -> 2
    ...
    'Z' -> 26

    给出一则加密后的消息,试图给出所有可能的明文的种树,比如给出字符串"12",返回2,因为该字符串可被解码为两种明文:"AB"或"L"。11

  • 思路:仍然是动态规划,而且比较简单。建立一个数组nd,nd[i]表示字符串的前i+1位s[0...i]可被解码的种类数,nd[p]的取值可以根据nd[p-1]和nd[p-2],以及相关的细小规则给出。
  • 实现:
  • class Solution {
    public:
        int numDecodings(string s) {
            int n = s.length();
            if(n==0){
                return 0;
            }
            
            vector<int> nd(n+1, 0);
            nd[0]=1;
            if(n>=1){
                nd[1]= s[0]=='0' ? 0 : 1;
            }
            for(int i=1; i<n; i++){
                // To do : set nd[i+1]
                if(s[i]=='0'){
                    if(s[i-1]=='1' || s[i-1]=='2'){
                        nd[i+1] = nd[i-1];
                    }
                    else{
                        nd[i+1] = 0;
                    }
                }
                else{ // s[i] != 0
                    if(s[i-1]=='0'){
                        nd[i+1] = nd[i];
                    }
                    else if(s[i-1]=='1'){
                        nd[i+1] = nd[i] + nd[i-1];
                    }
                    else if(s[i-1]=='2'){
                        if(s[i]>'6'){
                            nd[i+1] = nd[i];
                        }
                        else if(s[i]<='6'){
                            nd[i+1] = nd[i] + nd[i-1];
                        }
                    }
                    else{
                        nd[i+1] = nd[i];
                    }                
                }
            }
            
            return nd[n];
        }
    };

灰色编码排列

  • 灰色编码排列是一种二进制的编码方式,两个连续的元素只有一个bit有差异。给出一个整数n,表示数据元素的位数,然后返回整个灰色编码排列。比如,给出数字2,返回序列[0,1,3,2],因为灰色编码如下:
    00 - 0
    01 - 1
    11 - 3
    10 - 2
  • 思路:还是很简单的,生成n位的灰色编码排列,先生成n-1位的排列($2^n$个元素),然后,先用0接在n-1位的排列每个元素的最前面,然后再用1做相同的事情,最后将两个排列的元素接在一起。需要注意的是:为了使变化只有1位,所以用0接在n-1位排列和用1接n-1位排列时,n-1位的排列的顺序应当是相反的,这样,用0接n-1位排列的结果的最后一个元素,和用1接的最后一个元素,它们才相差1位,那就是第一位(0和1)。至于是先用0接还是先用1接,其实是无所谓的。
  • 实现:
    class Solution {
    public:
        vector<int> grayCode(int n) {
            int x1=0;
            int x2=1;
            vector<int> gc;
            
            if(n==0){
                gc.push_back(0);
            }        
            if(n==1){
                gc.push_back(x1);
                gc.push_back(x2);
            }
            if(n>1){
                vector<int> tc = grayCode(n-1);
                for(int i=0; i<tc.size(); i++){
                    gc.push_back(pow(2,n-1)*x1+tc[i]);
                }
                for(int i=tc.size()-1; i>=0; i--){
                    gc.push_back(pow(2,n-1)*x2+tc[i]);
                }
            }
            return gc;
        }
    };

两个已排序数组的中位数

  • 在代价O(log(m+n))内找到两个已排序数组元素的中位数。
  • 思路:假设两个已排序数组分别为 $A\{a_{1},a_{2}...a_{n}\}$ 和 $B\{b_{1},b_{2}...b_{m}\}$。取 $A$ 和 $B$ 的中位数 $a_{mid-A}$ 和 $b_{mid-B}$ 并比较大小:如果 $a_{mid-A}$ 比 $b_{mid-B}$ 小,那么数组 $A$ 的前半部分不可能包含中位数了,数组 $B$ 的后半部分也不可能包含中位数,因此砍掉 $A$ 的前 $x$ 个元素和 $B$ 的后 $x$ 个元素($x$ 为 数组 $A$ 中的前半部分的元素个数 和 数组 $B$ 中的后半部分的元素个数 中的较小者)。
  • 实际用代码实现的时候,考虑了数组 $A$ 和 $B$ 的奇偶性,如果某个数组有偶数个元素,就选出两个中位数,并作一些易于理解但更细致的处理,比如:当具有偶数个元素的数组 $A$ 的较小中位数都比具有奇数个元素的数组 $B$ 的中位数都大的时候,才符合 $A$ 的中位数大于 $B$ 的中位数这样的条件。
  • 实现:
    int findMedianSortedArrays(int A[], int m, int B[], int n) {
        int lenA = m/2;
        int lenB = n/2;
        int len = lenA<lenB?lenA:lenB;
        bool frontcutA;
        if (m%2==1 && n%2==1){
            int midA = m/2;
            int midB = n/2;
            if (A[midA]<B[midB]){frontcutA = true;}
            if (A[midA]>B[midB]){frontcutA = false;}
            if (A[midA]==B[midB]){return A[midA];}
        }
        if (m%2==1 && n%2==0){
            int midA = m/2;
            int midB1 = n/2-1;int midB2 = n/2;
            if (A[midA]<B[midB1]){frontcutA = true;}
            if (A[midA]>B[midB2]){frontcutA = false;}
            if (A[midA]>=B[midB1] && A[midA]<=B[midB2]){return A[midA];}
        }
        if (m%2==0 && n%2==1){
            int midA1 = m/2-1;int midA2 = m/2;
            int midB = n/2;
            if (A[midA2]<B[midB]){frontcutA = true;}
            if (A[midA1]>B[midB]){frontcutA = false;}
            if (A[midA1]<=B[midB] && A[midA2]>=B[midB]){return B[midB];}
        }
        if (m%2==0 && n%2==0){
            int midA1 = m/2-1;int midA2 = m/2;
            int midB1 = n/2-1;int midB2 = n/2;
            if (A[midA2]<B[midB1]){frontcutA = true;}
            if (A[midA1]>B[midB2]){frontcutA = false;}
            else{return A[midA1]>B[midB1]?A[midA1]:B[midB1];}
        }
        if (frontcutA){
            return findMedianSortedArrays(A+len, m-len, B, n-len);
        }
        else{
            return findMedianSortedArrays(A, m-len, B+len, n-len);
        }
    }

扭曲的字符串

  • 给定一个字符串,这个字符串可以被分割成一棵二叉树,树的每个叶子节点都是单个字母,比如字符串"great"可以被分为:
        great
       /    \
      gr    eat
     / \    /  \
    g   r  e   at
               / \
              a   t

    对任意的非叶子节点,交换左子树和右子树,能够得到一个新的字符串。该操作允许进行任意次,比如这样一个字符串"rgtae":

        rgtae
       /    \
      rg    tae
     / \    /  \
    r   g  ta  e
           / \
          t   a

     

    就可以先通过交换eat节点(的左右子树),然后交换at节点,最后交换gr节点来生成。
    现在给定字符串s1和s2,判断是否能够通过这种操作来将s1转换为s2。

  • 思路:

    • 首先,如果两个字符串的长度不一样,或者长度一样但是包含的字母不一样(这一点通过排序之后比较实现),那么是没可能转换成功的,这是显见的。

    • 其次,如果两个字符串包含相同的字母,也不一定能够成功,比如"abcd"和"bdac"(不信你可以试试)。

    • 在这种情况下,可以转换成功地条件是:存在这么一个数p<n(字符串的长度),使得(s1的前p位能够转换为s2的前p位,而且s1的后n-p位能够转换为s2的后n-p位)或者(s1的前p位能够转换为s2的后p位,而且s1的后n-p位能够转换为s2的前n-p位):如果存在这么一个p,那么就成功了。

  • 实现:
  • class Solution {
    public:
        bool isScramble(string s1, string s2) {
            if (s1.length()!=s2.length()){
                return false;
            }
            int n = s1.length();
            return isScramble(s1, s2, 0, 0, n);
        }
    
        bool isScramble(const string& s1, const string& s2, int n, int m, int p){
            if (!hasSameLetters(s1, s2, n, m, p)){
                return false;
            }
            if(p==2 || p==1){
                return true;
            }
            for (int i=0; i<p-1; i++){
                if(isScramble(s1, s2, n, m, i+1) && isScramble(s1, s2, n+i+1, m+i+1, p-i-1)){
                    return true;
                }
                if(isScramble(s1, s2, n, m+p-i-1, i+1) && isScramble(s1, s2, n+i+1, m, p-i-1)){
                    return true;
                }
            }
            return false;
        }
    
        bool hasSameLetters(const string& s1, const string& s2, int n, int m, int p){
            vector<char> v1, v2;
            for (int i=0; i<p; i++){
                v1.push_back(s1[n+i]);
                v2.push_back(s2[m+i]);
            }
            sort(v1.begin(), v1.end());
            sort(v2.begin(), v2.end());
            return v1==v2;
        }
    };

分割链表

  • 给出一个单链表,和一个值x,将单链表分为前后两部分,前一部分的所有元素都小于x,后一部分的所有元素都大于等于x,而且前后部分元素的相对位置和原链表一致。比如给出链表1->4->3->2->5->2和值3,返回链表1->2->2->4->3->5。
  • 思路:既然没有要求原地排序,而且还需要元素相对位置和原链表一致,那这道题实在太简单了,直接遍历一遍链表,按照条件复制到两个链表中,再将两个链表接起来就是。
  • 实现:
    class Solution {
    public:
        ListNode *partition(ListNode *head, int x) {
            ListNode* h1 = new ListNode(0);
            ListNode* h2 = new ListNode(0);
            ListNode* t1 = h1, *t2 = h2, *p = head;
            while(p!=NULL){
                if(p->val<x){
                    t1->next = new ListNode(p->val);
                    t1 = t1->next;
                }
                else{
                    t2->next = new ListNode(p->val);
                    t2 = t2->next;
                }
                p=p->next;
            }
            t1->next=h2->next;
            return h1->next;
        }
    };
posted @ 2013-05-09 11:06 一叶斋主人 阅读(...) 评论(...) 编辑 收藏