《劍指OFFER》刷題筆記

《劍指OFFER》刷題筆記

未完待更新

1.二維數組中的查找

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

  • 暴力查找 複雜度O(nm)

  • 對每行二分查找O(nlog(m))

  • 二維數組中的一個數,小於它的數一定在其左邊,大於它的數一定在其下邊。因此,從右上角開始查找,就可以根據 target 和當前元素的大小關係來縮小查找區間,當前元素的查找區間爲左下角的所有元素。複雜度O(n + m)

#include <bits/stdc++.h>
class Solution {
public:
    bool Find(int target, vector<vector<int> > arr) {
        int n = arr.size(), m = 0;
        if(n) m = arr[0].size();
        int x = n - 1, y = 0;
        while(x >= 0 && y < m) {
            if(arr[x][y] == target) 
                return true;
            if(arr[x][y] < target)
                ++y;
            else if(arr[x][y] > target)
                --x;
        }
        return false;
    }
};

2.替換空格

請實現一個函數,將一個字符串中的每個空格替換成“%20”。例如,當字符串爲We Are Happy.則經過替換之後的字符串爲We%20Are%20Happy。

  • 計算空格個數後從尾向前雙指針遍歷,空格替換爲%20即可

class Solution {
public:
    void replaceSpace(char *str,int length) {
        int cnt = 0;
        for(int i = 0; i < length; ++i) {
            if(str[i] == ' ')
                ++cnt;
        }
        int p = length + (cnt * 2);
        str[p] = 0;
        --p;
        
        --length;
        while(length >= 0) {
            if(str[length] != ' ') {
                str[p--] = str[length];
            }
            else {
                str[p--] = '0';
                str[p--] = '2';
                str[p--] = '%';
            }
            --length;
        }
    }
};

3.從尾到頭打印鏈表

輸入一個鏈表,按鏈表從尾到頭的順序返回一個ArrayList。

  • 遞歸/棧遍歷返回

/**
*  struct ListNode {
*        int val;
*        struct ListNode *next;
*        ListNode(int x) :
*              val(x), next(NULL) {
*        }
*  };
*/
class Solution {
public:
    
    void gao(vector<int> &ans, ListNode *now)
    {
        if(now == NULL)
                return ;
        gao(ans, now->next);
        ans.push_back(now->val);
    }
    vector<int> printListFromTailToHead(ListNode* head) {
        vector <int> ans;
        
        gao(ans, head);
        
        return ans;
    }
};

4.重建二叉樹

輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。

  • 前序中左右,中序左中右,針對前序確定當前結點並對中序進行分割遞歸重建即可

/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
​
​
class Solution {
public:
    void gao(deque <int> pre, deque <int> vin, TreeNode *now) {
        if(vin.size() == 0) {
            now = NULL;
            return ;
        }
        
        deque <int> fst, vfst;
        
        now->val = pre[0];
        
        pre.pop_front();
        
        while(vin.front() != now->val) {
            fst.push_back(pre.front());
            vfst.push_back(vin.front());
            pre.pop_front();
            vin.pop_front();
        }
        
        vin.pop_front();
        
    
        if(fst.size())
        {
            now->left = new TreeNode(0);
            gao(fst, vfst, now->left);
        }
        if(pre.size())
        {
            now->right = new TreeNode(0);
            gao(pre, vin, now->right);
        }
        
        
    }
    
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        TreeNode *head = NULL;
        
        if(pre.size())
        {
            head = new TreeNode(0);
            gao({pre.begin(), pre.end()},{vin.begin(), vin.end()}, head);
        }
        return head;
    }
};

5.用兩個棧實現隊列

用兩個棧來實現一個隊列,完成隊列的Push和Pop操作。 隊列中的元素爲int類型。

  • in棧記錄入隊順序,out棧記錄出隊順序,當out爲空時把in棧push人out棧中即可保證順序。

class Solution
{
public:
    void push(int node) {
       in.push(node);
    }
​
    int pop() {
        if(out.empty())
        {
            while(!in.empty())
            {
                out.push(in.top());
                in.pop();
            }
        }
        int ret = out.top();
        out.pop();
        return ret;
    }
​
private:
    stack<int> in;
    stack<int> out;
};

