牛客網劍指offer刷題總結


二維數組中的查找:

  題目描述:在一個二維數組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。

  兩種思路各有優勢:
    1、行枚舉列二分O(nlogm)(列二分行枚舉O(mlogn))
    2、從左下角或右上角移動O(m+n)
    如果n,m一個特別大一個特別小選擇方案1效率很高
    其他情況選擇第2中方案
 
替換空格:
  題目描述:請實現一個函數,將一個字符串中的空格替換成“%20”。例如,當字符串為We Are Happy.則經過替換之后的字符串為We%20Are%20Happy。
  思路:先計算出空格的數量,然后從后到前遍歷移動即可
 
從尾到頭打印鏈表:
  題目描述:輸入一個鏈表,從尾到頭打印鏈表每個節點的值。
  思路:遞歸后序(利用棧的思想)
 
重建二叉樹:
  題目描述:輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重復的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。
  思路:根據前序遍歷可以確定根節點,將中序遍歷分為兩半,遞歸即可
 
用兩個棧實現隊列:
  題目描述:用兩個棧來實現一個隊列,完成隊列的Push和Pop操作。 隊列中的元素為int類型。
  思路:兩個棧st1, st2。push操作向st1中插入。pop操作:如果st2為空則把st1中所有元素放入st2中,否則直接從st2中取元素即可
 
旋轉數組的最小數字:
  題目描述:把一個數組最開始的若干個元素搬到數組的末尾,我們稱之為數組的旋轉。輸入一個非遞減排序的數組的一個旋轉,輸出旋轉數組的最小元素。例如數組{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該數組的最小值為1。NOTE:給出的所有元素都大於0,若數組大小為0,請返回0。
  思路:二分查找
  代碼:
class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        if(rotateArray.empty()) return 0;
        int l = 0, r = rotateArray.size() - 1;
        int stanrd = rotateArray[0];
        int minv = stanrd;
        int mid;
        while(l <= r)
        {
            mid = (l + r) >> 1;
            if(rotateArray[mid] == rotateArray[r])
            {
                --r;//防止有重復元素出現的情況,如 2 2 2 2 2 2 1 2這種情況
                minv = min(minv, rotateArray[mid]);
            }
            else if(rotateArray[mid] > stanrd)
            {
                l = mid + 1;
            }
            else
            {
                r = mid - 1;
                minv = min(minv, rotateArray[mid]);
            }
        }
        return minv;
    }
};

  

 

-------------------------------------------------斐波那契數列專題-------------------------------------------------

解決方案:矩陣快速冪log(n)

代碼:

//代碼寫的很亂,主要看思路
class Solution {
public:
    int tmp[2][2];
    const int a[2][2] =
    {
        1, 1,
        1, 0
    };
    int Fibonacci(int n)
    {
        if (n == 0)
            return 0;
        else if (n == 1)
            return 1;
        else
        {
            Get(n - 1, tmp);
            return tmp[0][0];
        }
    }
    void Get(int n, int tmp[][2])
    {
        if (n == 1)
        {
            for (int i = 0; i < 2; ++i)
                for (int j = 0; j < 2; ++j)
                    tmp[i][j] = a[i][j];
            return;
        }
        Get(n >> 1, tmp);
        int cc[2][2];
        cc[0][0] = tmp[0][0] * tmp[0][0] + tmp[0][1] * tmp[1][0];
        cc[0][1] = tmp[0][0] * tmp[0][1] + tmp[0][1] * tmp[1][1];
        cc[1][0] = tmp[1][0] * tmp[0][0] + tmp[1][1] * tmp[1][0];
        cc[1][1] = tmp[1][0] * tmp[0][1] + tmp[1][1] * tmp[1][1];
        for (int i = 0; i < 2; ++i)
            for (int j = 0; j < 2; ++j)
                tmp[i][j] = cc[i][j];
        if (n & 1)
        {
            cc[0][0] = tmp[0][0] * a[0][0] + tmp[0][1] * a[1][0];
            cc[0][1] = tmp[0][0] * a[0][1] + tmp[0][1] * a[1][1];
            cc[1][0] = tmp[1][0] * a[0][0] + tmp[1][1] * a[1][0];
            cc[1][1] = tmp[1][0] * a[0][1] + tmp[1][1] * a[1][1];
        }
        for (int i = 0; i < 2; ++i)
            for (int j = 0; j < 2; ++j)
                tmp[i][j] = cc[i][j];
    }
};

  

1、斐波那契數列

2、跳台階

  題目描述:一只青蛙一次可以跳上1級台階,也可以跳上2級。求該青蛙跳上一個n級的台階總共有多少種跳法?

  思路1:記憶化搜索

  思路2:問題可以轉化為斐波那契數列,故使用矩陣快速冪可以在log(n)級別的時間復雜度內求解

  如果題目換成每次可以跳1級、2級、3級····n級呢?

  思路1:記憶化搜索

  思路2:推導

    f(n) = f(n - 1) + f(n - 2) + ··· + f(1);

    f(n - 1) = f(n - 2) + f(n - 3) + ··· + f(1);

  ->f(n) = 2 * f(n - 1)

3、矩形覆蓋

  題目描述:我們可以用2*1的小矩形橫着或者豎着去覆蓋更大的矩形。請問用n個2*1的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法?

  推導:

    f(n) = f(n - 1) + f(n - 2);//豎着放和橫着放

----------------------------------------------------------------------------------------------------------

 

二進制中1的個數:

  題目描述:輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼表示。

  思路:n&(n - 1),注意負數的情況即可

 

數值的整數次方:

  題目描述:給定一個double類型的浮點數base和int類型的整數exponent。求base的exponent次方

  思路:快速冪,注意exponent為負數的情況,倒數即可

 

