劍指offer刷題

題目1 孩子們的遊戲(圓圈中最後剩下的數)[題目鏈接](https://www.nowcoder.com/practice/f78a359491e64a50bce2d89cff857eb6?tpId=13&tqId=11199&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)

思路1 :模擬,很容易想到用循環鏈表模擬,選擇何種形式來實現循環鏈表的功能呢?最開始想的是用數組來模擬循環鏈表,用一個數組來記錄數字是否出局,若出局則跳過這個數字。 看到一個比較巧妙的思路,可以用一個隊列來模擬循環鏈表,利用隊列先進先出的特點,前面報數非爲m的數依次加入隊列,相當於一個滑動窗口,當報數爲m-1時,直接出棧。
代碼1 : 數組模擬(代碼略複雜,下次回來改)

class Solution {
public:
    int LastRemaining_Solution(int n, int m)
    {
        if( n<=0 || m<=0)
            return -1 ;
        if (n == 1)
            return 0;
        int exit = 0 ;
        int Count = 0, index=0;
        bool isExit[n] ;
        for(int i=0; i<n;i++)
            isExit[i] = false ;
        while(exit <n-1)
        {
            if(index == n)
                index = 0 ;
            //cout << "Count is " << Count << endl ;
            //cout <<"index is " << index << endl ;
            if(Count == m-1)
            {
                //cout <<"exit " << index << endl ;
                isExit[index] = true ;
                exit ++ ;
                index ++ ;
                if(index == n)
                    index = 0 ;
                while(isExit[index] == true)
                {
                    index ++ ;
                    if(index == n)
                        index = 0;
                }
                Count = 0;
            }
            else
            {
                    index ++ ;
                    if(index == n)
                        index = 0 ;
                    while(isExit[index] == true)
                    {
                        index ++ ;
                        if(index == n)
                            index = 0;
                    }
                    Count ++ ;
            }
 
        }
        for(int i=0; i<n; i++)
            if(isExit[i] == false)
                return i ;
    }
};

代碼2(隊列模擬)

class Solution {
public:
    int LastRemaining_Solution(int n, int m)
    {
        if(n <=0 || m <=0)
            return -1 ;
        if(n == 1)
            return 0 ;
        queue <int> tmp ;
        for(int i=0; i<n ;i++)
            tmp.push(i) ;
        while(tmp.size() > 1)
        {
            for(int i=0; i<m-1; i++)
            {
                int front = tmp.front() ;
                tmp.pop() ;
                tmp.push(front) ;
            }
            tmp.pop() ;
        }
        return tmp.front() ;
    }
};

思路2 :找規律(具體數學中學過,待補)

題目2 撲克牌順子[題目](https://www.nowcoder.com/practice/762836f4d43d43ca9deb273b3de8e1f4?tpId=13&tqId=11198&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
題意:判斷5張牌,是否組成順子(大小王用0,表示,A表示0,J表示11)。
思路1:判斷是否爲順子:(1) 長度爲5 (2)除0外沒有重複數字(3)max -min <5.推廣到一般情況順子長度爲k, max -min <k。

思路2:(1)除0外沒有重複數字 (2)排序後,用num[i]-num[i-1]-1記錄缺幾張牌,計算0的個數(可變牌數),若可變牌>=缺的牌,則可組成順子。

代碼1

class Solution {
public:
    bool IsContinuous( vector<int> numbers ) {
        int size = numbers.size() ;
        if(size != 5)
            return false ;
        int Count[14] ={0} ;
        int Max = -1, Min=14 ;
        for(int i=0; i<size; i++)
        {
            Count[numbers[i]]++ ;
            if(numbers[i] == 0)
                continue ;
            if(Count[numbers[i]] > 1)
                return false ;
            if(numbers[i] > 13 || numbers[i] < 0)
                return false ;
            if(numbers[i] > Max)
                Max = numbers[i] ;
            if(numbers[i] < Min)
                Min = numbers[i] ;
        }
        if(Max - Min < 5)
            return true ;
        return false ;
    }
};

代碼2

class Solution {
public:
    bool IsContinuous( vector<int> numbers ) {
        int size = numbers.size() ;
        if(size <=0)
            return false ;
        sort(numbers.begin(), numbers.end()) ;
        int Count=0, lost=0, index;
        for(int i=0; i<size; i++)
        {
            if(numbers[i] == 0)
            {
                Count ++ ;
            }
            if(numbers[i] >0)
            {
                index = i ;
                break ;
            }
        }
        for(int i=index+1; i< size; i++)
        {
            if(numbers[i] == numbers[i-1])
                return false ;
            else
                lost += numbers[i]-numbers[i-1]-1 ;
        }
        if(Count >= lost)
            return true ;
        return false ;
    }
};

題目3 翻轉單詞順序列 [題目](https://www.nowcoder.com/practice/3194a4f4cf814f63919d0790578d51f3?tpId=13&tqId=11197&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
題意:將單詞序列翻轉,如:“I am a student.”翻轉後爲“student. a am I”。
思路1 :(1)用指針p1掃描原字符串,並記錄每個單詞長度len,遇到空格時停止;(2)p2指向新字符串尾部;(3)p1複製給p2,p1向後走len步,p2向後走len步(4) p1+len回到空格處,將空格複製到新字符串中 。repeat上述過程,直到字符串結束。

思路2: 單指針從前往後掃描,巧妙利用string的連接操作。

代碼1 :

class Solution {
public:
    string ReverseSentence(string str) {
        int length = str.length() ;
        if(length <=1)
            return str ;
        string res=str ;
        int pstr=0, pres=length-1,pstart=0;
        int len = 0 ;
        while(str[pstr] != ' ' && pstr < length)
        {
            pstr ++ ;
            len ++ ;
            if(str[pstr] ==' ')
            {
                pstart = pstr - len ;
                pstr -=1 ;
                while(pstr>=pstart)
                {
                    res[pres--]=str[pstr--];
                }
                pstr += len+1 ;
                res[pres--] = str[pstr++] ;
                len = 0 ;
            }
            if(pstr == length-1)
            {
                pstart = pstr - len ;
                while(pstr>=pstart && pres >=0)
                {
                    res[pres--]=str[pstr--];
                }
                break ;
            }
        }
        return res ;
    }
};

代碼2:

class Solution {
public:
    string ReverseSentence(string str) {
        int length = str.length() ;
        if(length <= 1)
            return str ;
        string res="", tmp="" ;
        for(int i=0; i<length; i++)
        {
            if(str[i]==' ')
            {
                res =" "+tmp+res ;
                tmp="";
            }
            else tmp += str[i] ;
        }
        if(tmp.size()) res = tmp + res;
        return res ;
    }
};

題目4 左旋轉字符串 題目鏈接
題意:str = XY 求輸出YX
思路1 :直接利用字符串特性,取出X,Y,組合成YX
思路2:假設字符串abcdef,n=3,設X=abc,Y=def,所以字符串可以表示成XY,如題幹,問如何求得YX。假設X的翻轉爲X,XT=cba,同理YT=fed,那麼YX = (XTYT)T,三次翻轉後可得結果。
代碼1:

class Solution {
public:
    string LeftRotateString(string str, int n) {
        int length = str.length() ;
        if(length <= 1)
            return str ;
        n = n%length ;
        string tmp = str.substr(0,n);
        string tmp2 =str.substr(n, length-n);
        return tmp2+tmp;
         
    }
};

代碼2:

class Solution {
public:
    void fun(string &s,int start,int end)
    {
        char temp;
        while(start<end)
        {
            temp=s[start];
            s[start]=s[end];
            s[end]=temp;
            start++;
            end--;
        }
    }
    string LeftRotateString(string str, int n) {
        int len=str.length();
        if(0==len || 0==n)
            return str;
        string &temp=str;
        fun(temp,0,n-1);
        fun(temp,n,len-1);
        fun(temp,0,len-1);
        return str;
    }
};

題目5 和爲S的兩個數字題目
題意:遞增數列,求兩個數字和爲S,若有多組解,求使得乘積最小的那組。
思路:左右指針法,用兩個指針從兩頭向中間逼近,a+b=s,a,b相差越大,a*b越小。
代碼:

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
        vector<int> res ;
        int size = array.size() ;
        int plow = 0, phigh = size -1 ;
        while(plow <= phigh)
        {
            int s= array[plow] + array[phigh] ;
            if(s < sum)
            {
                plow ++ ;
            }
            if(s > sum)
            {
                phigh -- ;
            }
            if(s == sum)
            {
                res.push_back(array[plow]) ;
                res.push_back(array[phigh]) ;
                return res ;
            }
        }
        return res ;
    }
};

題目6 和爲S的連續正數序列題目
題意:輸出求和爲S的連續正數序列,序列內按照從小至大的順序,序列間按照開始數字從小到大的順序
思路 :雙指針法,兩個指針分別放在數組的第一個數字和第二個數字處,比較數字的和來移動指針,cur = (plow + phigh)*(phigh-plow+1)/2 ,cur>s, high 向左移,cur <s plow右移(遞增數列的性質),cur=s, 得到一組解,plow右移(或者high 左移)繼續找另一組解。 因爲是等差數列,所以可以直接用公式求和。

推廣到一般情況: 正數數組,求正數子序列使得 和爲s。可以用前綴和數組來計算,plow 到phigh之間的數字和可以表示爲 sum[phigh]-sum[plow-1]。同樣地用雙指針,只要前綴和數組滿足遞增數列就可以cur>s, high 向左移,cur <s plow右移(遞增數列的性質),cur=s, 得到一組解,plow右移(或者high 左移)繼續找另一組解移動指針了。如果前綴和數組滿足遞增數列,就只能暴力遍歷o(n2)了

代碼

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        vector<vector<int> > res ;
        int plow =1, phigh = 2 ;
        while(plow < phigh)
        {
            int cur = (plow + phigh)*(phigh-plow+1)/2 ;
            if(cur == sum)
            {
                vector<int > array ;
                for(int i=plow; i<=phigh; i++)
                {
                    array.push_back(i) ;
                }
                res.push_back(array) ;
                array.clear() ;
                plow ++ ;
            }
            if(cur < sum)
                phigh ++ ;
            if(cur > sum)
                plow ++ ;
        }
        return res ;
    }
};

