劍指offer題目合集及解法

最近做了劍指offer的題目,很多題目的最優解很有意思,很多時候想不到,寫篇博客專門來記錄一下。

 

二維數組中的查找

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

利用排序的特性

class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        if(array.empty()) return 0;
        int n=array.size(),m=array[0].size();
        int p=0,q=m-1;
        while(p<n&&q>=0)    //利用排序特性不斷的改變座標
        {
            if(target==array[p][q]) return 1;
            else if(target<array[p][q]) q--;
            else p++;
        }
        return 0;
    }
};

替換空格

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

看評論的做法是從後往前掃描空格,這樣的話可以預先處理出變換後的字符串的長度,然後就是O(n)的變化了,我自己用來新的string類= =,這樣空間就是O(n)了。

class Solution {
public:
	void replaceSpace(char *str,int length) {   //很弱智的做法
        string s;
        for(int i=0;i<length;i++)
        {
            if(str[i]==' ') s+="%20";
            else s+=str[i];
        }
        int ls=s.length();
        for(int i=0;i<ls;i++) str[i]=s[i];
        str[ls]='\0';
	}
};
class Solution {
public:
	void replaceSpace(char *str,int length) {   //從後往前掃描
        int cnt=0;
        for(int i=0;i<length;i++)
        {
            if(str[i]==' ')
                cnt++;
        }
        int ls=length+cnt*2;
        for(int i=length-1;i>=0;i--)
        {
            if(str[i]!=' ') str[i+cnt*2]=str[i];    //計算出當前字符處理後的位置
            else    //遇到空格的處理
            {
                cnt--;
                str[i+2*cnt]='%',str[i+1+2*cnt]='2',str[i+2+2*cnt]='0';
            }
        }
        str[ls]='\0';
	}
};

從尾到頭打印鏈表

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

這個題目,我按照鏈表頭到尾順序輸出到vector中最後reverse了一下,當然也可以使用遞歸輸出。

/**
*  struct ListNode {
*        int val;
*        struct ListNode *next;
*        ListNode(int x) :
*              val(x), next(NULL) {
*        }
*  };
*/
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> v;
        ListNode *p=head;
        while(p)    //順序輸出
        {
            v.push_back(p->val);
            p=p->next;
        }
        reverse(v.begin(),v.end()); //反轉vector
        return v;
    }
};
class Solution {
public:
    vector<int> res;
    void cal(ListNode* root)    //遞歸版
    {
        if(root==nullptr) return ;
        cal(root->next);
        res.push_back(root->val);
    }
    vector<int> printListFromTailToHead(ListNode* head) {
        res.clear();
        if(head==nullptr) return res;
        cal(head);
        return res;
    }
};

重建二叉樹

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

根據前序中序得到建二叉樹,數據結構實驗做過很多,就是不斷遞歸就好。

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
 
class Solution {
public:
    TreeNode* findroot(vector<int> pre,int pl,int pr,vector<int> vin,int vl,int vr)
    {
        if(pl>pr||vl>vr) return nullptr;
        TreeNode *root=new TreeNode(pre[pl]);
        int pos=0;
        for(int i=vl;i<=vr;i++)
        {
            if(vin[i]==pre[pl]) //找到這個節點分界線
            {
                pos=i;
                break;
            }
        }
        root->left=findroot(pre,pl+1,pl+pos-vl,vin,vl,pos-1);
        root->right=findroot(pre,pl+pos-vl+1,pr,vin,pos+1,pr);
        return root;
    }
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        if(pre.size()==0||vin.size()==0||pre.size()!=vin.size()) return nullptr;
        TreeNode *root=findroot(pre,0,pre.size()-1,vin,0,vin.size()-1); //確定下一次遞歸的範圍
        return root;
    }
};

用兩個棧實現隊列

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

假設兩個棧s1 s2 。push的時候只加入s1,當需要pop的時候,將s1的數pop出加入s2直到s1只剩一個數,彈出這個數,再將s2的數一個個加回s1,感覺複雜度蠻高的,也沒啥好方法。。

class Solution
{
public:
    void push(int node) {
        stack1.push(node);
    }

    int pop() {
        while(stack1.size()>1)
        {
            stack2.push(stack1.top());
            stack1.pop();
        }
        int tmp=stack1.top();
        stack1.pop();
        while(!stack2.empty())
        {
            stack1.push(stack2.top());
            stack2.pop();
        }
        return tmp;
    }

private:
    stack<int> stack1;
    stack<int> stack2;
};

旋轉數組的最小數字

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

這個題搞了一會兒,主要就是這個二分搞得我不是很清楚,跟一般的二分不太一樣。

假設 l r mid,最大數和最小數之間稱爲分界線。

因爲是旋轉數組,所以第一個元素大於最後一個元素,接下來就是中間元素的問題,如果 v[mid]<v[r],說明mid在分界線右邊,這個時候調整r=mid,如果v[mid]>v[r],說明在分界線左邊,這個時候調整l=mid+1,至於爲什麼這個時候是l=mid+1,我的理解是因爲兩個數的時候r=mid-1會出錯,但是l=mid+1仍然是正確答案,反正這個二分我也還有點迷= =

class Solution {
public:
    int minNumberInRotateArray(vector<int> v) {
        int l=0,r=v.size()-1;
        if(r==0) return 0;
        while(l<r)
        {
            int mid=(l+r)>>1;
            if(v[mid]>v[r]) l=mid+1;
            else if(v[mid]==v[r]) r--;
            else r=mid;
        }
        return v[l];
    }
};

斐波那契數列  f(n)=f(n-1)+f(n-2)

跳臺階

一隻青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法(先後次序不同算不同的結果)(仔細推一下也是斐波那契入門題 應該第n階臺階可以從 n-1階 和 n-2階得到)

矩形覆蓋

我們可以用2*1的小矩形橫着或者豎着去覆蓋更大的矩形。請問用n個2*1的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法?(其實也是斐波那契 想一下 對於 2*n的矩陣 用 2*1去填它  如果緊貼左邊貼一個2*1 那麼還剩下2*(n-1)的矩陣要貼,如果貼了1*2,剩下的就是2*(n-2)的矩陣要貼)所以都是斐波那契。