調整數組順序使奇數於偶數前面:

  題目描述:輸入一個整數數組,實現一個函數來調整該數組中數字的順序,使得所有的奇數於數組的前半部分,所有的偶數於位於數組的后半部分,並保證奇數和奇數,偶數和偶數之間的相對位置不變

  思路:1、新開辟一個鏈表記錄偶數,奇數在遍歷的過程中直接移到前面即可,時間復雜度O(n), 空間O(n)

       2、使用類似於插入排序的思想時間復雜度O(n^2),空間復雜度O(1)

 

 鏈表中倒數第k個節點

  題目描述:輸入一個鏈表,輸出該鏈表中倒數第k個結點。

  思路:兩個指針,其中一個先向后移動k次,然后兩個指針同時移動即可

 

反轉鏈表:

  題目描述:輸入一個鏈表,反轉鏈表后,輸出鏈表的所有元素。

  思路:1、三個指針直接反轉(代碼比較復雜)

       2、使用頭插法思想,從前向后遍歷鏈表進行反轉(代碼簡潔)

 

合並兩個排序鏈表:

  題目描述:輸入兩個單調遞增的鏈表,輸出兩個鏈表合成后的鏈表,當然我們需要合成后的鏈表滿足單調不減規則。

  思路:直接合並即可(注意添加頭結點可簡化代碼量的編寫)

 

樹的子結構:

  題目描述:輸入兩棵二叉樹A,B,判斷B是不是A的子結構。(ps:我們約定空樹不是任意一個樹的子結構)

  思路:對A進行DFS,在DFS的過程中如果A的某個節點與B的根節點值相同,那么以B為樹根進行DFS,比較A下面的部分是否包含B即可,時間復雜度O(n * m)

  擴展:如果B是A的子結構,並且滿足B是A中以某個節點為根的子樹(意味着下面這種情況不屬於包含)

      1     1
      /   \     /  \
      2     3   2     3
    /  \
    4    5

  思路:先序遍歷A和B,判斷B的先序序列是否是A的先序序列的子序列即可,顯然KMP算法即可搞定。時間復雜度O(n + m)

   擴展部分代碼:

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        if(pRoot2 == NULL) return false;
        vector<int> root1, root2;
        DFS(pRoot1, root1);
        DFS(pRoot2, root2);
        vector<int> next;
        GetNext(root2, next);
        return KMP(root1, root2, next);
    }
private:
    //取得前序序列
    void DFS(TreeNode * root, vector<int> &ans)
    {
        if(root == NULL) return;
        ans.push_back(root->val);
        DFS(root->left, ans);
        DFS(root->right, ans);
    }
    //求解next數組
    void GetNext(vector<int> &data, vector<int> &next)
    {
        next.push_back(0);
        int len = data.size();
        for(int i = 1, k = 0; i < len; ++i)
        {
            while(k > 0 && data[i] != data[k])
                k = next[k - 1];
            if(data[i] == data[k])
                ++k;
            next.push_back(k);
        }
    }
    bool KMP(vector<int> &parent, vector<int> &child, vector<int> &next)
    {
        int i = 0, j = 0;
        int len1 = parent.size();
        int len2 = child.size();
        
        for(; i < len1; ++i)
        {
            while(j > 0 && parent[i] != child[j])
                j = next[j - 1];
            if(parent[i] == child[j])
                ++j;
            if(j >= len2) return true;
        }
        return false;
    }
};

  

二叉樹的鏡像:

  題目描述:操作給定的二叉樹,將其變換為源二叉樹的鏡像。 

  思路:交換左右孩子指針即可

 

順時針打印矩陣:

  題目描述:輸入一個矩陣,按照從外向里以順時針的順序依次打印出每一個數字,例如,如果輸入如下矩陣: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 則依次打印出數字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

  思路:控制上下左右邊界,每次打印一層外圍,然后所有邊界即可。注意最后剩下一行或一列的特殊情況

  代碼:

class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) 
    {
        vector<int> ans;
        int xl, xr, yl, yr;
        
        if((xr = matrix.size()) == 0) return ans;
        if((yr = matrix[0].size()) == 0) return ans;
        int tol = min((xr + 1) / 2, (yr + 1) / 2);
   
        xl = yl = 0;     
        --xr;
        --yr;
        
        for(int t = 0; t < tol; ++t)
        {
            for(int j = yl; j <= yr; ++j) ans.push_back(matrix[xl][j]);
            for(int i = xl + 1; i <= xr; ++i) ans.push_back(matrix[i][yr]);
            for(int j = yr - 1; j >= yl && xl < xr; --j) ans.push_back(matrix[xr][j]);
            for(int i = xr - 1; i > xl && yl < yr; --i) ans.push_back(matrix[i][yl]);
            ++xl;++yl;
            --xr;--yr;
        }
        return ans;
    }
};

 

包含min函數的棧

  題目描述:定義棧的數據結構,請在該類型中實現一個能夠得到棧最小元素的min函數。

  思路:用兩個棧data、mindata,data保存數據按照正常的棧操作即可,mindata保存的內容:

     1、入棧:如果當前值小於等於最小值那么壓入棧O(1)

     2、出棧:如果data棧頂的值等於mindata的值那么出棧O(1)

          3、最小值:最小值即為mindata棧頂元素O(1)

  代碼:

class Solution {
public:
    void push(int value) 
    {
        if(mindata.empty() || value <= mindata.top())
            mindata.push(value);
        data.push(value);
    }
    void pop() {
        if(data.top() == mindata.top())
            mindata.pop();
        data.pop();
    }
    int top() {
      return data.top();  
    }
    int min() 
    {
       return mindata.top();
    }
private:
   stack<int> data;
   stack<int> mindata;
};

 