題目7 數組中只出現一次的兩個數字題目
題意: 數組中只有兩個數字只出現一次,剩下的都出現兩次,找到那兩個數字。

思路: 初看這題的時候,想到了異或操作, 任何數和0異或結果都是自身,相同的數異或爲0。利用這個特性,如果數組中只有一個數字,那麼可以找到那個數字。現在有兩個這樣的數字該怎麼辦呢?分組(分兩組分別找),設只出現一次的數字是a和b,先把所有數字異或一遍,得到結果a xor b。a xor b二進制爲1的位表明 a,b 在該位一個爲0,一個爲1. 利用這個性質把數字分爲兩組(相同的數字肯定在一組),a,b肯定被分在不同組,然後組內異或就可以得到結果了。
代碼

class Solution {
public:
    int findFirst1(int num)
    {
        int bit = 1;
        while((num & 1) == 0  && bit < 32) //記得加括號,運算符優先級 & 比==優先級低
        {
            bit ++ ;
            num = num >> 1 ;
        }
        return bit ;
    }
    
    bool isBit1(int num, int bit)
    {
        for(int i=1; i<bit; i++)
            num = num >> 1 ;
        return (num & 1) == 1 ;
    }
    
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        int size = data.size() ;
        if(size == 2)
        {
            num1 = &data[0] ;
            num2 = &data[1] ;
        }
        int res = 0 ;
        for (int i=0; i<size; i++)
        {
            res ^= data[i] ;
        }
        int bit = findFirst1(res) ;
        int n1 = 0, n2 = 0;
        for (int i=0; i< size; i++)
        {
            if (isBit1(data[i], bit))
            {
                n1 = n1^data[i]; 
            }
            else 
                n2 = n2^data[i] ;
        }
        *num1 =n1 , *num2 =n2 ;
    }
};