6.旋轉數組的最小數字

把一個數組最開始的若干個元素搬到數組的末尾,我們稱之爲數組的旋轉。 輸入一個非遞減排序的數組的一個旋轉,輸出旋轉數組的最小元素。 例如數組{3,4,5,1,2}爲{1,2,3,4,5}的一個旋轉,該數組的最小值爲1。 NOTE:給出的所有元素都大於0,若數組大小爲0,請返回0。

  • 對窗口進行二分查找,縮進查找於窗口首值小於窗口尾值(此時旋轉點位於當前窗口內)。當首值等於尾值時,無法確定,只能暴力查找

class Solution {
public:
    int minNumberInRotateArray(vector<int> arr) {
        if(!arr.size())
            return 0;
        int fst = 0, lst = arr.size();
        
        int mid;
        
        while(fst != lst)
        {
            mid = (fst + lst) / 2;
            if(arr[mid] > arr[fst])
            {
                fst = mid;
            }
            else if(arr[mid] < arr[fst])
            {
                lst = mid;
            }
            else
            {
                int x = 0x3f3f3f3f;
                for(int i = fst; i <= lst; ++i)
                    x = min(x, arr[i]);
                return x;
            }
        }
        
        return arr[fst];
    }
};

7.斐波那契數列

大家都知道斐波那契數列,現在要求輸入一個整數n,請你輸出斐波那契數列的第n項(從0開始,第0項爲0)。

n<=39

  • 數組記錄遞推 dp[i] = dp[i - 1] + dp[i - 2]

class Solution {
public:
    
    typedef long long ll;
    
    ll fib[50] = {0, 1, 1};
    int Fibonacci(int n) {
        
        for(int i = 2; i <= n; ++i) {
            fib[i] = fib[i - 1] + fib[i - 2];
        }
        return fib[n];
    }
};

8. 跳臺階

一隻青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法(先後次序不同算不同的結果)。

  • 計數dp,來源n-1與n-2

​
const int MAXN = 1e5 + 7;
class Solution {
public:
    typedef long long ll;
    ll dp[MAXN] = {1};
    int jumpFloor(int number) {
        for(int i = 1; i <= number; ++i) {
            if(i >= 1) 
                dp[i] += dp[i - 1];
            if(i >= 2)
                dp[i] += dp[i - 2];
        }
        return dp[number];
    }
};

9.變態跳臺階

一隻青蛙一次可以跳上1級臺階,也可以跳上2級……它也可以跳上n級。求該青蛙跳上一個n級的臺階總共有多少種跳法。

  • 計數dp 來源1- (i - 1)

class Solution {
public:
    int dp[10010] = {0}, sum[10010] = {0};
    int jumpFloorII(int number) {
        for(int i = 1; i <= number; ++i)
        {
            dp[i] = sum[i - 1] + 1;
            sum[i] = sum[i - 1] + dp[i];
        }
        return dp[number];
    }
};

10.矩形覆蓋

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

  • 計數dp int dp[MAXN] = {0, 1, 2, 3 }; dp[i] = dp[i - 1] + dp[i - 2];

const int MAXN = 1e5 + 7;
class Solution {
public:
    
    int dp[MAXN] = {0, 1, 2, 3 };
    
    
    int rectCover(int number) {
        for(int i = 4; i <= number; ++i)
        {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[number];
    }
};

11.二進制中1的個數

輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼錶示。

  • 減lowbit次數

class Solution {
public:
     int  NumberOf1(int n) {
         int ret = 0;
         
         while(n)
         {
             n -= (n & -n);
             ++ret;
         }
         return ret;
     }
};

12.數值的整數次方

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

保證base和exponent不同時爲0

  • 快速冪 負冪要取反變爲1.0/ret

class Solution {
public:
    typedef long double ld;
    double Power(double base, int exponent) {
        ld ret = 1.0;
        int f = 0;
        if(exponent < 0) f = 1, exponent *= -1;
        while(exponent) {
            if(exponent & 1) 
                ret *= base;
            base *= base;
            exponent >>= 1;
        }
        return f ? 1.0 / ret : ret;
    }
};

13.調整數組順序使奇數位於偶數前面

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