棧的壓入、彈出序列

  題目描述:輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否為該棧的彈出順序。假設壓入棧的所有數字均不相等。例如序列1,2,3,4,5是某棧的壓入順序,序列4,5,3,2,1是該壓棧序列對應的一個彈出序列,但4,3,5,1,2就不可能是該壓棧序列的彈出序列。(注意:這兩個序列的長度是相等的)

  思路:用pushV模擬棧,空間復雜度O(1),時間復雜度O(n)

  代碼:

class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) 
    {
    	int len = pushV.size();
        int index = -1, j = 0;//index標示棧頂
        for(int i = 0; i < len + 1; ++i)
        {
        	while(index >= 0 && pushV[index] == popV[j])
            {
                --index;
                ++j;
            }
            if(i < len)
            	pushV[++index] = pushV[i];

        }
        if(j == len) return true;
        return false;
    }
};

  

從上往下打印二叉樹:

  題目描述:從上往下打印出二叉樹的每個節點,同層節點從左至右打印。

  思路:BFS

 

二叉搜索樹的后序遍歷:

  題目描述:輸入一個整數數組,判斷該數組是不是某二叉搜索樹的后序遍歷的結果。如果是則輸出Yes,否則輸出No。假設輸入的數組的任意兩個數字都互不相同。

  思路:二叉搜索樹的后序遍歷特點:

     1、最后一個元素為根節點

     2、除最后一個元素v外,該序列可分為前后兩部分,前面部分都小於v,后面部分都大於v。

     3、遞歸即可,判斷從后向前遍歷,如果一個元素小於v之后還有元素大於v則返回false

 

二叉樹中和為某一值得路徑:

  題目描述:輸入一顆二叉樹和一個整數,打印出二叉樹中結點值的和為輸入整數的所有路徑。路徑定義為從樹的根結點開始往下一直到葉結點所經過的結點形成一條路徑。

  思路:DFS即可

 

復雜鏈表的復制:

  題目描述:輸入一個復雜鏈表(每個節點中有節點值,以及兩個指針,一個指向下一個節點,另一個特殊指針指向任意一個節點),返回結果為復制后復雜鏈表的head。(注意,輸出結果中請不要返回參數中的節點引用,否則判題程序會直接返回空)

  思路:1、hashmap,在原鏈表的節點地址saddress和新鏈表的節點地址naddress直接一一對應做一個映射,然后順序復制即可

     2、可以采用之字鏈表方式替換hashmap,這種方法時間復雜度比較穩定。時間復雜度O(n)。ps:記得還原原來的鏈表

  之字鏈表法代碼:

/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
*/
class Solution 
{
public:
    RandomListNode* Clone(RandomListNode* pHead)
    {
        if(pHead == NULL) return NULL;
        RandomListNode *Root = new RandomListNode(pHead->label);
        RandomListNode *p, *q, *r;
        p = pHead, q = Root;
        while(p != NULL)
        {
       		r = p->next;
            p->next = q;
            q->next = r;
            p = r;
            if(p != NULL)
                q = new RandomListNode(p->label);
        }
        p = pHead;
        q = Root;
        while(p != NULL)
        {
            if(p->random != NULL)
        		q->random = p->random->next;
            else
                q->random = NULL;
         	
            p = q->next;
            if(p != NULL)
            	q = p->next;
        }
        p = pHead;
        q = Root;
        while(q->next != NULL)
        {
        	r = q->next;
            q->next = q->next->next;
            p->next = r;
            p = r;
            q = q->next;
        }
        p->next = NULL;
        return Root;
    }
};

  

二叉搜索樹與雙向鏈表:(和鏈表逆序一樣,不要總想改變指針指向,可以從整體上考慮。鏈表逆序整體:就是把原鏈表中的節點一個一個插入到新鏈表中。本題:就是中序遍歷的過程中修改指針即可)

  題目描述:輸入一棵二叉搜索樹,將該二叉搜索樹轉換成一個排序的雙向鏈表。要求不能創建任何新的結點,只能調整樹中結點指針的指向。

  思路:開兩個變量:root記錄鏈表頭,p記錄鏈表尾節點。中序遍歷二叉樹,p->right指向當前節點,當前節點的left指向p即可

  代碼:

class Solution 
{
public:
    TreeNode *root, *p;
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        root = NULL;
        DFS(pRootOfTree);
        p->right = NULL;
        root->left == NULL;
        return root;
    }
private:
    void DFS(TreeNode *pRootOfTree)
    {
		if(pRootOfTree == NULL) return;
        
        TreeNode *left = pRootOfTree->left, *right = pRootOfTree->right;
       	
        DFS(left);
        if(root == NULL)
        {
            root = pRootOfTree;
            p = root;
        } 
        else
        {
        	p->right = pRootOfTree;
            pRootOfTree->left = p;
            p = p->right;
        }
        DFS(right);
    }
};

 

字符串排列:

  題目描述:輸入一個字符串,按字典序打印出該字符串中字符的所有排列。例如輸入字符串abc,則打印出由字符a,b,c所能排列出來的所有字符串abc,acb,bac,bca,cab和cba。 結果請按字母順序輸出。 

  思路:字典序

class Solution {
public:
    vector<string> Permutation(string str) 
    {
        vector<string> ans;
        if(str.size() == 0) return ans;
    	sort(str.begin(), str.end()); 
        int len = str.size();
        while(true)
        {
         	ans.push_back(str);
            int l, r, i;
            for(i = len - 2; i >= 0; --i)
            {
            	if(str[i] < str[i + 1])
                {
                    l = i;
                    break;
				}
            }
            if(i < 0) break;
            for(int j = l + 1; j < len; ++j)
            {
            	if(str[j] <= str[l]) break;
                else
                    r = j;
            }
            swap(str[l], str[r]);
            for(int i = l + 1, j = len - 1; i < j; ++i, --j)
                swap(str[i], str[j]);
        }
        return ans;
    }
};

 