class Solution {
public:
    int F(int number) {
        int x=1,y=2,z;
        if(number==0) return 0;
        if(number==1) return 1;
        if(number==2) return 2;
        for(int i=3;i<=number;i++)
        {
            z=x+y;
            x=y;
            y=z;
        }
        return z;
    }
};

變態跳臺階

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

這個利用上面的性質很容易推 f(n)=f(n-1)+f(n-2)+f(n-3)+f(n-4)+......+f(1),簡單推理得f(n)=2*f(n-1)。

class Solution {
public:
    int jumpFloorII(int number) {
        int x=1;
        for(int i=2;i<=number;i++) x=x*2;
        return x;
    }
};

二進制中1的個數

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

這個做完纔想起來 樹狀數組裏的lowbit 也就是 x&(-x),利用lowbit可以在很短時間得出答案

class Solution {
public:
     int  NumberOf1(int n) {
         int ans=0;
         while(n!=0)
         {
             ans++;
             n-=(n&(-n));   //n&(-n)就是二進制1的最後一位
         }
         return ans;
     }
};

數值的整數次方

給定一個double類型的浮點數base和int類型的整數exponent。求base的exponent次方。(簡單快速冪 注意指數可能爲負)

class Solution {
public:
    double Power(double base, int exponent) {
        double ans=1;
        int p=abs(exponent);
        while(p)
        {
            if(p&1) ans*=base;
            base*=base;
            p=p/2;
        }
        return exponent<0 ? 1/ans:ans;
    }
};

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

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

這個題目如果不新建空間感覺還是挺麻煩的,所以就新建空間好一點。不然也沒什麼特別好的方法,其實可以只建一個的,在原有的裏面不斷刪除數即可,我爲了方便建了2個vector。


class Solution {
public:
    void reOrderArray(vector<int> &array) {
        vector<int> v1,v2;
        for(auto it=array.begin();it!=array.end();it++)
        {
            if((*it)&1) v1.push_back(*it);
            else v2.push_back(*it);
        }
        array.clear();
        for(auto it=v1.begin();it!=v1.end();it++) array.push_back(*it);
        for(auto it=v2.begin();it!=v2.end();it++) array.push_back(*it);
    }
};

鏈表中倒數第k個結點

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

這個剛開始我做法是先跑一遍看一共多少個節點,然後判斷倒數第k個是第幾個,最後再跑一遍,其實可以設置兩個指針,一個跑k個節點以後,第二個指針開始跑,這樣第一個指針跑完第二個指針所指的即爲答案。

struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        int cnt=0;
        ListNode *p1=pListHead,*p2=pListHead;
        while(pListHead)
        {
            if(cnt>=k) p1=p1->next;
            cnt++;
            pListHead=pListHead->next;
            p2=p2->next;
        }
        if(cnt<k) p1=nullptr;
        return p1;
    }
};

反轉鏈表

輸入一個鏈表,反轉鏈表後,輸出新鏈表的表頭。(學過數據結構的應都會啊)

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        ListNode *p=NULL;
        while(pHead!=NULL)
        {
            ListNode *tmp=pHead->next;
            pHead->next=p;
            p=pHead;
            pHead=tmp;
        }
        return p;
    }
};

合併兩個排序的鏈表

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

class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        ListNode *tmp=NULL,*p=NULL;
        if(pHead1==NULL) return pHead2;
        if(pHead2==NULL) return pHead1;
        while(pHead1&&pHead2)   //兩表都不爲空
        {
            if(pHead1->val<=pHead2->val)    //選擇較小的加入新鏈表
            {
                if(tmp==NULL) tmp=p=pHead1;
                else p->next=pHead1,p=p->next;
                pHead1=pHead1->next;
            }
            else
            {
                if(tmp==NULL) tmp=p=pHead2;
                else p->next=pHead2,p=p->next;
                pHead2=pHead2->next;
            }
        }
        if(pHead1) p->next=pHead1;  //最後將剩下鏈表所有數加入
        if(pHead2) p->next=pHead2;
        return tmp;
    }
};

樹的子結構

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

本來感覺挺麻煩的,後來仔細想想就是對於每個節點遞歸處理判斷,同時用到了大一學到的一個知識點 A||B 如果第一個滿足則不會執行B語句的判斷。利用這一點這個題代碼會簡單很多

class Solution {
public:
    bool check(TreeNode *r1,TreeNode *r2)
    {
        if(r2==nullptr) return 1;//這個函數如果r2爲空就代表判斷結束 這個節點符合要求
        if(r1==nullptr) return 0;//r1爲空 r2不爲空 則一定不符合要求
        if(r1->val==r2->val)    //只有當前節點相同往下判斷
            return check(r1->left,r2->left)&&check(r1->right,r2->right);
        return 0;
    }
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        if(pRoot1==nullptr||pRoot2==nullptr) return 0;//其實這裏應該有點小問題 B上來爲空應該是符合要求的感覺
        return check(pRoot1,pRoot2)||HasSubtree(pRoot1->left,pRoot2)||check(pRoot1->right,pRoot2);  //檢查當前節點 不符合則檢查兒子節點
    }
};

二叉樹的鏡像

 

操作給定的二叉樹,將其變換爲源二叉樹的鏡像。(直接在當前層將左右兒子swap即可)

class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if(pRoot==NULL) return ;
        swap(pRoot->left,pRoot->right); //直接交換即可
        if(pRoot->left) Mirror(pRoot->left);
        if(pRoot->right) 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.(這個題想到了大一困擾我許久的回形矩陣不過那個題目n*n 這個是n*m的)

class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
        vector<int> res;
        int n=matrix.size();
        if(n==0) return res;
        int m=matrix[0].size();
        int cr=(min(n,m)-1)/2+1;    //計算旋轉層數
        for(int i=0;i<cr;i++)
        {
            for(int j=i;j<m-i;j++) res.push_back(matrix[i][j]);
            for(int j=i+1;j<n-i;j++) res.push_back(matrix[j][m-i-1]);//其實中間這一堆條件是因爲某些情況出錯 
            for(int j=m-i-2;(i!=m-i-1)&&(i!=n-i-1)&&j>=i;j--) res.push_back(matrix[n-i-1][j]);  //因爲n m值不同 避免重複計算某些數
            for(int j=n-i-2;(i!=m-i-1)&&j>i;j--) res.push_back(matrix[j][i]);
        }
        return res;
    }
};