  • 隊列記錄並還原 時間O(n) 空間O(n)

  • 冒泡上浮 時間O(n^2) 空間O(1)

class Solution {
public:
    void reOrderArray(vector<int> &arr) {
        queue <int> qa, qb;
        
        for(auto x : arr)
        {
            if(x & 1)
                qa.push(x);
            else
                qb.push(x);
        }
        
        int p = 0;
        
        while(!qa.empty())
        {
            arr[p++] = qa.front();
            qa.pop();
        }
        
        while(!qb.empty())
        {
            arr[p++] = qb.front();
            qb.pop();
        }
        
        return ;
    }
};

14 鏈表中倒數第k個結點

輸入一個鏈表,輸出該鏈表中倒數第k個結點。

  • 遞歸查詢標記

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
            val(x), next(NULL) {
    }
};*/
class Solution {
public:
    
    int cnt = 0;
    
    ListNode *ret = NULL;
    
    void get(ListNode *now, int k)
    {
        if(now == NULL || cnt >= k)
            return ;
        get(now->next, k);
        
        ++cnt;
        
        if(cnt == k)
        {
            ret = now;
            return ;
        }
    }
    
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        get(pListHead, k);
        
        return ret;
    }
};

15 反轉鏈表

輸入一個鏈表,反轉鏈表後,輸出新鏈表的表頭。

  • 循環頭插

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
            val(x), next(NULL) {
    }
};*/
class Solution {
public:
    
    ListNode* ReverseList(ListNode* pHead) {
        ListNode *ret = NULL;
        ListNode *tmp = NULL;
        while(pHead != NULL)
        {
            tmp = pHead->next;
            pHead->next = ret;
            ret = pHead;
            pHead = tmp;
        }
        return ret;
    }
};

16 合併兩個排序的鏈表

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

  • 歸併,兩個合併到一個結束,再將另一個接在後面

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
            val(x), next(NULL) {
    }
};*/
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        ListNode *head = new ListNode(-1);
        ListNode *now = head;
        ListNode *tmp;
        
        while(pHead1 != NULL && pHead2 != NULL)
        {
            if(pHead1->val <= pHead2->val)
            {
                tmp = pHead1->next;
                pHead1->next = now->next;
                now->next = pHead1;
                now = now->next;
                pHead1 = tmp;
            }
            else
            {
                tmp = pHead2->next;
                pHead2->next = now->next;
                now->next = pHead2;
                now = now->next;
                pHead2 = tmp;
            }
        }
        
        
        while(pHead1 != NULL)
        {
            tmp = pHead1->next;
            now->next = pHead1;
            now = now->next;
            now->next = NULL;
            pHead1 = tmp;
        }
        
        while(pHead2 != NULL)
        {
            tmp = pHead2->next;
            now->next = pHead2;
            now = now->next;
            now->next = NULL;
            pHead2 = tmp;
        }
        
        
        return head->next;
    }
};

樹的子結構

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

  • 暴力匹配 注意空樹的特別處理

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
​
typedef unsigned long long ull;
​
class Solution {
public:
    bool ok = 0, rok = 1;
    void check(TreeNode * now, TreeNode * prt2)
    {
        if(now == NULL || ok)
             return ;
        
        rok = 1;
        rck(now, prt2);
        if(rok)
            ok = 1;
        
        check(now->left, prt2);
        check(now->right, prt2);
    }
    void rck(TreeNode *now, TreeNode *prt2)
    {
        if(!rok || prt2 == NULL)
            return ;
        
        if( (now == NULL && prt2 != NULL) || (now->val != prt2 -> val))
        {
            rok = 0;
            return ;
        }
​
        rck(now -> left, prt2->left);
        rck(now -> right, prt2->right);
    }
    
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        if(pRoot2 == NULL)
            return 0;
        check(pRoot1, pRoot2);
        return ok;
    }
};