數組中出現次超過一半的數字:

  題目描述:數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字。例如輸入一個長度為9的數組{1,2,3,2,2,2,5,4,2}。由於數字2在數組中出現了5次,超過數組長度的一半,因此輸出2。如果不存在則輸出0。

  思路:見代碼,O(n)

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
    	int len = numbers.size();
        if(len == 0) return 0;
        int cnt = 1;
        int v = numbers[0];
        for(int j = 1; j < len; ++j)
        {
            if(numbers[j] == v) ++cnt;
            else --cnt;
            if(cnt == 0)
            {
            	v = numbers[j];
                cnt = 1;
            }
        }
        
        cnt = 0;
        for(int i = 0; i < len; ++i)
        {
        	if(numbers[i] == v) ++cnt;
        }
        if(cnt >= (len + 2) / 2) return v;
        return 0;
    }
};

  

最小的k個數:最大堆

 

連續子數組的最大和:O(n)遍歷即可

 

整數中1出現的次數(從1到n整數中1出現的次數):

  題目描述:求出1~13的整數中1出現的次數,並算出100~1300的整數中1出現的次數?為此他特別數了一下1~13中包含1的數字有1、10、11、12、13因此共出現6次,但是對於后面問題他就沒轍了。ACMer希望你們幫幫他,並把問題更加普遍化,可以很快的求出任意非負整數區間中1出現的次數。

  思路:時間復雜度O(log10n)

class Solution {
public:
    /*
    	我們從低位到高位求每位1出現的次數,累加求和即可
        例如:求0~abcde中1的個數
        現在我們求c這一位中1出現的次數,其他位雷同
        有兩部分組成
        第一部分:ab * 1000,表示當ab這兩位在0~ab-1范圍內時,cde可以從0~999取值
    	第二部分:如果c>1時,當ab為ab時1的個數為0~999
        		 如果c=1時,當ab為ab時1的個數為cde + 1
                 如果c<0時,當ab為ab是1的個數為0
    
    */
    int NumberOf1Between1AndN_Solution(int n)
    {
        int exp = 1;
        int ans = 0; 
        while(n / exp)
        {
        	ans += n / (exp * 10) * exp;
            if(n % (exp * 10) / exp  > 1) ans += exp;
            else if(n % (exp * 10) / exp == 1) ans += (n % exp + 1);
            exp *= 10;
        }
        return ans;
    }
};

 

把數組排成最小的數:

  題目描述:輸入一個正整數數組,把數組里所有數字拼接起來排成一個數,打印能拼接出的所有數字中最小的一個。例如輸入數組{3,32,321},則打印出這三個數字能排成的最小數字為321323。

  思路:將整數轉化為字符串,然后對字符數組排序,將排序好的字符數組從前到后串聯即可。

     排序規則:從前往后比較兩個字符串a,b

     如果a[i] < b[i]返回true,a[i] > b[i] 返回 false,如果相等繼續比較

     如果a和b完全相同返回true或者false都行

     如果a比b長,那么a剩余部分的元素從前到后如果大於a[0]返回false, 如果小於a[0]返回true,如果相等則任意返回即可

       如果b比a長,與上述相反即可

  代碼:ps討論中有另一種比較方法如果ab < ba返回true, 如果ab>ba返回false, 如果ab==ba任意返回即可,這種思路很好

bool cmp(const string &a, const string &b)
{  
    int len1 = a.size(), len2 = b.size();
    int i = 0;
    while(i < len1 && i < len2)
    {
        if(a[i] < b[i]) return true;
        else if(a[i] > b[i]) return false;
        ++i;
    }
    if(len1 < len2)
    {
        while(i < len2)
        {
            if(b[i] > b[0]) return true;
            if(b[i] < b[0]) return false;
            ++i;
        }
        return false;
    }
    else if(len1 > len2)
    {
        while(i < len2)
        {
            if(a[i] > a[0]) return false;
            if(a[i] < a[0]) return true;
            ++i;
        }
    }
    return true;
}
 
class Solution
{
public:
    string PrintMinNumber(vector<int> numbers)
    {
        vector<string> num;
        char st[100];
        for(vector<int>::iterator iter = numbers.begin(); iter != numbers.end(); ++iter)
        {
            sprintf(st, "%d", *iter);
            num.push_back(st);
        }
        sort(num.begin(), num.end(), cmp);
        string ans;
        for(vector<string>::iterator iter = num.begin(); iter != num.end(); ++iter)
            ans += *iter;
        return ans;
    }
};

 

丑數:

  題目描述:把只包含因子2、3和5的數稱作丑數(Ugly Number)。例如6、8都是丑數,但14不是,因為它包含因子7。 習慣上我們把1當做是第一個丑數。求按從小到大的順序的第N個丑數。

  思路:由於后面的數肯定是由前邊的數*2、 *3、 *5得到的,那么我們用t2、t3、t5分別記錄輪到第幾個數分別與2、3、5相乘。注意相同的情況不要重復插入

  代碼:

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        if (index<=0) return 0;
        if (index==1) return 1;
        vector<int>k(index);k[0]=1;
        int t2=0,t3=0,t5=0;
        for (int i=1;i<index;i++) {
            k[i]=min(k[t2]*2,min(k[t3]*3,k[t5]*5));
            if (k[i]==k[t2]*2) t2++;
            if (k[i]==k[t3]*3) t3++;
            if (k[i]==k[t5]*5) t5++;
        }
        return k[index-1];
    }
};

 

第一個只出現一次的字符:沒什么可講的

 

數組中的逆序對:

  思路:1、離散化+樹狀數組

     2、歸並排序

  代碼:(離散化+樹狀數組)