包含min函數的棧

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

棧s1正常存數,棧s2壓數的時候只有當前要壓入的數<=s2棧頂元素的時候才壓入,彈出時如果s1.top()==s2.top(),則一起彈出。

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

棧的壓入、彈出序列

輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否可能爲該棧的彈出順序。假設壓入棧的所有數字均不相等。例如序列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> s;
        auto gk=popV.begin();
        for(auto it=pushV.begin();it!=pushV.end();it++)
        {
            s.push(*it);
            while(!s.empty()&&s.top()==*gk) s.pop(),gk++;
        }
        if(gk==popV.end()) return 1;
        return 0;
    }
};

從上往下打印二叉樹

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

class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        queue<TreeNode*> que;
        vector<int> res;
        if(root==NULL) return res;
        que.push(root);
        while(!que.empty())
        {
            TreeNode *tmp=que.front();
            que.pop();
            res.push_back(tmp->val);
            if(tmp->left!=NULL) que.push(tmp->left);
            if(tmp->right!=NULL) que.push(tmp->right);
        }
        return res;
    }
};

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

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

剛開始看錯題因爲是二叉樹的後序遍歷,心想這怎麼搞,後來發現是二叉搜索樹,後序遍歷的順序是左 右 根,那麼在二叉搜索樹裏左小於根 右大於跟  利用這個性質遞歸判斷看是否符合即可

class Solution {
public:
    bool check(vector<int> v,int l,int r)
    {
        if(l>r) return 1;
        if(l==r) return 1;
        int root=v[r],pos=-1;
        for(int i=l;i<r;i++)//找分界點
        {
            if(v[i]>root)
            {
                pos=i;
                break;
            }
        }
        if(pos==-1) return check(v,l,r-1);  //找不到的情況
        for(int i=pos;i<r;i++)  //判斷是否合法
            if(v[i]<root) return 0;
        return check(v,l,pos-1)&&(v,pos,r-1);
    }
    bool VerifySquenceOfBST(vector<int> sequence) {
        if(sequence.size()==0) return 0;
        return check(sequence,0,sequence.size()-1);//遞歸判斷
    }
};

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

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

這個題目剛開始也看錯了,以爲路徑就是任意兩點間的路徑,感覺真的挺複雜的。後來發現路徑的定義是根到葉子節點,服了。直接dfs就好

class Solution {
public:
    vector<vector<int> > res;
    vector<int> v;
    void dfs(TreeNode *root,int x)
    {
        if(root==nullptr) return ;
        v.push_back(root->val);
        if(x-root->val==0&&!root->left&&!root->right) res.push_back(v);//葉子節點並且符合要求
        dfs(root->left,x-root->val);
        dfs(root->right,x-root->val);
        v.erase(v.end()-1);
    }
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        if(root) dfs(root,expectNumber);
        return res;
    }
};

複雜鏈表的複製

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

這個稍微有點複雜,就是首先在每個節點後面增加一個他自己的複製節點。

例如原鏈表A->B->C->D->E 轉換後爲 A->A'->B->B'->C->C'->D->D'->E->E',這樣之後將原先隨機指的節點同樣複製過去,例如原先有A->D 則將A'->D' 方法也很簡單 A'->random=A->random->next即可。最後在斷開恢復正常鏈表 返回A‘

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==nullptr) return nullptr;
        RandomListNode *p=pHead;
        while(p)
        {
            RandomListNode *tmp=new RandomListNode(p->label);   //複製節點
            tmp->next=p->next;
            p->next=tmp;
            p=p->next->next;
        }
        p=pHead;
        while(p)    //修正random的位置
        {
            if(p->random) p->next->random=p->random->next;
            p=p->next->next;
        }
        RandomListNode *pc=pHead->next,*tmp;
        p=pHead;
        while(p->next)  //依次斷開
        {
            tmp=p->next;
            p->next=tmp->next;
            p=tmp;
        }
        return pc;
    }
};

二叉搜索樹與雙向鏈表

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

這個題沒想出來,最後看了評論裏的一個大佬的做法,真的很厲害也很簡潔。

首先也知道的就是二叉搜索樹的排序狀態就是中序遍歷,那麼中序遍歷的結果就是答案,那麼問題就是邊的問題了,具體看代碼,很難講清楚。

class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        if(pRootOfTree==nullptr) return nullptr;
        TreeNode *tmp=nullptr;
        create(pRootOfTree,tmp);//開始遍歷
        while(pRootOfTree->left) pRootOfTree=pRootOfTree->left;//頭節點是最小的節點
        return pRootOfTree;
    }
    void create(TreeNode *root,TreeNode* &tmp)//注意tmp引用
    {
        if(root==nullptr) return ;
        create(root->left,tmp);
        root->left=tmp;
        if(tmp) tmp->right=root;
        tmp=root;
        create(root->right,tmp);
    }
};

字符串的排列

輸入一個字符串,按字典序打印出該字符串中字符的所有排列。例如輸入字符串abc,則打印出由字符a,b,c所能排列出來的所有字符串abc,acb,bac,bca,cab和cba。(感覺就是dfs找所有情況,沒什麼太難的東西,寫一種邪道方法)

class Solution {
public:
    vector<string> Permutation(string str) {
        vector<string> res;
        if(str=="") return res;
        do
        {
            res.push_back(str);
        }while(next_permutation(str.begin(),str.end()));
        return res;
    }
};

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

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

這個我自己沒想出最優解,最優解蠻神奇的,感覺直到是對的但是自己有點難想,看代碼應該就明白了。

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        int cnt=numbers.size(),res=numbers[0],co=1;
        if(cnt==0) return 0;
        for(auto it=numbers.begin()+1;it!=numbers.end();it++)
        {
            if(*it==res) co++;  //如果和前一個數相同 co++
            else co--;  //不相同--
            if(co==0) res=*it,co=1; //co爲0則重新賦值
        }
        co=0;
        for(auto it=numbers.begin();it!=numbers.end();it++) //檢查res是否符合要求
        {
            if(*it==res) co++;
        }
        if(co>cnt/2) return res;
        return 0;
    }
};