題目 8 平衡二叉樹
題意:判斷一棵樹是不是平衡二叉樹
思路: 遞歸判斷樹的深度差是否爲1, skills 深度大於0表示正常的深度, <0表示不平衡。
代碼:

class Solution {
public:
    int getDepth(TreeNode *root, int depth)
    {
        if (depth < 0 ) 
            return -1 ;
        if (root == nullptr)
            return depth ;
        int leftDepth = getDepth(root->left, depth+1);
        int rightDepth = getDepth(root->right,depth+1 ) ;
        int diff = leftDepth - rightDepth ;
        if (diff >1 || diff <-1)
            return -1 ;
        else return max(leftDepth, rightDepth) ;
    }

    bool IsBalanced_Solution(TreeNode* pRoot) {
        int depth = getDepth(pRoot, 0) ;
        if (depth < 0)
            return false ;
        else 
            return true ;
    }
};

題目 9 二叉樹的深度
代碼:

class Solution {
public:
    int getDepth(TreeNode *root, int depth)
    {
        if(root == nullptr)
            return depth;
        int leftDepth = getDepth(root->left, depth+1) ;
        int rightDepth = getDepth(root->right, depth+1) ;
        return max(leftDepth, rightDepth);
    }
    
    int TreeDepth(TreeNode* pRoot)
    {
        return getDepth(pRoot, 0) ;
        
    }
};