class Solution {
public:
    int C[200010];
    void add(int pos, int v, int n)
    {
     	while(pos <= n)
        {
           	C[pos] += v;
        	pos += (pos & (-pos));
        }
    }
    int sum(int pos)
    {
        int ans = 0;
        while(pos > 0)
        {
        	ans += C[pos];
            pos -= (pos & (-pos));
        }
        return ans;
    }
    typedef tuple<int, int> node;
    int InversePairs(vector<int> data) 
    {
       	int len = data.size();
        vector<node> tdata;
        for(int i= 0 ; i < len; ++i)
            tdata.push_back(make_tuple(data[i], i));
        
      	function<bool(node, node)> fun = [](node a, node b)->bool{return get<0>(a) < get<0>(b);};
        sort(tdata.begin(), tdata.end(), fun);
        for(int i = 0; i < len; ++i)
        	get<0>(tdata[i]) = i + 1;    
        function<bool(node, node)> fun1 = [](node a, node b)->bool{return get<1>(a) < get<1>(b);};
        sort(tdata.begin(), tdata.end(), fun1);
        
        memset(C, 0, sizeof(C));
        long long ans = 0;
        for(int i = 0; i < len; ++i)
        {
        	ans += sum(len) - sum(get<0>(tdata[i]));
            ans %= 1000000007;
            add(get<0>(tdata[i]), 1, len);
        }
        return ans;
    }
};

 

兩個鏈表的第一個公共結點:

  題目描述:輸入兩個鏈表p, q,找出它們的第一個公共結點。

  思路:1、如果兩個鏈表有公共節,那么從該公共節點到最后一定是相同的。令p、q中較長的那個鏈表移動abs(len(p) - len(q)),然后兩個鏈表再同時移動即可。時間:O(n),空間O(1)

     2、使用unordered_set記錄p中每個節點的地址,然后遍歷q即可。時間:O(n),空間O(n)

  代碼:

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    //unordered_set
    ListNode* FindFirstCommonNode_unordered_set( ListNode *pHead1, ListNode *pHead2) 
    {
        if(pHead1 == NULL) return NULL;
        unordered_set<ListNode *>st;
       	ListNode *ptr = pHead1;
        while(ptr != NULL)
        {
            st.insert(ptr);
            ptr = ptr->next;
		}
        ptr = pHead2;
        while(ptr != NULL)
        {
        	if(st.find(ptr) != st.end()) break;
            ptr =  ptr->next;
        }
        return ptr;
    }
    
    ListNode* FindFirstCommonNode( ListNode *pHead1, ListNode *pHead2) 
    {
        if(pHead1 == NULL) return NULL;
        ListNode *ptr = pHead1;
     	int len1 = 0;
        while(ptr != NULL)
	    {
            ++len1;
            ptr = ptr->next;
		}
        ptr = pHead2;
        int len2 = 0;
        while(ptr != NULL)
        {
        	++len2;
            ptr = ptr->next;
        }
        ListNode *ptr1;
        if(len1 >= len2)
        {
         	ptr = pHead1;
            int len = len1 - len2;
            while(len--)
                ptr = ptr->next;
       		ptr1 = pHead2;
            while(ptr != NULL)
            {
            	if(ptr == ptr1) break;
                ptr = ptr->next;
                ptr1 = ptr1->next;
            }
        }
        else
        {
            ptr = pHead2;
            int len = len2 - len1;
            while(len--)
                ptr = ptr->next;
       		ptr1 = pHead1;
            while(ptr != NULL)
            {
            	if(ptr == ptr1) break;;
                ptr = ptr->next;
                ptr1 = ptr1->next;
            }
        }
        return ptr;
    }
};

 

數字在排序數組中出現的次數:

  題目描述:統計一個數字在排序數組中出現的次數。

  思路:二分查找第一個出現的位置和最后一個出現的位置

 

二叉樹的深度:

  題目描述:輸入一棵二叉樹,求該樹的深度。從根結點到葉結點依次經過的結點(含根、葉結點)形成樹的一條路徑,最長路徑的長度為樹的深度。

  思路:DFS遍歷即可

 

 平衡二叉樹:

  題目描述:輸入一棵二叉樹,判斷該二叉樹是否是平衡二叉樹。

  思路:DFS,判斷左右子樹深度相差是否大於1即可

 

數組中只出現一次的數字:

  題目描述:一個整型數組里除了兩個數字之外,其他的數字都出現了兩次。請寫程序找出這兩個只出現一次的數字。

  思路:1、unordered_map統計一下即可

       2、根據異或的性質

       先遍歷一遍數組,求出所有數的異或;然后任意找出該值一個為1的位,其他位置零,假設為x

       第二次遍歷,如果data & x ==  0則與num1異或,反之與num2異或

  代碼:

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) 
    {
		int t = 0;
        for(auto x : data) t ^= x;
        t = t&(-t);
        *num1 = *num2 = 0;
        for(auto x : data)
        {
        	if((x & t) != 0) *num1 ^= x;
            else *num2 ^= x;
        }
    }
};

 

 和為S的連續正數序列:

  題目描述:小明很喜歡數學,有一天他在做數學作業時,要求計算出9~16的和,他馬上就寫出了正確答案是100。但是他並不滿足於此,他在想究竟有多少種連續的正數序列的和為100(至少包括兩個數)。沒多久,他就得到另一組連續正數和為100的序列:18,19,20,21,22。現在把問題交給你,你能不能也很快的找出所有和為S的連續正數序列? Good Luck! 

  思路:1、尺取法,設定左右邊界l,r如果(l + r)*(r - l + 1) / 2 == sum滿足條件則成功;否則如果>則l++, <則r++。時間復雜度O(n)

     2、數學方法:設定x為開始值,m為個數,求解開是否有滿足條件的結即可。時間復雜度O(n)

  代碼:

class Solution {
public:
    /*
    //數學方法
    vector<vector<int> > FindContinuousSequence1(int sum) 
    {
        vector<vector<int>> ans;
        for(int i = 1; i <= sum / 2; ++i)
        {
        	int n = (2 * i - 1) * (2 * i - 1) + 8 * sum;
            int kn = sqrt(n) + 0.1;
            if(kn * kn == n)
            {
            	if((-2 * i - 1 +  kn) % 2 == 0 && (-2 * i - 1 +  kn) / 2 > 0)
                {
                    int m = (-2 * i - 1 + kn) / 2;
                	vector<int> tans;
                    for(int j = i; j <= i + m; ++j)
                        tans.push_back(j);
                    ans.push_back(tans);
                }
            }
        }
        return ans;
    }
    */
    //標尺法
    vector<vector<int> > FindContinuousSequence(int sum) 
    {
        vector<vector<int>> ans;
        int l = 1, r = 2;
        while(l < r && l <= sum / 2)
        {
            int s = (l + r) * (r - l + 1);
            if((s & 1) == 0 && (s >> 1) == sum)
            {
                vector<int> tans;
                for(int i = l; i <= r; ++i)
                	tans.push_back(i);
                ans.push_back(tans);
                r++;
            }
            else
            {
                if((s >> 1) < sum) r++;
                else
                {
                    l++;
                    if(l == r) r++;
                }
            }
        }
        return ans;
    }
};

  

和為S的兩個數字:

  題目描述:輸入一個遞增排序的數組和一個數字S,在數組中查找兩個數,使得他們的和正好是S,如果有多對數字的和等於S,輸出兩個數的乘積最小的。 

  思路:從兩側往中間移動,如果相等直接跳出循環,如果 < 則i++ , > 則j--

  代碼:

    class Solution {
    public:
         //	1、向中間逼近
         vector<int> FindNumbersWithSum(vector<int> array,int sum) 
        {
            vector<int> ans;
           
            int len = array.size();
            int i = 0, j = len - 1;
            while(i < j)
            {
                if(array[i] + array[j] == sum) break;
                else if(array[i] + array[j] > sum) --j;
                else ++i;
            }
            if(i < j)
            {
            	ans.push_back(array[i]);
                ans.push_back(array[j]);
            }
            return ans;
        }

    };

 

旋轉字符串:

  題目描述:匯編語言中有一種移位指令叫做循環左移(ROL),現在有個簡單的任務,就是用字符串模擬這個指令的運算結果。對於一個給定的字符序列S,請你把其循環左移K位后的序列輸出。例如,字符序列S=”abcXYZdef”,要求輸出循環左移3位后的結果,即“XYZdefabc”。是不是很簡單?OK,搞定它!

  思路:字符串YX = (XTYT)TX,故可以根據移動的位數將字符串分為X、Y兩部分,然后先分別反轉。然后整體反轉

  代碼:

class Solution {
public:
    string LeftRotateString(string str, int n) 
    {
    	int len = str.size();
        if(len == 0) return str;
        n %= len;
        for(int i = 0, j = n - 1; i < j; ++i, --j) swap(str[i], str[j]);
        for(int i = n, j = len - 1; i < j; ++i, --j) swap(str[i], str[j]);
        for(int i = 0, j = len - 1; i < j; ++i, --j) swap(str[i], str[j]);
        return str;
    }
};

  

翻轉單詞順序列:

  題目描述:牛客最近來了一個新員工Fish,每天早晨總是會拿着一本英文雜志,寫些句子在本子上。同事Cat對Fish寫的內容頗感興趣,有一天他向Fish借來翻看,但卻讀不懂它的意思。例如,“student. a am I”。后來才意識到,這家伙原來把句子單詞的順序翻轉了,正確的句子應該是“I am a student.”。Cat對一一的翻轉這些單詞順序可不在行,你能幫助他么?

  思路:1、先反轉整個字符串

       2、從前到后遍歷字符串,遇到空格則反轉上次空格之后到這次空格之前的字符串。時間復雜度O(n)

 

撲克牌順子:

  題目描述:LL今天心情特別好,因為他去買了一副撲克牌,發現里面居然有2個大王,2個小王(一副牌原本是54張^_^)...他隨機從中抽出了5張牌,想測測自己的手氣,看看能不能抽到順子,如果抽到的話,他決定去買體育彩票,嘿嘿!!“紅心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是順子.....LL不高興了,他想了想,決定大\小 王可以看成任何數字,並且A看作1,J為11,Q為12,K為13。上面的5張牌就可以變成“1,2,3,4,5”(大小王分別看作2和4),“So Lucky!”。LL決定去買體育彩票啦。 現在,要求你使用這幅牌模擬上面的過程,然后告訴我們LL的運氣如何。為了方便起見,你可以認為大小王是0。

  思路:排序,然后判斷是否有非零重復的,以及maxv-minv是否大於n - 1即可

 