最小的K個數

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

求最小的k個數,其實就是用堆吧,很多大佬手寫堆,真的厲害。。。我這裏用的c++優先隊列

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        priority_queue<int> que;
        vector<int> res; 
        if(k>input.size()) return res;
        for(auto it=input.begin();it!=input.end();it++)
        {
            que.push(*it);
            while(que.size()>k) que.pop();
        }
        while(!que.empty()) res.push_back(que.top()),que.pop();
        return res;
    }
};

連續子數組的最大和

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

class Solution {
public:
    int dp[100005];
    int INF=1e9+7;
    int FindGreatestSumOfSubArray(vector<int> array) {
        int ans=-INF;
        dp[0]=array[0];
        for(int i=1;i<array.size();i++)
        {
            dp[i]=max(dp[i-1]+array[i],array[i]);
            ans=max(ans,dp[i]);
        }
        return ans;
    }
};

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

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

這個題就是針對每一位計算即可,個位十位百位,看一下每一位1出現的規律。

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n)
    {
        int tmp=n,ans=0,gs=1;
        while(tmp)
        {
            int k=tmp%10;
            tmp=tmp/10;
            if(k>1) ans+=(tmp+1)*gs;
            else if(k==1) ans+=(tmp*gs+n%gs+1);
            else ans+=(tmp*gs);
            gs=gs*10;
        }
        return ans;
    }
};

把數組排成最小的數

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

這個題也挺神奇,剛開始沒想出來= = 就是把所有數丟進去排序 排序規則是 a+b<b+a,因爲位數一定相同所以直接使用string的比較就行。做完發現以前好像做過這道題(不過我電腦運行不了 to_string()函數 煩)

class Solution {
public:
    static bool cmp(int a,int b)
    {
        string s1=to_string(a)+to_string(b);
        string s2=to_string(b)+to_string(a);
        return s1<s2;
    }
    string PrintMinNumber(vector<int> numbers) {
        string ans="";
        if(numbers.size()==0) return ans;
        sort(numbers.begin(),numbers.end(),cmp);
        for(auto it=numbers.begin();it!=numbers.end();it++)
            ans+=to_string(*it);
        return ans;
    }
};

醜數

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

醜數一定是 2 3 5相乘得到 又1是第一個醜數,直接讓 1*2 1*3 1*5丟到優先隊列裏就好,bfs,其實也沒必要直接乘出來最小的數繼續乘即可,我只是閒着沒事幹

class Solution {
public:
    map<long long,bool> mp;   //檢查是否出現過
    int GetUglyNumber_Solution(int index) {
        if(index<=0) return 0;
        priority_queue<long long,vector<long long>,greater<long long> > que;//注意long long會爆int
        que.push(1);
        mp[1]=1; 
        int cnt=0,ans=0;
        while(!que.empty())
        {
            long long x=que.top();
            que.pop(),cnt++;
            if(cnt==index)
            {
                ans=x;
                break;
            }
            if(mp[x*2]==0) que.push(x*2),mp[x*2]=1;
            if(mp[x*3]==0) que.push(x*3),mp[x*3]=1;
            if(mp[x*5]==0) que.push(x*5),mp[x*5]=1;
        }
        return ans;
    }
};

第一個只出現一次的字符

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

這個感覺也沒啥方法,map儲存字符出現次數,最後掃一遍看只出現一次的是誰。

class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        map<char,int> mp;
        for(auto it=str.begin();it!=str.end();it++) mp[*it]++;
        for(auto it=str.begin();it!=str.end();it++)
        {
            if(mp[*it]==1) return it-str.begin();
        }
        return -1;
    }
};

數組中的逆序對

在數組中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個數組中的逆序對的總數P。並將P對1000000007取模的結果輸出。 即輸出P%1000000007(歸併排序就完事,好久沒寫歸併了,出好多問題)

class Solution {
public:
    int ans=0;
    int mod=1e9+7;
    vector<int> res;
    void mer(int l,int r)
    {
        if(l==r) return ;
        int mid=(l+r)>>1;
        mer(l,mid);
        mer(mid+1,r);
        vector<int> v;
        int pl=l,ql=mid+1;
        while(pl<=mid&&ql<=r)   //合併左右的結果
        {
            if(res[pl]<=res[ql]) v.push_back(res[pl++]);
            else v.push_back(res[ql]),ans+=(mid+1-pl),ans%=mod,ql++;//合併時更新ans的結果
        }
        while(pl<=mid) v.push_back(res[pl++]);
        while(ql<=r) v.push_back(res[ql++]);
        int cnt=0;
        for(int i=l;i<=r;i++) res[i]=v[cnt++];
    }
    int InversePairs(vector<int> data) {
        res=data;
        int l=0,r=res.size()-1;
        mer(l,r);   //歸併排序
        return ans;
    }
};

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

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

又是被大佬折服的題目,思路不麻煩,首先找到長度差,讓長的鏈表先走鏈表差,然後一起走,如果有公共節點則會相同,主要是被一個寫法折服了。巨簡潔。。。

class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        ListNode *p1=pHead1,*p2=pHead2;
        while(p1!=p2)   //仔細思考發現它就是對的
        {
            p1= p1==NULL ? pHead2:p1->next;
            p2= p2==NULL ? pHead1:p2->next;
        }
        return p1;
    }
};

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

統計一個數字在排序數組中出現的次數。(兩次二分即可)

class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        if(data.size()==0) return 0;
        int p=lower_bound(data.begin(),data.end(),k)-data.begin();
        int q=upper_bound(data.begin(),data.end(),k)-data.begin();
        if(data[p]==k) return q-p;
        return 0;
    }
};

二叉樹的深度