題目10 數字在排序數組中出現的次數
思路:看見數組有序就想到 二分查找, 比較巧妙的思路是 k是整數,搜索k+0.5和k-0.5應該插入的位置,兩者相減就得到了。
代碼:

class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        return biSearch(data, k+0.5) - biSearch(data, k-0.5) ;
    }
private:
    int biSearch(const vector<int> & data, double num){
        int s = 0, e = data.size()-1;     
        while(s <= e){
            int mid = (e - s)/2 + s;
            if(data[mid] < num)
                s = mid + 1;
            else if(data[mid] > num)
                e = mid - 1;
        }
        return s;
    }
};

題目11 兩個鏈表的第一個公共節點

思路:最開始的時候連題意都沒有弄明白。先弄懂題意,兩個鏈表若有公共節點的話,會表現像一個Y字,問題是兩個鏈表長度不一樣,若鏈表長度一樣直接一一比較直到兩個指針相同。skills :讓長的鏈表先走 k步(k爲兩鏈表長度之差),然後兩個鏈表一起走。快慢指針在鏈表操作中很常見,求鏈表的倒數第k個節點,也是先讓一個指針走k步,然後另一個指針從頭和當前指針一起走,當前指針走到尾節點的時候,頭指針正好指向倒數第k個節點。

代碼

class Solution {
public:
    int GetListLength(ListNode * head)
    {
        int length = 0 ;
        while(head)
        {
            length ++ ;
            head = head->next ;
        }
        return length ;
    }
    
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        int len1 = GetListLength(pHead1) ;
        int len2 = GetListLength(pHead2) ;
        int diff , flag;
        if (len1 > len2) 
        {
            diff = len1 - len2 ;
            flag =1 ;
        }
        else {
            diff = len2 - len1 ;
            flag = 2 ;
        }
        if (flag == 1)
        {
            for (int i=1; i<= diff; i++)
                pHead1 = pHead1->next ;
        }
            
        if (flag == 2)
        {
            for (int i=1; i <=diff; i++)
                pHead2 = pHead2->next ;
        }
        while (pHead1 != nullptr && pHead2 != nullptr && pHead1 != pHead2)
        {
            pHead1 = pHead1->next ;
            pHead2 = pHead2->next ;
        }
        return pHead1 ;
    }
};

題目 12 數組中的逆序對題目
思路:能想到歸併排序這題就出來啦,記得歸併的時候從後到前,從前到後會有問題。(如:1 2 7 8、4 5 9 10歸併的時候從右到左,8 可以輕易判斷4,5都比8小,但是從 左到右的時候, 4,5已經歸並進去了,9,10大於8,這樣子計數是有問題的)。主代碼中不要寫太多遞歸,容易棧溢出
代碼

class Solution {
public:
    int InversePairs(vector<int> data) {
        if(data.size()<2) return 0;
        vector<int> copy;
        for(int i=0;i<data.size();i++){
            copy.push_back(data[i]);
        }
        long long count=InversePairsCore(data,copy,0,data.size()-1);
        copy.clear();
        return count%1000000007;
    }
     