孩子們的游戲(圓圈中最后剩下的數:

  題目描述:每年六一兒童節,牛客都會准備一些小禮物去看望孤兒院的小朋友,今年亦是如此。HF作為牛客的資深元老,自然也准備了一些小游戲。其中,有個游戲是這樣的:首先,讓小朋友們圍成一個大圈。然后,他隨機指定一個數m,讓編號為0的小朋友開始報數。每次喊到m-1的那個小朋友要出列唱首歌,然后可以在禮品箱中任意的挑選禮物,並且不再回到圈中,從他的下一個小朋友開始,繼續0...m-1報數....這樣下去....直到剩下最后一個小朋友,可以不用表演,並且拿到牛客名貴的“名偵探柯南”典藏版(名額有限哦!!^_^)。請你試着想下,哪個小朋友會得到這份禮品呢?(注:小朋友的編號是從0到n-1)

  思路:1、暴力模擬,時間復雜度O(n * m)。這個就不敘述了

     2、遞推,時間復雜度O(n)

       1、長度為n的序列:   0, 1, 2,···, m - 2, m - 1, m, ··· , n - 1   從總刪除第一個之后變成

        長度為n-1的序列:m, m + 1, m + 2, ···, n - 2, n - 1, 0, 1, ···, m - 2

        對應值:      0, 1, 2, ···

        假設我們現在已經求得n-1的序列刪除m時的最后刪除的位置是:f[n - 1],根據上述映射f[n - 1] + m就是其在序列n中的位置

        所以f[n] = (f[n - 1] + m) % n(其中f[1] = 0)

     代碼:

class Solution {
public:
    int LastRemaining_Solution(unsigned int n, unsigned int m)
    {
        int x = 0;
        while(--n)
            x = (x + m) % n;   
        return x;
    }
};

         

求1+2+3+...+n:

  題目描述:求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字及條件判斷語句(A?B:C)。

  思路:循環可以用遞歸替代,條件可以用&&、||等最短路替代。直接遞歸即可O(n)

 

不用加減乘除做加法:使用位運算法

 

把字符串轉成整數:

  題目描述:將一個字符串轉換成一個整數,要求不能使用字符串轉換整數的庫函數。

  思路:從前向后ans = ans * 10 + str[i] - '0'; 注意有正負號情況;非法字符返回0即可

 

數組中重復的數字:

  題目描述:在一個長度為n的數組里的所有數字都在0到n-1的范圍內。 數組中某些數字是重復的,但不知道有幾個數字是重復的。也不知道每個數字重復幾次。請找出數組中任意一個重復的數字。 例如,如果輸入長度為7的數組{2,3,1,0,2,5,3},那么對應的輸出是重復的數字2或者3。

  思路:1、由於數據在0~n - 1的范圍,因此我們可以吧number[i]當做下標把對應的數值+length,如果發現某個值大於等於length則該值是重復的。O(n)

     2、hashset

       3、排序

   思路1代碼:

class Solution {
public:
    // Parameters:
    //        numbers:     an array of integers
    //        length:      the length of array numbers
    //        duplication: (Output) the duplicated number in the array number
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    bool duplicate(int numbers[], int length, int* duplication) 
    {
    	for(int i = 0; i < length; ++i)
        {
            int index = numbers[i];
            if(index >= length) index -= length;
            if(numbers[index] >= length) 
            {
            	*duplication = index;
                return true;
            }
            numbers[index] += length;
		}
        return false;
    }
};

  

  

構建乘積數組:

  題目描述:給定一個數組A[0,1,...,n-1],請構建一個數組B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。

  思路:構造一個前綴積數組,和一個后綴積數組。則ans[i] = pre[i - 1] * aft[i + 1]

 

正則表達式匹配:

  題目描述:請實現一個函數用來匹配包括'.'和'*'的正則表達式。模式中的字符'.'表示任意一個字符,而'*'表示它前面的字符可以出現任意次(包含0次)。 在本題中,匹配是指字符串的所有字符匹配整個模式。例如,字符串"aaa"與模式"a.a"和"ab*ac*a"匹配,但是與"aa.a"和"ab*a"均不匹配

  思路:有限自動機,DFS搜索即可

 

表示數值的字符串:

  題目描述:請實現一個函數用來判斷字符串是否表示數值(包括整數和小數)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示數值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。

  思路:這是上一題的簡單版本,模式串固定看字符串是否匹配。同樣有限自動機,DFS搜索即可

 

 字符流中第一個不重復的字符:

  題目:請實現一個函數用來找出字符流中第一個只出現一次的字符。例如,當從字符流中只讀出前兩個字符"go"時,第一個只出現一次的字符是"g"。當從該字符流中讀出前六個字符“google"時,第一個只出現一次的字符是"l"。 

  思路:時間復雜度O(1),空間復雜度O(n)

          1、用一個128大小的數組統計每個字符出現的次數
          2、用一個隊列,如果第一次遇到ch字符,則插入隊列;其他情況不在插入
          3、求解第一個出現的字符,判斷隊首元素是否只出現一次,如果是直接返回,否則刪除繼續第3步驟
  代碼:
  
class Solution
{
public:
  //Insert one char from stringstream
    void Insert(char ch)
    {	 
        ++cnt[ch - '\0'];
        if(cnt[ch - '\0'] == 1)
         	data.push(ch);
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce()
    {
    	while(!data.empty() && cnt[data.front()] >= 2) data.pop();
        if(data.empty()) return '#';
        return data.front();
    }
    Solution()
    {
    	memset(cnt, 0, sizeof(cnt));    
    }
private:
	queue<char> data;
    unsigned cnt[128];
};

  

 

 鏈表中環的入口結點:

  思路:1、首先求得環的長度,通過兩個指針一個每次走一步,另一個每次走兩步。兩次相等之間的步數即為環的大小len

     2、於是問題轉變為求鏈表的倒數第k個節點的問題

     ps(有acmer提出可以通過兩個指針p1, p2;p1為p2的前一個,每次通過置p1->next=null也可以達到效果,因為只有環的第一個節點有兩個前驅)

 

刪除鏈表中重復的結點:

  題目描述:在一個排序的鏈表中,存在重復的結點,請刪除該鏈表中重復的結點,重復的結點不保留,返回鏈表頭指針。 例如,鏈表1->2->3->3->4->4->5 處理后為 1->2->5

  思路:按照題目要求模擬即可。對於鏈表其實很多時候加一個頭結點對處理有很大的幫助

 

二叉樹的下一個結點
  題目描述:給定一個二叉樹和其中的一個結點,請找出中序遍歷順序的下一個結點並且返回。注意,樹中的結點不僅包含左右子結點,同時包含指向父結點的指針。
  思路:分情況討論
     1、若當前節點為NULL,返回NULL
     2、如果父節點為NULL
       2.1、如果當前節點右子樹不存在,則返回NULL
       2.2、如果當前節點右子樹存在,則找出右子樹中最左邊的節點
     3、如果當前節點為父節點的左孩子
       3.1、如果當前節點右子樹不存在,則返回當前節點父節點
       3.2、如果當前節點右子樹存在,則找出右子樹中最左邊的節點
     4、如果當前節點為父節點的右孩子
       4.1、如果當前節點右子樹不存在,則向上尋找直到一個節點為其父節點的左孩子
       4.2、如果當前節點右子樹存在,則找出右子樹中最左邊的節點
 

對稱的二叉樹:

  題目描述:請實現一個函數,用來判斷一顆二叉樹是不是對稱的。注意,如果一個二叉樹同此二叉樹的鏡像是同樣的,定義其為對稱的。

  思路:判斷前序DFS與后序DFS對應節點的val是否相同就行

class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot)
    {
      return DFS(pRoot, pRoot);
    }
    bool DFS(TreeNode *p, TreeNode *q)
    {
        if(p == NULL && q != NULL) return false;
        if(p != NULL && q == NULL) return false;
        if(p == NULL && q == NULL) return true;
        if(p->val != q->val) return false;
        if(!DFS(p->left, q->right)) return false;
        if(!DFS(p->right, q->left)) return false;
        return true;
    }
};

  