二叉樹的鏡像

題目描述

操作給定的二叉樹,將其變換爲源二叉樹的鏡像。

輸入描述:

二叉樹的鏡像定義:源二叉樹 
        8
       /  \
      6   10
     / \  / \
    5  7 9 11
    鏡像二叉樹
        8
       /  \
      10   6
     / \  / \
    11 9 7  5
  • 對所有節點進行遞歸反轉

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if(pRoot== NULL)
            return ;
        swap(pRoot->left, pRoot->right);
        Mirror(pRoot->left);
        Mirror(pRoot->right);
    }
};

順時針打印矩陣

輸入一個矩陣,按照從外向裏以順時針的順序依次打印出每一個數字,例如,如果輸入如下4 X 4矩陣: 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 
{
    private:
        int n, m;
    public;
    bool inmap(int x, int y) { return x >= 0 && x < n && y >= 0 && y < m; }
    vector<int> printMatrix(vector<vector<int> > arr) 
    {
        n = arr.size(), m = n ? arr[0].size() : 0;
        vector < vector <bool> > used(n, vector <bool> (m, 0));
        vector <int> ans;
        int x = 0, y = -1, cnt = 0;
        while(cnt < n * m)
        {
            ++y;
            while(cnt < n * m)
            {
                ans.push_back(arr[x][y]), ++cnt;
                used[x][y] = 1;
                if(!inmap(x, y + 1) || used[x][y + 1])
                    break;
                ++y;
            }
            ++x;
            while(cnt < n * m)
            {
                ans.push_back(arr[x][y]), ++cnt;
                used[x][y] = 1;
                if(!inmap(x + 1, y) || used[x + 1][y])
                    break;
                ++x;
            }
            --y;
            while(cnt < n * m)
            {
                ans.push_back(arr[x][y]), ++cnt;
                used[x][y] = 1;
                if(!inmap(x, y - 1) || used[x][y - 1])
                    break;
                --y;
            }
                   --x;
            while(cnt < n * m)
            {
                ans.push_back(arr[x][y]), ++cnt;
                used[x][y] = 1;
                if(!inmap(x - 1, y) || used[x - 1][y])
                    break;
                --x;
            }
        }
        return ans;
    }
};
public ArrayList<Integer> printMatrix(int[][] matrix) {
    ArrayList<Integer> ret = new ArrayList<>();
    int r1 = 0, r2 = matrix.length - 1, c1 = 0, c2 = matrix[0].length - 1;
    while (r1 <= r2 && c1 <= c2) {
        for (int i = c1; i <= c2; i++)
            ret.add(matrix[r1][i]);
        for (int i = r1 + 1; i <= r2; i++)
            ret.add(matrix[i][c2]);
        if (r1 != r2)
            for (int i = c2 - 1; i >= c1; i--)
                ret.add(matrix[r2][i]);
        if (c1 != c2)
            for (int i = r2 - 1; i > r1; i--)
                ret.add(matrix[i][c1]);
        r1++; r2--; c1++; c2--;
    }
    return ret;
}

包含min函數的棧

定義棧的數據結構,請在該類型中實現一個能夠得到棧中所含最小元素的min函數(時間複雜度應爲O(1))。

  • 一個記錄val一個記錄mx

const int MAXN = 1e6 + 7;
class Solution {
public:
    
    struct node 
    {
        int val, mx;
    }stk[MAXN];
    
    int tp;
    
    Solution() 
    {
        tp = -1;
    }
    
    void push(int value) {
        stk[++tp].val = value;
        stk[tp].mx = (tp == 0 ? stk[tp].val : std::min(stk[tp].val, stk[tp - 1].val) );
    }
    void pop() {
        --tp;
    }
    int top() {
        return stk[tp].val;
    }
    int min() {
        return stk[tp].mx;
    }
};

棧的壓入、彈出序列

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

  • 根據入棧順序模擬 全符合爲真