輸入一棵二叉樹,求該樹的深度。從根結點到葉結點依次經過的結點(含根、葉結點)形成樹的一條路徑,最長路徑的長度爲樹的深度。(簡單dfs遍歷一遍不斷取最大值即可)

class Solution {
public:
    int res=0;
    void finddep(TreeNode* root,int dep)
    {
        if(root==NULL) return ;
        res=max(res,dep);
        finddep(root->left,dep+1);
        finddep(root->right,dep+1);
    }
    int TreeDepth(TreeNode* pRoot)
    {
        finddep(pRoot,1);
        return res;
    }
};

平衡二叉樹

根據定義出發遞歸判斷,兩兒子的深度差不超過1

class Solution {
public:
    int dep(TreeNode* root)
    {
        if(root==NULL) return 0;
        return max(dep(root->left),dep(root->right))+1;
    }
    bool IsBalanced_Solution(TreeNode* pRoot) {
        if(pRoot==NULL) return 1;
        if(abs(dep(pRoot->left)-dep(pRoot->right))<=1&&IsBalanced_Solution(pRoot->left)&&IsBalanced_Solution(pRoot->right))
           return 1;
        return 0;
    }
};

數組中只出現一次的數字

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

這個題很有意思,如果只有1個數字,那麼直接所有數異或就是答案,因爲偶數次的數異或和爲0.

這裏有兩個數,那麼我們可以將異或出來的結果中二進制某一位爲1的位置挑出來,然後以這個位置的0 1情況將所有數分爲兩類,然後每一類裏進行異或就是答案。因爲這兩個數的這個位置01情況一定不同,所以按這樣的情況分組。

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        int res=0;
        for(auto it=data.begin();it!=data.end();it++) res^=*it;
        int cnt=0;
        for(int i=0;(1<<i)<=2e9+7;i++)
        {
            if((1<<i)&res)    //記錄二進制1的位置
            {
                cnt=i;
                break;
            }
        }
        vector<int> v1,v2;
        for(auto it=data.begin();it!=data.end();it++)//分組
        {
            if(*it&(1<<cnt)) v1.push_back(*it);
            else v2.push_back(*it);
        }
        int r1=0,r2=0;
        for(auto it=v1.begin();it!=v1.end();it++) r1^=*it;//分別異或得出答案
        for(auto it=v2.begin();it!=v2.end();it++) r2^=*it;
        *num1=r1,*num2=r2;
    }
};

和爲S的連續正數序列

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

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        int l=1,r=1,res=0;  //雙指針掃描
        vector<vector<int> > ans;
        while(r<=sum)
        {
            res+=r;
            while(res>sum) res-=l,l++;//比結果大移動左指針
            if(res==sum&&r-l+1>=2)//符合儲存答案
            {
                vector<int> v;
                for(int i=l;i<=r;i++) v.push_back(i);
                ans.push_back(v);
            }
            r++;
        }
        return ans;
    }
};

和爲S的兩個數字

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

和相同的數,乘積最小必然是從最左開始找到的第一對符合要求的數(感覺還是雙指針,因爲是排好序的)。

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
        vector<int> ans;
        if(array.size()==0) return ans;
        int head=0,tail=array.size()-1;
        int res=array[head]+array[tail];
        while(head<tail)
        {
            while(res>sum&&head<tail) res-=array[tail--],res+=array[tail];
            while(res<sum&&head<tail) res-=array[head++],res+=array[head];
            if(res==sum)
            {
                ans.push_back(array[head]);
                ans.push_back(array[tail]);
                break;
            }
        }
        return ans;
    }
};

左旋轉字符串

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

class Solution {
public:
    string LeftRotateString(string str, int n) {
        reverse(str.begin(),str.end());
        reverse(str.begin(),str.begin()+str.size()-n);
        reverse(str.begin()+str.size()-n,str.end());
        return str;
    }
};

翻轉單詞順序列

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

class Solution {
public:
    string ReverseSentence(string str) {
        reverse(str.begin(),str.end());//整體反轉
        int ls=str.length();
        int l=0,r=0;
        for(int i=0;i<ls;i++)
        {
            if(str[i]==' ')
            {
                reverse(str.begin()+l,str.begin()+i);//單詞局部反轉
                l=i+1;
            }
        }
        reverse(str.begin()+l,str.end());
        return str;
    }
};

撲克牌順子

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的運氣如何, 如果牌能組成順子就輸出true,否則就輸出false。爲了方便起見,你可以認爲大小王是0。

模擬題目吧,找出組順子的條件,我比較暴力,就是找最小最大,然後從最小到最大遍歷沒有就用大小王替代即可。

class Solution {
public:
    bool IsContinuous( vector<int> numbers ) {
        if(numbers.size()==0) return 0;
        int mi=100,ma=0,cnt=0,x=0;
        int flag=0;
        for(auto it=numbers.begin();it!=numbers.end();it++)
        {
            if(*it!=0)
            {
                mi=min(mi,*it);
                ma=max(ma,*it);
                if((x&(1<<(*it)))==0) x|=(1<<(*it));//位運算儲存牌有無
                else flag=1;
            }
            else cnt++; //王的數目
        }
        if(flag) return 0;
        for(int i=mi;i<=ma;i++)//判斷是否順子
        {
            if((x&(1<<i))==0)
            {
                if(cnt) cnt--;
                else flag=1;
            }
        }
        if(flag) return 0;
        return 1;
    }
};

孩子們的遊戲(圓圈中最後剩下的數)

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

兩種方法,暴力模擬和公式,我這裏暴力模擬用的隊列,感覺簡單一些。

class Solution {
public:
    bool vis[1000005];
    int LastRemaining_Solution(int n, int m)
    {
        if(n<1||m<1) return -1;
        queue<int> que;
        for(int i=0;i<n;i++) que.push(i);
        while(que.size()>1)
        {
            for(int i=0;i<m-1;i++)    //讀m-1個數
            {
                que.push(que.front());
                que.pop();
            }
            que.pop();    //第m個彈出
        }
        return que.front(); //返回最後一個數
    }
};

公式法不是很會證明,貼別人的。牛客網

 

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

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

很矇蔽,說用什麼短路求值,以爲什麼高深的東西 結果就是 A&&B A不滿足不執行B