    long long InversePairsCore(vector<int> &data,vector<int> &copy,int begin,int end){
        if(begin==end){
            copy[begin]=data[begin];
            return 0;
        }
         
        int L=(end-begin)/2;
        long long left=InversePairsCore(copy,data,begin,begin+L);
        long long right=InversePairsCore(copy,data,begin+L+1,end);
        int i=begin+L;
        int j=end,inxcopy=end;
        long long count=0;
        while(i>=begin && j>=begin+L+1){
            if(data[i]>data[j]){
                copy[inxcopy--]=data[i--];
                count+=j-begin-L;
            }else copy[inxcopy--]=data[j--];
        }
        //for(i=begin;i<=end;i++) copy[i]=data[i];
        for(;i>=begin;i--) copy[inxcopy--]=data[i];
        for(;j>=begin+L+1;j--) copy[inxcopy--]=data[j];
        return left+right+count;    
    }
};

題目13 第一個只出現一次的字母
在一個字符串(0<=字符串長度<=10000,全部由字母組成)中找到第一個只出現一次的字符,並返回它的位置, 如果沒有則返回 -1(需要區分大小寫).
思路: hash 表,桶排序。
代碼:

class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        int len = str.length() ;
        if(len <= 0)
            return -1 ;
        int res[100] ;
        for(int i=0; i<100; i++)
            res[i] = 0; 
        for(int i=0; i< len; i++)
        {
            res[str[i] -'A'] ++ ;
        }
        for(int i=0; i< len; i++)
        {
            if(res[str[i] -'A'] == 1)
                return i ;
        }
        return -1;
    }
};

題目14 醜數
把只包含質因子2、3和5的數稱作醜數(Ugly Number)。例如6、8都是醜數,但14不是,因爲它包含質因子7。 習慣上我們把1當做是第一個醜數。求按從小到大的順序的第N個醜數。
思路:最開始拿到這個題目的時候,第一反應是找規律(希望能發現數字間的規律)結果發現沒有規律,不要直接陷入找規律中有些規律可能不是以數字的形式呈現,可以多考慮堆棧隊列等數據結構。
醜數是質因子只有2、3、5的數,也就是說每個醜數是由原來的醜數乘以2、3、5得到的,1乘以2、3、5得到2,3,5 依次乘以2,3,5得到4,6,10 ,6 ,9,15,10,15,25。這樣得到的序列不是有序的且有重複。可以用3個隊列來維護*2,*3,*5的結果。
1)醜數數組: 1
乘以2的隊列:2
乘以3的隊列:3
乘以5的隊列:5
選擇三個隊列頭最小的數2加入醜數數組,同時將該最小的數乘以2,3,5放入三個隊列;
(2)醜數數組:1,2
乘以2的隊列:4
乘以3的隊列:3,6
乘以5的隊列:5,10
選擇三個隊列頭最小的數3加入醜數數組,同時將該最小的數乘以2,3,5放入三個隊列;
(3)醜數數組:1,2,3
乘以2的隊列:4,6
乘以3的隊列:6,9
乘以5的隊列:5,10,15
選擇三個隊列頭裏最小的數4加入醜數數組,同時將該最小的數乘以2,3,5放入三個隊列;
(4)醜數數組:1,2,3,4
乘以2的隊列:6,8
乘以3的隊列:6,9,12
乘以5的隊列:5,10,15,20
選擇三個隊列頭裏最小的數5加入醜數數組,同時將該最小的數乘以2,3,5放入三個隊列;
1.爲什麼分三個隊列?
醜數數組裏的數一定是有序的,因爲我們是從醜數數組裏的數乘以2,3,5選出的最小數,一定比以前未乘以2,3,5大,同時對於三個隊列內部,按先後順序乘以2,3,5分別放入,所以同一個隊列內部也是有序的
2.爲什麼比較三個隊列頭部最小的數放入醜數數組?
因爲三個隊列是有序的,所以取出三個頭中最小的,等同於找到了三個隊列所有數中最小的。
實現思路:
我們沒有必要維護三個隊列,只需要記錄三個指針顯示到達哪一步;

代碼:

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        if (index < 7)
            return index ;
        vector<int> res(index) ;
        res[0] = 1 ;
        int p2=0, p3=0, p5=0 ;
        for(int i=1; i< index; i++)
        {
            res[i] = min(res[p2]*2, min(res[p3]*3, res[p5]*5)) ;
            if(res[p2]*2 == res[i]) p2++ ;
            if(res[p3]*3 == res[i]) p3++ ;
            if(res[p5]*5 == res[i]) p5++ ;
        }
        return res[index -1] ;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章