class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        stack <int> STK;
        int p = 0;
        for(auto x : pushV)
        {
            STK.push(x);
            while(p < popV.size() && STK.size() && popV[p] == STK.top())
            {
                ++p;
                STK.pop();
            }
        }
        
        if(p == pushV.size() && STK.size() == 0)
            return true;
        return false;
    }
};

從上往下打印二叉樹

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

  • BFS

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        
        vector <int> ret;
        queue <TreeNode*> Q;
        
        Q.push(root);
        
        while(!Q.empty())
        {
            auto x = Q.front();
            Q.pop();
            if(x != NULL)
            {
                ret.push_back(x->val);
                Q.push(x->left);
                Q.push(x->right);
            }
        }
        
        
        return ret;
        
    }
};

二叉搜索樹的後序遍歷序列

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

  • 後序遍歷特點:左右中,當前節點最後遍歷,因此將最後節點拿出後看剩下序列能不能拆爲兩部分使得前部分小於當前節點,後部分大於當前結點即可(遞歸處理)

class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        if(sequence.size() == 0)
            return false;
        return check(sequence, 0, sequence.size() - 1);
    }
    
    bool check(vector <int> arr, int fst, int lst)
    {
        if(fst >= lst)
            return true;
        
        int x = arr[lst];
        
        int rf = fst;
        
        while(arr[rf] <= x && rf < lst)
            ++rf;
        
        for(int i = rf; i < lst; ++i)
            if(arr[i] <= x)
                return false;
        
        return check(arr, fst, rf - 1) && check(arr, rf, lst - 1);
    }
};

二叉樹中和爲某一值的路徑

輸入一顆二叉樹的跟節點和一個整數,打印出二叉樹中結點值的和爲輸入整數的所有路徑。路徑定義爲從樹的根結點開始往下一直到葉結點所經過的結點形成一條路徑。(注意: 在返回值的list中,數組長度大的數組靠前)

  • DFS同時記錄路徑,對於形成路徑長度爲某值的記錄

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
const int MAXN = 1e5 + 7;
class Solution {
public:
    vector < vector<int> > ans;
    
    int road[MAXN] = {0};
    
    int rsum;
    
    void dfs(TreeNode *now, int step, int sum)
    {
        if(now == NULL)
            return ;
        
        road[step] = now->val;
        sum += now->val;
        
        if(now->left == NULL && now->right == NULL && sum == rsum)
        {
            vector <int> x;
            for(int i = 0; i <= step; ++i)
                   x.push_back(road[i]);
            ans.push_back(x);
            return ;
        }
        
        dfs(now->left, step + 1, sum);
        dfs(now->right, step + 1, sum);
    }
    
    bool cmp(vector <int> a, vector <int> b)
    {
        return a.size() >= b.size();
    }
    
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        rsum = expectNumber;
        dfs(root, 0, 0);
        return ans;
    }
};

複雜鏈表的複製

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

  • 待補充

 

二叉搜索樹與雙向鏈表

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

  • DFS,記錄頭尾指針邊遍歷邊更新

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
    TreeNode *ret = NULL, *nxt = ret;
    
    void dfs(TreeNode *now)
    { 
        if(now == NULL)
            return ;
        
        dfs(now->left);
        
        if(ret == NULL)
        {
            ret = now;
            nxt = ret;
        }
        else
        {
            now->left = nxt;
            nxt->right = now;
            nxt = nxt->right;
        }
        
        dfs(now->right);
    }
    
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        dfs(pRootOfTree);
        
        return ret;
    }
};

字符串的排列

題目描述

輸入一個字符串,按字典序打印出該字符串中字符的所有排列。例如輸入字符串abc,則打印出由字符a,b,c所能排列出來的所有字符串abc,acb,bac,bca,cab和cba。

輸入描述:

輸入一個字符串,長度不超過9(可能有字符重複),字符只包括大小寫字母。
  • DFS標記回溯搜索 注意要判斷重複(SET維護)

class Solution {
public:
    
    bool used[20] = {0};
    
    set <string> ans;
    
    string rs, tmp;
    
    void gao(int step)
    {
        if(step == rs.length())
        {
            ans.insert(tmp);
            return ;
        }
        
        for(int i = 0; i < rs.length(); ++i)
        {
            if(!used[i])
            {
                tmp[step] = rs[i];
                used[i] = true;
                
                gao(step + 1);
                
                used[i] = false;
                
            }
        }
    }
    
    vector<string> Permutation(string str) {
        rs = str;
        tmp = str;
        if(str.length())
            gao(0);
        //sort(ans.begin(), ans.end());
        return vector <string> {ans.begin(), ans.end()};
    }
};

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

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

  • HASH記錄次數 空間O(n) 時間O(n)

  • 維護結果投票,當前值等於結果投一票,反之結果減一票,票數爲零時更新答案爲當前值。對答案驗證 空間 O(1) 時間O(n)

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> arr) {
        int ret = 0, num = 0;
        for(auto x : arr)
        {
            if(num == 0)
            {
                ++num;
                ret = x;
            }
            else
            {
                if(x == ret)
                    ++num;
                else
                    --num;
            }
        }
        num = 0;
        for(auto x : arr)
            if(x == ret)
                ++num;
        if(num * 2 <= arr.size())
            ret = 0;
        return ret;
    }
};

最小的K個數

輸入n個整數,找出其中最小的K個數。例如輸入4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4,。

  • Heap 維護

class Solution 
{
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) 
    {
        if(k > input.size())
            return vector <int> {};
        multiset <int> ST;
        for(auto x : input)
        {
            ST.insert(x);
            while(ST.size() > k)
            {
                auto x = --ST.end();
                ST.erase(x);
            }
        }
        vector <int> ret{ST.begin(), ST.end()};
        return ret;
    }
};

連續子數組的最大和

HZ偶爾會拿些專業問題來忽悠那些非計算機專業的同學。今天測試組開完會後,他又發話了:在古老的一維模式識別中,常常需要計算連續子向量的最大和,當向量全爲正數的時候,問題很好解決。但是,如果向量中包含負數,是否應該包含某個負數,並期望旁邊的正數會彌補它呢?例如:{6,-3,-2,7,-15,1,2,2},連續子向量的最大和爲8(從第0個開始,到第3個爲止)。給一個數組,返回它的最大連續子序列的和,你會不會被他忽悠住?(子向量的長度至少是1)

  • 做前綴和,維護之前前綴和的最小值相減更新即可 空間O1 時間On

  • 動態規劃 dp[i] = max(arr[i] + dp[i - 1], arr[i]); 空間O1 時間On

const int INF = 0x3f3f3f3f;
class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> arr) {
        int rx = 0;
        int ret = -INF;
        for(int i = 0; i < arr.size(); ++i)
        {
            if(i)
                arr[i] += arr[i - 1];
            ret = std::max(ret, arr[i] - rx);
            rx = min(rx, arr[i]);
        }
        return ret;
    }
};
const int INF = 0x3f3f3f3f;
class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> arr) {
        int rx = 0;
        if(!arr.size())
            return 0;
        int ret = arr[0];
        int *dp = new int[10010];
        dp[0] = arr[0];
        for(int i = 1; i < arr.size(); ++i)
        {
            dp[i] = max(dp[i - 1] + arr[i], arr[i]);
            ret = max(ret, dp[i]);
        }
        return ret;
    }
};

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

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

  • 待寫

把數組排成最小的數

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

  • 冒泡兩兩比較 return a + b < b + a

class Solution {
public:
    
    string ret;
    
    void get(int x)
    {
        if(x > 10)
            get(x / 10);
        ret = ret + char('0' + x % 10);
    }
    
    static bool cmp(string a, string b)
    {
        return a + b < b + a;
    }
    string PrintMinNumber(vector<int> numbers) {
        
        vector <string> rs;
        
        for(auto x : numbers)
        {
            ret = "";
            get(x);
            rs.push_back(ret);
        }
        
        sort(rs.begin(), rs.end(), cmp);
        
        ret = "";
        for(auto x : rs)
        {
            ret = ret + x;
        }
        return ret;
    }
};

醜數

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

  • 生成法,優先隊列維護 On*logn

  • 生成發,比較維護 On

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        int i2 = 0, i3 = 0, i5 = 0;
        vector <int> dp(index + 1, 0);
        dp[0] = 1;
        for(int i = 1; i < index; ++i)
        {
            int nxt2 = dp[i2] * 2, nxt3 = dp[i3] * 3, nxt5 = dp[i5] * 5;
            dp[i] = min(nxt2, min(nxt3, nxt5));
            if(dp[i] == nxt2)
                ++i2;
            if(dp[i] == nxt5)
                ++i5;
            if(dp[i] == nxt3)
                ++i3;
        }
        return dp[index - 1];
    }
};

第一個只出現一次的字符

在一個字符串(0<=字符串長度<=10000,全部由字母組成)中找到第一個只出現一次的字符,並返回它的位置, 如果沒有則返回 -1(需要區分大小寫).

  • 維護次數 位置判斷

  • 隊列維護

class Solution {
public:
    
    vector <int> pos[256];
    
    int FirstNotRepeatingChar(string str) {
        for(int i = 0; i < str.size(); ++i)
        {
            pos[str[i]].push_back(i);
        }
        set <int> ans;
        for(int i = 0; i < 256; ++i)
        {
            if(pos[i].size() == 1)
                ans.insert(pos[i][0]);
        }
        if(ans.size())
            return *ans.begin();
        else
            return -1;
    }
};

數組中的逆序對

在數組中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個數組中的逆序對的總數P。並將P對1000000007取模的結果輸出。 即輸出P%1000000007

  • 歸併求逆序對

  • 樹狀數組求逆序對

const int MAXN = 2e5 + 100;
typedef long long ll;
class Solution {
public:
    struct bit
    {
        ll c[MAXN], n;
        bit(ll rn) 
        {
            memset(c, 0, sizeof(c));
            n = rn + 10;
        }
        ll lowbit(ll x)
        {
            return x & -x;
        }
        void updata(ll pos)
        {
            for( ;pos <= n; pos += lowbit(pos))
            {
                c[pos] += 1;
            }
        }
        ll ask(ll pos)
        {
            ll ret = 0;
            for(;pos; pos -= lowbit(pos))
                ret += c[pos];
            return ret;
        }
    };
    
    vector <int> lsh;
    
    int get(ll val) { return lower_bound(lsh.begin(), lsh.end(), val) - lsh.begin() + 1;}
    
    int InversePairs(vector <int> data) {
        bit T(data.size());
        lsh = data;
        sort(lsh.begin(), lsh.end());
        lsh.erase(unique(lsh.begin(), lsh.end()), lsh.end());
        ll ans = 0;
        for(int i = data.size() - 1; i >= 0; --i)
        {
            ll t = get(data[i]);
            ans += T.ask(t - 1);
            T.updata(t);
        }
        
        return (ans % 1000000007);
    }
};

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

輸入兩個鏈表,找出它們的第一個公共結點。

  • 分別記錄兩個鏈表的長度,讓長的鏈表先後跑直到兩鏈表長度相同,然後同時向後遍歷,第一個相遇即爲答案

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
            val(x), next(NULL) {
    }
};*/
class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        int x = 0, y = 0;
        
        ListNode *tmp = pHead1;
        
        while(tmp != NULL)
        {
            ++x;
            tmp = tmp->next;
        }
        
        tmp = pHead2;
        
        while(tmp != NULL)
        {
            ++y;
            tmp = tmp->next;
        }
        
        
        while(x > y)
        {
            --x;
            pHead1 = pHead1->next;
        }
        
        while(x < y)
        {
            --y;
            pHead2 = pHead2->next;
        }
        
        while(true)
        {
            if(pHead1 == pHead2)
            {
                return pHead1;
            }
            pHead1 = pHead1->next;
            pHead2 = pHead2->next;
        }
    }
};

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

統計一個數字在排序數組中出現的次數。

  • 二分查找

class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        return upper_bound(data.begin(), data.end(), k) - lower_bound(data.begin(), data.end(), k);
    }
};

二叉樹的深度

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

  • 遞歸計算 出口爲空時返回0

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
    int TreeDepth(TreeNode* pRoot)
    {
        if(pRoot == NULL)
            return 0;
        return max(TreeDepth(pRoot->left) + 1, TreeDepth(pRoot->right) + 1);
    }
};

平衡二叉樹

輸入一棵二叉樹,判斷該二叉樹是否是平衡二叉樹。

  • 平衡二叉樹定義 任意節點左右子樹高度值相差不超過一

class Solution {
public:
    bool ok = 1;
    int dfs(TreeNode *now)
    {
        if(now == NULL || !ok)
            return 0;
        int x = dfs(now->left) + 1, y = dfs(now->right) + 1;
        if(abs(x - y) > 1)
            ok = 0;
        return max(x, y);
    }
    bool IsBalanced_Solution(TreeNode* pRoot) {
        dfs(pRoot);
        return ok;
    }
};

數組中只出現一次的數字

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

  • 先異或一遍,結果爲x ^ y再對最低位不同的分組,再單獨異或分出x y

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int *num1,int *num2) {
        int rx = 0;
        for(auto x : data)
            rx ^= x;
        vector <int> ra, rb;
        int f = 0;
        while(!(rx & 1))
        {
            rx >>= 1;
            ++f;
        }
        for(auto x : data)
        {
            if((x >> f) & 1)
                ra.push_back(x);
            else
                rb.push_back(x);
        }
        *num1 = 0;
        *num2 = 0;
        for(auto x : ra)
            *num1 ^= x;
        for(auto x : rb)
            *num2 ^= x;
    }
};

和爲S的連續正數序列

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

  • 暴力 On^2

  • 枚舉前端點+二分 Onlogn

  • 雙指針 On

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        int fst = 1, lst = 1, rsum = 1;
        vector <vector<int>> ans;
        while(lst <= sum)
        {
            if(rsum < sum)
            {
                ++lst;
                rsum += lst;
            }
            else if(rsum > sum)
            {
                rsum -= fst;
                ++fst;
            }
            else
            {
                vector <int> rs;
                for(int i = fst; i <= lst; ++i)
                    rs.push_back(i);
                if(rs.size() > 1)
                    ans.push_back(rs);
                ++lst, rsum += lst; 
            }
        }c
        return ans;
    }
};

和爲S的兩個數字

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

  • 排序數組 首位雙指針逼近更新答案

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> arr,int sum) {
        int fst = 0, lst = arr.size() - 1;
        int x = 10086, y = 10086;
        while(fst < lst)
        {
            if(arr[fst] + arr[lst] == sum && arr[fst] * arr[lst] < x * y)
                x = arr[fst], y = arr[lst];
            else if(arr[fst] + arr[lst] > sum)
                --lst;
            else
                ++fst;
        }
        if(x == 10086 && y == 10086)
            return vector <int> {};
        return vector <int> {x, y};
    }
};

左旋轉字符串

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

  • 旋轉0-n-1 n-str.length 0-str.length即可

class Solution {
public:
    void rvse(string &x, int fst, int lst)
    {
        while(fst <= lst)
            swap(x[fst++], x[lst--]);
    }
    string LeftRotateString(string str, int n) {
        if(str.length())
        {
            n %= str.length();
            rvse(str, 0, n - 1);
            rvse(str, n, str.length() - 1);
            rvse(str, 0, str.length() - 1);
        }
        return str;
    }
};

翻轉單詞順序列

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

  • 針對每個單詞先翻轉,再對整個序列反轉 同上

class Solution {
public:
    void rvs(string &x, int fst, int lst)
    {
        while(fst < lst)
            swap(x[fst++], x[lst--]);
    }
    
    string ReverseSentence(string str) {
        int la = 0;
        for(int i = 0; i < str.length(); ++i)
        {
            if((i + 1 < str.length() && str[i + 1] == ' ') || i == str.length() - 1)
            {
                rvs(str, la, i);
                la = i + 2;
            }
        }
        rvs(str, 0, str.length() - 1);
        return str;
    }
};
發佈了292 篇原創文章 · 獲贊 30 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章