class Solution {
public:
    int Sum_Solution(int n) {
        int res=n;
        res&&(res+=Sum_Solution(n-1));
        return res;
    }
};

不用加減乘除做加法

寫一個函數,求兩個整數之和,要求在函數體內不得使用+、-、*、/四則運算符號。

這個題目也很有意思,兩個數之和,經過研究可以發現 x^y的結果是 x+y(不帶進位),而(x&y)<<1是單純的進位,那麼不停的利用這個性質直到進位爲0即可

class Solution {
public:
    int Add(int num1, int num2)
    {
        if(num1&num2) return Add(num1^num2,(num1&num2)<<1);
        else return num1^num2;
    }
};

把字符串轉換成整數

將一個字符串轉換成一個整數(實現Integer.valueOf(string)的功能,但是string不符合數字要求時返回0),要求不能使用字符串轉換整數的庫函數。 數值爲0或者字符串不是一個合法的數值則返回0。

暴力模擬

class Solution {
public:
    int StrToInt(string str) {
        int ls=str.length(),ans=0;
        if(str[0]=='+'||str[0]=='-'||(str[0]>='0'&&str[0]<='9'))//第一個位置的要求
        {
            int fa=0;    //標記是否正負號
            if(str[0]=='+'||str[0]=='-') fa=1;    //
            if(fa) ans=0;
            else ans=str[0]-'0';
            for(int i=1;i<ls;i++)
                if(str[i]<'0'||str[i]>'9') return 0;
            for(int i=1;i<ls;i++) ans=ans*10+(str[i]-'0');
            if(fa&&str[0]=='-') ans=-ans;
            return ans;
        }
        else return 0;
    }
};

數組中重複的數字

在一個長度爲n的數組裏的所有數字都在0到n-1的範圍內。 數組中某些數字是重複的,但不知道有幾個數字是重複的。也不知道每個數字重複幾次。請找出數組中任意一個重複的數字。 例如,如果輸入長度爲7的數組{2,3,1,0,2,5,3},那麼對應的輸出是第一個重複的數字2(很有趣的方法,因爲所有數0~n-1,當遇到num[i]的時候 可以讓 num[num[i]]+=n,這樣碰到一個num[num[i]]>length 如果大於n的話就說明重複了)

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 x=numbers[i];
            if(x>=length) x-=length;
            if(numbers[x]>=length)
            {
                *duplication=x;
                return 1;
            }
            numbers[x]+=length;
        }
        return 0;
    }
};

構建乘積數組

給定一個數組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]。不能使用除法。

我的做法是計算一個前綴積數組和後綴積數組,之後的就很簡單。

class Solution {
public:
    vector<int> multiply(const vector<int>& A) {
        int n=A.size();
        vector<int> v1(n,1),v2(n,1),ans(n,1);
        if(n==0) return ans;
        v1[0]=A[0],v2[n-1]=A[n-1];
        for(int i=1;i<n;i++) v1[i]=v1[i-1]*A[i];    //計算前綴和後綴
        for(int i=n-2;i>=0;i--) v2[i]=v2[i+1]*A[i];
        ans[0]=v2[1],ans[n-1]=v1[n-2];//然後計算答案
        for(int i=1;i<n-1;i++) ans[i]=v1[i-1]*v2[i+1];
        return ans;
    }
};

正則表達式匹配

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

class Solution {
public:
    bool check(char *str,char *s,int p1,int p2)
    {
        int lt=strlen(str),ls=strlen(s);
        //printf("%d %d\n",p1,p2);
        if(p1==lt)//str串空了 有一種情況符合要求
        {
            for(int i=p2;i<ls;i+=2)
                if(s[i+1]!='*') return 0;
            return 1;
        }
        if(p2==ls) return 0;//str沒空s空了 失敗
        if(s[p2+1]!='*'&&(str[p1]==s[p2]||s[p2]=='.'))//沒有*只能往前推進
            return check(str,s,p1+1,p2+1);
        if(s[p2+1]=='*')//如果是*號 相同有兩種匹配方式
        {
            if(str[p1]==s[p2]||s[p2]=='.') return check(str,s,p1+1,p2)||check(str,s,p1,p2+2);
            return check(str,s,p1,p2+2);//否則只能跳過
        }
        return 0;
    }
    bool match(char* str, char* pattern)
    {
        return check(str,pattern,0,0);
    }
};

 

表示數值的字符串

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

這個題目我的代碼應該還有很多缺陷,不過真的感覺沒什麼辦法 應該評論區什麼的用自動機做的是對的,我暴力判斷。

討論區:牛客網

class Solution {
public:
    bool isNumeric(char* str)
    {
        bool flag1=0,flag2=0,flag3=0; //分別代表   +-  .  E
        int ls=strlen(str);
        for(int i=0;i<ls;i++)
        {
            if(str[i]=='E'||str[i]=='e')
            {
                if(i==0||i==ls-1||flag3) return 0;    //不能是第一個和最後一個 並且之前沒出現過
                flag3=1;
            }
            else if(str[i]=='.')
            {
                if(i==ls-1||flag2||flag3) return 0; //. 和 E都沒出現過並且不是最後一位 試了發現能做第一位 
                flag2=1;
            }
            else if(str[i]=='+'||str[i]=='-')
            {
                if(i>0&&(str[i-1]!='E'&&str[i-1]!='e')) return 0;//+-前一位不能是E 或者 e
                flag1=1;
            }
            else if(str[i]<'0'||str[i]>'9') return 0;
        }
        return 1;
    }
};

字符流中第一個不重複的字符

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

直接將字符推入隊列 同時一個數組計數,彈出隊列頭時如果出現次數大於1 則循環彈出直到 隊列頭爲只出現1次的字符。

class Solution
{
public:
  //Insert one char from stringstream
    int vis[128];
    queue<char> que;
    void Insert(char ch)
    {
        if(vis[ch]==0) que.push(ch);
        vis[ch]++;
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce()
    {
        while(!que.empty())
        {
            char tmp=que.front();
            if(vis[tmp]==1) return tmp;
            que.pop();
        }
        return '#';
    }
};

鏈表中環的入口結點

給一個鏈表,若其中包含環,請找出該鏈表的環的入口結點,否則,輸出null。

這個很有趣的題目 貼評論區吧 解法很有趣:牛客網

正常的方法

class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if(pHead->next==NULL||pHead->next->next==NULL) return nullptr;
        ListNode *fast=pHead->next->next;
        ListNode *slow=pHead->next;
        while(fast!=slow)
        {
            if(fast!=NULL&&slow!=NULL)
            {
                fast=fast->next->next;
                slow=slow->next;
            }
            else return NULL;
        }
        fast=pHead;
        while(fast!=slow)
        {
            fast=fast->next;
            slow=slow->next;
        }
        return fast;
    }
};

斷鏈法

class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if(pHead->next==NULL||pHead->next->next==NULL) return nullptr;
        ListNode *fast=pHead->next;
        ListNode *slow=pHead;
        while(fast!=nullptr)
        {
            slow->next=nullptr;
            slow=fast;
            fast=fast->next;
        }
        return slow;
    }
};

刪除鏈表中重複的結點

在一個排序的鏈表中,存在重複的結點,請刪除該鏈表中重複的結點,重複的結點不保留,返回鏈表頭指針。 例如,鏈表1->2->3->3->4->4->5 處理後爲 1->2->5

這個刪除重複節點,可以先新建一個空的頭節點,然後統一處理即可。

class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead)
    {
        if(pHead==NULL||pHead->next==NULL) return pHead;
        ListNode *tmp=new ListNode(-1);
        tmp->next=pHead;
        ListNode *p=tmp;
        while(p!=NULL)
        {
            if(p->next!=NULL&&p->next->next!=NULL&&p->next->val==p->next->next->val)
            {
                while(p->next!=NULL&&p->next->next!=NULL&&p->next->val==p->next->next->val) p->next=p->next->next;
                p->next=p->next->next;
            }
            else p=p->next;
        }
        return tmp->next;
    }
};

二叉樹的下一個結點

給定一個二叉樹和其中的一個結點,請找出中序遍歷順序的下一個結點並且返回。注意,樹中的結點不僅包含左右子結點,同時包含指向父結點的指針。

如果當前節點有右兒子,則找右兒子最左邊的節點。沒有則不斷返回父親節點,看當前節點是否是父親節點的左兒子,如果是,那麼就將當前節點做爲答案,否則就不斷向上找。直到爲根,返回空

/*
struct TreeLinkNode {
    int val;
    struct TreeLinkNode *left;
    struct TreeLinkNode *right;
    struct TreeLinkNode *next;
    TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
        
    }
};
*/
class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode)
    {
        if(pNode->right)    //如果有右兒子
        {
            pNode=pNode->right;
            while(pNode->left) pNode=pNode->left;
            return pNode;
        }
        while(pNode->next)//向上返回
        {
            TreeLinkNode *fa=pNode->next;
            if(fa->left==pNode) return fa;//是否是左兒子
            pNode=fa;
        }
        return NULL;
    }
};

對稱的二叉樹

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

就,就遞歸判斷啊= = 注意某些特殊的東西 鏡像的注意鏡像的位置

class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot)
    {
        if(pRoot==NULL) return 1;
        return cmp(pRoot->left,pRoot->right);//檢查左右兒子
    }
    bool cmp(TreeNode *lson,TreeNode *rson)
    {
        if(lson==NULL&&rson==NULL) return 1;
        if(lson==NULL&&lson!=rson) return 0;
        if(rson==NULL&&lson!=rson) return 0;//只有當前節點相同 檢查下一層 注意鏡像性
        if(lson->val==rson->val) return cmp(lson->left,rson->right)&&cmp(lson->right,rson->left);
        return 0;
    }
};

按之字形順序打印二叉樹

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

使用兩個棧進行bfs,剛好滿足了之字形的要求。

class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        stack<TreeNode*> s1,s2;
        vector<vector<int> > res;
        if(pRoot==NULL) return res;
        int flag=1;//標記使用s1 還是 s2
        s1.push(pRoot);//s1開始
        while(!s1.empty()||!s2.empty())
        {
            vector<int> ans;
            if(flag&1)
            {
                while(!s1.empty())//s1 bfs的值進入s2
                {
                    TreeNode *tmp=s1.top();
                    s1.pop();
                    ans.push_back(tmp->val);
                    if(tmp->left) s2.push(tmp->left);
                    if(tmp->right) s2.push(tmp->right);
                }
                res.push_back(ans);
                flag^=1;
            }
            else
            {
                while(!s2.empty())//同上
                {
                    TreeNode *tmp=s2.top();
                    s2.pop();
                    ans.push_back(tmp->val);
                    if(tmp->right) s1.push(tmp->right);
                    if(tmp->left) s1.push(tmp->left);
                }
                res.push_back(ans);
                flag^=1;
            }
        }
        return res;
    }
};

把二叉樹打印成多行

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

用一個隊列即可,用一個變量記錄下一層的節點數進行bfs

class Solution {
public:
        vector<vector<int> > Print(TreeNode* pRoot) {
            queue<TreeNode*> que;
            que.push(pRoot);
            vector<vector<int> > res;
            if(pRoot==NULL) return res;
            int op=0,ed=1;
            while(!que.empty())
            {
                vector<int> ans;
                int tp=ed;
                while(op<ed)//ed不斷更新 當前層的節點
                {
                    TreeNode *tmp=que.front();
                    que.pop();
                    ans.push_back(tmp->val);
                    if(tmp->left) que.push(tmp->left),tp++;//tp不斷更新個數
                    if(tmp->right) que.push(tmp->right),tp++;
                    op++;
                }
                ed=tp;
                res.push_back(ans);
            }
            return res;
        }
};

序列化二叉樹

請實現兩個函數,分別用來序列化和反序列化二叉樹

很奇怪的題目,其實並不難,主要是字符串和數字的轉換很噁心,但是跟着大佬學了一種神奇的方法。

class Solution {
public:
    vector<int> res;
    void dfs1(TreeNode *root)
    {
        if(root==nullptr)//空節點存入-1
        {
            res.push_back(-1);
            return ;
        }
        res.push_back(root->val);
        dfs1(root->left);
        dfs1(root->right);
    }
    TreeNode* dfs2(int* &p)
    {
        if(*p==-1)//空節點 return
        {
            p++;
            return nullptr;
        }
        TreeNode *root=new TreeNode(*p);//不斷擴展二叉樹
        p++;
        root->left=dfs2(p);
        root->right=dfs2(p);
        return root;
    }
    char* Serialize(TreeNode *root) {
        res.clear();
        dfs1(root); //按照前序遍歷序列化 結果存入res
        int *s=new int[res.size()];
        for(int i=0;i<(int)res.size();i++) s[i]=res[i];
        return (char*)s;//直接強制轉換
    }
    TreeNode* Deserialize(char *str) {
        int *p=(int*)str;
        TreeNode *root=dfs2(p);//得到原二叉樹
        return root;
    }
};

二叉搜索樹的第k個結點

給定一棵二叉搜索樹,請找出其中的第k小的結點。例如, (5,3,7,2,4,6,8)    中,按結點數值大小順序第三小結點的值爲4(左邊都比它大,右邊都比它小)

class Solution {
public:
    int s=0;//s記錄遍歷的節點數
    TreeNode* KthNode(TreeNode* pRoot, int k)
    {
        if(pRoot==NULL) return NULL;
        TreeNode *tmp=KthNode(pRoot->left,k);//正常遍歷節點
        if(tmp!=NULL) return tmp;
        s++;//如果s==k 則當前節點爲答案
        if(s==k) return pRoot;
        return KthNode(pRoot->right,k);//返回右子樹的答案
    }
};

數據流中的中位數

不斷輸入數據並且不斷返回排好序的中位數

利用兩個優先隊列,一個從大到小,一個從小到大,並動態調整他們的大小差小於等於1,如果兩個隊列中的數個數相同 返回均分。否則返回大小多1的那個隊列頭。

class Solution {
public:
    int cnt=0;
    priority_queue<int> que1;
    priority_queue<int,vector<int>,greater<int> > que2;
    void Insert(int num)
    {
        que1.push(num);//推入que1
        while(que1.size()>que2.size()+1) que2.push(que1.top()),que1.pop();//動態調整
        if(!que1.empty()&&!que2.empty()&&que1.top()>que2.top()) que2.push(que1.top()),que1.pop();
    }
    double GetMedian()
    {
        if(que1.size()==que2.size()) return ((que1.top()+que2.top())*1.0/2);
        else if(que1.size()>que2.size()) return que1.top();
        else return que2.top();
        return 0;
    }
};

滑動窗口的最大值

給定一個數組和滑動窗口的大小,找出所有滑動窗口裏數值的最大值。例如,如果輸入數組{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]}。

單調隊列的經典題目了= = 

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

矩陣中的路徑

請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。路徑可以從矩陣中的任意一個格子開始,每一步可以在矩陣中向左,向右,向上,向下移動一個格子。如果一條路徑經過了矩陣中的某一個格子,則之後不能再次進入這個格子。 例如 a b c e s f c s a d e e 這樣的3 X 4 矩陣中包含一條字符串"bcced"的路徑,但是矩陣中不包含"abcb"路徑,因爲字符串的第一個字符b佔據了矩陣中的第一行第二個格子之後,路徑不能再次進入該格子(暴力dfs)

class Solution {
public:
    int ls=0;
    bool vis[1005][1005];
    int dx[4]={1,0,-1,0};
    int dy[4]={0,1,0,-1};
    bool findpath(char *matrix,int x,int y,int row,int col,int cnt,char *str)
    {
        if(matrix[x*col+y]!=str[cnt]) return 0;
        if(cnt==ls-1&&matrix[x*col+y]) return 1;
        for(int i=0;i<4;i++)//延伸dfs
        {
            int xx=x+dx[i];
            int yy=y+dy[i];
            if(xx<0||xx>=row||yy<0||yy>col) continue;
            if(vis[xx][yy]) continue;
            vis[xx][yy]=1;
            if(findpath(matrix,xx,yy,row,col,cnt+1,str)) return 1;
            vis[xx][yy]=0;
        }
        return 0;
    }
    bool hasPath(char* matrix, int rows, int cols, char* str)
    {
        memset(vis,0,sizeof vis);
        ls=strlen(str);
        if(rows*cols<ls) return 0;
        if(ls==0) return 1;
        for(int i=0;i<rows;i++)
        {
            for(int j=0;j<cols;j++)
            {
                if(matrix[i*cols+j]==str[0])
                {
                    vis[i][j]=1;
                    if(findpath(matrix,i,j,rows,cols,0,str)) return 1;//從當前點出發是否可行
                    vis[i][j]=0;
                }
            }
        }
        return 0;
    }
};

機器人的運動範圍

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

class Solution {
public:
    map< pair<int,int>,bool> mp;//存當前點是否出現過
    int dx[4]={0,1,0,-1};
    int dy[4]={1,0,-1,0};
    int cal(int x)//計算數各位和
    {
        int res=0;
        while(x)
        {
            res+=(x%10);
            x=x/10;
        }
        return res;
    }
    bool check(int xx,int yy,int k)//檢查座標的合法性
    {
        if((cal(xx)+cal(yy))>k) return 0;
        return 1;
    }
    int movingCount(int threshold, int rows, int cols)
    {
        mp.clear();
        queue<pair<int,int>> que;
        que.push({0,0});
        int ans=0; mp[{0,0}]=1;
        if(threshold<0) return 0;
        while(!que.empty())//bfs
        {
            pair<int,int> tmp=que.front();
            que.pop(); ans++;
            for(int i=0;i<4;i++)
            {
                int xx=tmp.first+dx[i];
                int yy=tmp.second+dy[i];
                if(xx<0||xx>=rows||yy<0||yy>=cols) continue;
                if(check(xx,yy,threshold)&&mp[{xx,yy}]!=1)
                    que.push({xx,yy}),mp[{xx,yy}]=1;
            }
        }
        return ans;
    }
};

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章