按之字形順序打印二叉樹:

  題目描述:請實現一個函數按照之字形打印二叉樹,即第一行按照從左到右的順序打印,第二層按照從右至左的順序打印,第三行按照從左到右的順序打印,其他行以此類推。

  思路:使用兩個棧,交替打印即可。注意左右字數的添加順序即可。當然使用一個雙向鏈表,通過插入分隔符NULL也是可以完成的

 

 把二叉樹打印多行:

  題目描述:從上到下按層打印二叉樹,同一層結點從左至右輸出。每一層輸出一行。

  思路:1、在每次結束后向隊列中加入一個NULL,每次碰到NULL換行即可

       2、使用兩個隊列交叉工作即可

     3、用一個變量記錄有多少個節點是上一層的即可,上一層打印完后用q.size()更新變量即可

 

序列化二叉樹:

  題目描述:請實現兩個函數,分別用來序列化和反序列化二叉樹。這里沒有規定序列化的方式。

  思路:DFS遍歷,用?代表NULL,#代表節點之間的分隔符。注意(正數、負數、0)的序列化方式。對於整數與字符串之間的轉化可以自己手動實現,當然也可以使用sprintf或sstream進行序列哈

 

二叉搜索樹的第k個結點:

  題目描述:給定一顆二叉搜索樹,請找出其中的第k大的結點。例如, 5 / \ 3 7 /\ /\ 2 4 6 8 中,按結點數值大小順序第三個結點的值為4。

  思路:DFS中序遍歷即可。如果多次查詢可以記錄每個子樹的大小,這樣每次查詢效率就會大大提高

 

數據流中的中位數:

  題目描述:如何得到一個數據流中的中位數?如果從數據流中讀出奇數個數值,那么中位數就是所有數值排序之后位於中間的數值。如果從數據流中讀出偶數個數值,那么中位數就是所有數值排序之后中間兩個數的平均值。

  思路:維護一個大頂堆和一個小頂堆。如果大頂堆中的數量小於等於小頂堆中的數量,則插入大頂堆,反之插入小頂堆。如果大頂堆的堆頂數據大於小頂堆的堆頂數據則交換堆頂數據。

       每次中位數即為大頂堆堆頂或大頂堆堆頂+小頂堆堆頂的平均值

 

滑動窗口最大值:

  題目描述:給定一個數組和滑動窗口的大小,找出所有滑動窗口里數值的最大值。例如,如果輸入數組{2,3,4,2,6,2,5,1}及滑動窗口的大小3,那么一共存在6個滑動窗口,他們的最大值分別為{4,4,6,6,6,5}; 針對數組{2,3,4,2,6,2,5,1}的滑動窗口有以下6個: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

  思路:單調隊列。向后移動1位時,如果隊列后面的元素小於將要添加的元素的則刪除隊列中的元素直到大於或隊列為空。然后判斷隊首元素的下標是否已經超過了滑動窗口的范圍,超過就刪除。

  代碼:

class Solution {
public:
    vector<int> maxInWindows(const vector<int>& num, unsigned int size)
    {
        vector<int> ans;
        int len = num.size();if
        (size > len) return ans;
        
        for(int i = 0; i < len; ++i)
        {
        	while(!array.empty() && num[array.back()] < num[i]) array.pop_back();
            array.push_back(i);
            if(i >= size - 1)
            {
                if(array.front() < i - (size - 1)) array.pop_front();
            	ans.push_back(num[array.front()]);
            }
        }
        return ans;
    }
private:
    list<int> array;
};

 

 

矩陣中的路徑:

  題目描述:請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。路徑可以從矩陣中的任意一個格子開始,每一步可以在矩陣中向左,向右,向上,向下移動一個格子。如果一條路徑經過了矩陣中的某一個格子,則該路徑不能再進入該格子。 例如 a b c e s f c s a d e e 矩陣中包含一條字符串"bcced"的路徑,但是矩陣中不包含"abcb"路徑,因為字符串的第一個字符b占據了矩陣中的第一行第二個格子之后,路徑不能再次進入該格子。

  思路:回溯法,好暴力哦。。。

 

機器人的運動范圍

  題目描述:地上有一個m行和n列的方格。一個機器人從坐標0,0的格子開始移動,每一次只能向左,右,上,下四個方向移動一格,但是不能進入行坐標和列坐標的數位之和大於k的格子。 例如,當k為18時,機器人能夠進入方格(35,37),因為3+5+3+7 = 18。但是,它不能進入方格(35,38),因為3+5+3+8 = 19。請問該機器人能夠達到多少個格子?

  思路:回溯法,注意k可能<0。本題主要考察了回溯法,此外我們要注意數據的取值范圍,這點要和面試官多交流

 

 

 

機器人的運動范圍:

歷時兩周,利用空閑時間終於刷完了劍指offer。下一步leetcode。

come on!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM