劍指offer刷題(二)(21-43)題

21 棧的壓入、彈出

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

class Solution {
public:
    bool IsPopOrder(vector<int> p1,vector<int> p2) {
        
        if(p1.empty() || p2.empty() || p1.size()!= p2.size() )return false;
        stack<int> s;
        int index = 0;
        
        for(int i = 0; i < p1.size(); i++){
            s.push(p1[i]);//先把p1數據壓入棧
            
            //如果棧頂元素和p2元素相同則彈出棧頂元素 並使索引自增
            // p1 1 2 3 4 5     p2 4 5 3 2 1 
            // 當壓入4時 此時 index = 0  p2[index] = 4 所以把4彈出棧 index自增到1
            // 5同理
            // 此時棧中的元素爲 1 2 3 p2[2 - 4]元素爲 3 2 1 剛好相反 能把棧中的所有元素彈出
            // 當循環結束 棧爲空 說明是符合彈出序列的
            while(!s.empty() && s.top() == p2[index]){
                index++;
                s.pop();
            }
        }
        
        return s.empty();
    }
};

22 從上往下打印二叉樹

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

/*
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> res;
        if(!root) return res;
        queue<TreeNode*> q;
        q.push(root);//先把根結點入隊
        while(q.size()){//當隊列不爲空時
            auto cur = q.front();//取出隊首元素
            res.push_back(cur->val);//存儲隊首元素
            q.pop();//隊首元素出隊
            if(cur->left)q.push(cur->left);//如果當前結點有左孩子,左孩子入隊
            if(cur->right)q.push(cur->right);//如果當前結點有右孩子,右孩子入隊
        }
        return res;
    }
};

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

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

/*
BST的後序序列的合法序列是,對於一個序列S,最後一個元素是x (也就是根),
如果去掉最後一個元素的序列爲T,那麼T滿足:T可以分成兩段,
前一段(左子樹)小於x,後一段(右子樹)大於x,
且這兩段(子樹)都是合法的後序序列。
*/

class Solution {
public:
    bool VerifySquenceOfBST(vector<int> seq) {
        if(seq.size() == 0) return false;
        return dfs(seq, 0, seq.size() - 1);
    }
    bool dfs(vector<int> &seq, int s, int e){
        if(s >= e)return true;
        // r 是 最後一個元素 第一步 可以看作root
        int r = seq[e], l = s;
        //取得左子樹所有的數
        while(seq[l] < r) l++;
        // 如果右子樹中 有比root小的 肯定不是BST 直接返回false
        for(int i = l; i < e; i++)if(r > seq[i])return false;
        
        //遞歸搜索root的左子樹和右子樹 要兩個子樹都滿足 左子樹 < root 和 右子樹 > root
        return dfs(seq, s, l - 1) && dfs(seq, l, e - 1);
    }
};

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

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

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};
這題就是dfs
取一個數組來記錄路徑
每次先向左子樹找 假如當前左子樹的結點的值小於餘下的sum值
就用sum減去當前結點的值 然後繼續往左子樹遞歸 如果左子樹不滿足 就向右子樹
當減去左子樹或右子樹的結點的值後 不滿足 回溯 刪除路徑數組中的最後一個結點
*/
class Solution {
public:
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        vector<vector<int>> res; 
        vector<int> path;
        dfs(root, expectNumber, res, path);
        return res;
    }
    void dfs(TreeNode *root, int sum, vector<vector<int>> &res, vector<int> &path) {

        if(!root) return;//如果當前結點爲空 則退出遞歸
        path.push_back(root->val);//加入路徑數組
        sum = sum - root->val;//和減去當前結點的值
        if(!root->left && !root->right && !sum) res.push_back(path);//sum爲0並且左右子樹都爲空,就是找到一條path
        if(root->left)  dfs(root->left, sum, res, path);//向左子樹遞歸
        if(root->right) dfs(root->right, sum, res, path);//向右子樹遞歸
        path.pop_back();//回溯法  一定要恢復現場!

    }

};

25 複雜鏈表的複製

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

(1)每個結點複製一個與其相同的結點接到其屁股後面

(2)每個複製結點的random指向原來結點指向的下一個結點,現在算是全部複製,但是有兩個同樣的
(3)所以第三步就是分離原來的結點和複製的結點,不用考慮random,只需把所有clone結點串起來

/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
這題有難度 不過也有規律
分三個步驟:
(1)每個結點複製一個與其相同的結點接到其屁股後面
(2)每個複製結點的random指向原來結點指向的下一個結點,現在算是全部複製,但是有兩個同樣的
(3)所以第三步就是分離原來的結點和複製的結點,不用考慮random,只需把所有clone結點串起來
*/
class Solution {
public:
    // 先不管random 每個結點複製和自己結點一樣的放到自己後面
    RandomListNode *CloneNodes(RandomListNode *pHead)
    {
        auto node = pHead;
        while (node){
            auto tempNode = new RandomListNode(node->label);
            tempNode->next = node->next;
            node->next = tempNode;
            node = tempNode->next;
        }
        return pHead;
    }
    //  每個clone結點的random指向原先指向的後一個結點
    RandomListNode *ConnectRandomNodes(RandomListNode *pHead)
    {
        auto node = pHead;
        while (node){
            auto tempNode = node->next;
            if(node->random)tempNode->random = node->random->next;
            node = tempNode->next;
        }
        return pHead;
    }
    // 分離結點,cloneHead取pHead->next直到結束
    RandomListNode *ReConnectRandomNodes(RandomListNode *pHead)
    {
        auto node = pHead;
        RandomListNode *cloneHead = nullptr;
        RandomListNode *cloneNode = nullptr;
        if(node){
            cloneHead = cloneNode = node->next;
            node->next = cloneNode->next;
            node = node->next;
        }
        while (node){
            cloneNode->next = node->next;
            cloneNode = cloneNode->next;
            node->next = cloneNode->next;
            node = node->next;
        }
        return cloneHead;
    }
    RandomListNode* Clone(RandomListNode* pHead)
    {
        return ReConnectRandomNodes(ConnectRandomNodes(CloneNodes(pHead)));
    }
};

26 二叉搜索樹與雙向鏈表

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

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
/*利用平衡二叉樹中序遍歷有序的特點,例如:

           10
          /  \
         8   14

維護一個last變量,存儲當前雙向鏈表的最後一個節點。當中序遍歷到10時,last應該指向8。

將10的left指針指向8,如果8不爲NULL,將8的right指針指向10,然後last指向10。
*/
class Solution {
public:
    TreeNode* last = NULL;
    TreeNode* Convert(TreeNode* r)
    {
        dfs(r);
        
        auto head = last;
        while(head && head->left)head = head->left;
        return head;
    }
    void dfs(TreeNode* r){
        if(!r)return;
        auto cur = r;
        if(cur->left)Convert(cur->left);
        
        cur->left = last;
        if(last) last->right = cur;
        last = cur;
        
        if(cur->right)Convert(cur->right);
    }
};

27 字符串的排列

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

/*
經典的dfs 入門 不知道怎麼說
在回溯時 要麼把tmp最後一個字符彈出 要麼把字符交換回來 
*/

class Solution {
public:
    vector<string> Permutation(string str) {
        vector<string> res;
        if(str.size() == 0)return res;
        string tmp;
        dfs(str, tmp, res, 0);
        return res;
    }
    void dfs(string str, string &tmp, vector<string> &res, int start){
        if(start < 0 || str.size() == 0)return;
        if(str.size() == start){
            res.push_back(tmp);
            return;
        }
        for(int i = start; i < str.size(); i++){
            if( i != start && str[i] == str[start])continue;//如果字符相同,不用交換
            swap(str[i],str[start]);
            tmp += str[start];
            dfs(str,tmp,res,start + 1);
            tmp.pop_back();//回溯法的關鍵,恢復上一個現場
        }
    }
    
};

看不明白的 可以看這個高分答案,Java寫的:

import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
public class Solution {
    public ArrayList<String> Permutation(String str) {
        List<String> resultList = new ArrayList<>();
        if(str.length() == 0)
            return (ArrayList)resultList;
        //遞歸的初始值爲(str數組,空的list,初始下標0)
        fun(str.toCharArray(),resultList,0);
        Collections.sort(resultList);
        return (ArrayList)resultList;
    }
     
    private void fun(char[] ch,List<String> list,int i){
        //這是遞歸的終止條件,就是i下標已經移到char數組的末尾的時候,考慮添加這一組字符串進入結果集中
        if(i == ch.length-1){
            //判斷一下是否重複
            if(!list.contains(new String(ch))){
                list.add(new String(ch));
                return;
            }
        }else{
            //這一段就是回溯法,這裏以"abc"爲例
             
            //遞歸的思想與棧的入棧和出棧是一樣的,某一個狀態遇到return結束了之後,會回到被調用的地方繼續執行
             
            //1.第一次進到這裏是ch=['a','b','c'],list=[],i=0,我稱爲 狀態A ,即初始狀態
            //那麼j=0,swap(ch,0,0),就是['a','b','c'],進入遞歸,自己調自己,只是i爲1,交換(0,0)位置之後的狀態我稱爲 狀態B 
            //i不等於2,來到這裏,j=1,執行第一個swap(ch,1,1),這個狀態我稱爲 狀態C1 ,再進入fun函數,此時標記爲T1,i爲2,那麼這時就進入上一個if,將"abc"放進list中
            /////////////-------》此時結果集爲["abc"]
             
            //2.執行完list.add之後,遇到return,回退到T1處,接下來執行第二個swap(ch,1,1),狀態C1又恢復爲狀態B
            //恢復完之後,繼續執行for循環,此時j=2,那麼swap(ch,1,2),得到"acb",這個狀態我稱爲C2,然後執行fun,此時標記爲T2,發現i+1=2,所以也被添加進結果集,此時return回退到T2處往下執行
            /////////////-------》此時結果集爲["abc","acb"]
            //然後執行第二個swap(ch,1,2),狀態C2迴歸狀態B,然後狀態B的for循環退出回到狀態A
             
            //             a|b|c(狀態A)
            //               |
            //               |swap(0,0)
            //               |
            //             a|b|c(狀態B)
            //             /  \
            //   swap(1,1)/    \swap(1,2)  (狀態C1和狀態C2)
            //           /      \
            //         a|b|c   a|c|b
             
            //3.回到狀態A之後,繼續for循環,j=1,即swap(ch,0,1),即"bac",這個狀態可以再次叫做狀態A,下面的步驟同上
            /////////////-------》此時結果集爲["abc","acb","bac","bca"]
             
            //             a|b|c(狀態A)
            //               |
            //               |swap(0,1)
            //               |
            //             b|a|c(狀態B)
            //             /  \
            //   swap(1,1)/    \swap(1,2)  (狀態C1和狀態C2)
            //           /      \
            //         b|a|c   b|c|a
             
            //4.再繼續for循環,j=2,即swap(ch,0,2),即"cab",這個狀態可以再次叫做狀態A,下面的步驟同上
            /////////////-------》此時結果集爲["abc","acb","bac","bca","cab","cba"]
             
            //             a|b|c(狀態A)
            //               |
            //               |swap(0,2)
            //               |
            //             c|b|a(狀態B)
            //             /  \
            //   swap(1,1)/    \swap(1,2)  (狀態C1和狀態C2)
            //           /      \
            //         c|b|a   c|a|b
             
            //5.最後退出for循環,結束。
             
            for(int j=i;j<ch.length;j++){
                swap(ch,i,j);
                fun(ch,list,i+1);
                swap(ch,i,j);
            }
        }
    }
     
    //交換數組的兩個下標的元素
    private void swap(char[] str, int i, int j) {
            if (i != j) {
                char t = str[i];
                str[i] = str[j];
                str[j] = t;
            }
        }
    }
}

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

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

/*
這題我有兩種思路
1、空間換時間
題目沒有申請內存限制 可以申請一個map key存儲數 value存儲該數出現的字數
對數組進行遍歷 如果當前數不在keys裏面 直接插入 並使value爲1 後面如果遇到相同的 value++
如果value>=size/2 直接返回該數
時間複雜度O(n) 空間複雜度O(n)

2、時間換空間
先對數組進行排序
如果當前數 相同數的區間
如果區間大於5 直接返回該數
由於要排序 所以 時間複雜度爲O(nlogn) 
不消耗空間 所以空間複雜度爲O(1)

我只寫了第一種
*/

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        if(numbers.size() == 1)return numbers[0];
        map<int,int> m;
        for(int i = 0; i<numbers.size(); i++){
            if(m.count(numbers[i])){
                if(m[numbers[i]] >= numbers.size()/2)
                    return numbers[i];
                else m[numbers[i]]++;
            }else{
                m.insert({numbers[i],1});
            }
        }
        return 0;
    }
};

29 最小的K個數

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

/*
遇到最小的k個數 或者最大的k個數
首先要想到優先隊列
最小的k個數 --->>> 最大優先隊列 --->>> 大頂堆
最大的k個數 --->>> 最小優先隊列 --->>> 小頂堆
所以本題 只需維護一個長爲k的 最大優先隊列
這就保證了當有新的數據入隊後 只會優先彈出隊列中最大的元素
*/
class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> res;
        if(k>input.size())return res;
        
        priority_queue<int> q; 
        for(auto x : input){
            q.push(x);
            if(q.size() > k)//只保留最小的k個數
                q.pop();
        }
        // 這k個數放到vector中
        while(!q.empty()){
            res.push_back(q.top());
            q.pop();
        }
        return res;
    }
};

30 連續子數組的最大和

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

/*
6,-3,-2,7,-15,1,2,2
給一個sum 表示最大和 一個max存儲最大的sum
如果加sum + 當前數 <= 0 那麼 sum 賦值當前數
否則 sum += 當前數
如果sum > max 
max = sum
*/
class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> a) {
        int n = a.size(), max = INT32_MIN, sum = 0;
        if(n == 0) return 0;
        for(int i = 0; i < n; i++){
            if(sum <= 0)sum = a[i];
            else sum += a[i];
            if(max < sum)
                max = sum;
        }
        return max;
    }
};

31 整數中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的個數
怎麼計算一個數的1?
首先先計算個位
然後右移一位
直到當前數變爲0
*/

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n)
    {
        int num = 0;
        for(int i = 1; i <= n; i++)
            num += count(i);
        return num;
    }
    int count(int n){
        int res = 0;
        while(n){
            if(n % 10 == 1)
                res ++;
            n /= 10;
        }
        return res;
    }
};

32 把數組排成最小的數

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

/*
想一下
這個相當於按兩個數組合在一起 
能組合成比較小的數就是我們要想的組合
那麼可以對兩兩進行比較
取最小的組合
並調用排序算法對數組按照組合最小的排序
排序後的數組
把所有元素合併起來的結果就是題設結果
*/

class Solution {
public:
    static bool myCompare(int &a, int &b){
        if(to_string(a) + to_string(b) < to_string(b) + to_string(a))
            return true;
        else
            return false;
    }
    string PrintMinNumber(vector<int> numbers) {
        string str;
        if(!numbers.size())return str;
        sort(numbers.begin(),numbers.end(),myCompare);
        for(int i = 0; i < numbers.size(); i++){
            str += to_string(numbers[i]);
        }
        return str;
    }
};

33 醜數

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

/*
醜數只包含 2 3 5 三個質因子
那麼說明 醜數S = i*2 + j*3 + k*5
所以我們維護 i j k三個變量
因爲醜數是連續的 因此要保證下一個醜數和前一個醜數之間 不能含有醜數
於是在找下一個數時 分別要找 i j k對應的前一個醜數 
並取他們分別乘下一個質因子最小的數 這個數就是下一個醜數
*/
class Solution {
public:
    int GetUglyNumber_Solution(int n) {
        if(n <= 1)return n;
        vector<int> v(1,1);
        int i = 0, j = 0, k = 0;
        long long t = 0;
        while(--n){
            t = min(v[i] * 2, min(v[j] * 3, v[k] * 5));
            if(t == v[i] * 2) i++;
            if(t == v[j] * 3) j++;
            if(t == v[k] * 5) k++;
            v.push_back(t);
        }
        return v.back();
    }
};

34  第一個只出現一次的字符

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

/*
看到出現幾次的字符 要想到 int chars[256] 或者 map
如果是用chars[256] 那麼初始chars[256] = {0}
以後遇到每個字符 往對應的ASCII碼 - 'A' ++ 到最後遍歷 每個元素爲1 就直接返回
使用map思路也一樣一樣的 如果map已經有了就++ 沒有插入 最後遍歷一樣
*/
class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        map<char,int> m;
        for(int i = 0;i<str.size();i++){
            if(m.count(str[i]))m[str[i]]++;
            else m.insert({str[i],1});
        }
        for(int i = 0;i<str.size();i++){
            if(m[str[i]] == 1)return i;
        }
        return -1;
    }
};

35 數組中的逆序隊

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

/**
本題就是考個歸併排序 然後遞歸結束後合併數組時
如果左半邊的比右半邊中的數大 那麼左半邊所有的數和右半邊這個數都構成逆序對
下面簡單模擬一下歸併排序 關於排序 後面會更新一個詳細的 
1,2,3,4,5,6,7,0
1,2,3,4        5,6,7,0 
1,2  3,4       5,6   7,0
1 2  3 4       5 6   7 0       ans: 1
12   34        56    07        ans: 2
1234           0567            ans: 4
01234567   ans: 1 + 2 + 4 = 7
由於可能數據會很大 int32 可能會溢出 所以ans 以 long long 型
*/
class Solution {
public:
    static const int N = 1e6 + 50;
    long long ans;
    int a[N], tmp[N];//大內存申請 最好放在全局變量 由堆開闢空間
    void mergeSort(int q[], int l, int r)
    {
        if (l >= r) return;

        int mid = (l + r) >> 1;

        mergeSort(q, l, mid), mergeSort(q, mid + 1, r);

        int k = 0, i = l, j = mid + 1;
        
        while (i <= mid && j <= r)
            if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];//前一個數比後一個數小
            //後一個數比前一個數大 把前面的數對 都加到ans
            else tmp[k ++ ] = q[j ++ ],ans += mid - i + 1;
        //合併比較長的數組的那一部分
        while (i <= mid) tmp[k ++ ] = q[i ++ ];
        while (j <= r) tmp[k ++ ] = q[j ++ ];

        //把數據放到temp數組中
        for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
    }

    int InversePairs(vector<int> nums) {
        for(int i=0;i<nums.size();i++)
            a[i] = nums[i];
        mergeSort(a, 0,nums.size() - 1);
        return ans%1000000007;
    }
};

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

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

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};
兩個鏈表的公共結點 其實就是兩個鏈表的尾部一定數據相同
那麼可以用兩個數組分別存儲兩個鏈表的數據
然後一起從後往前遍歷 當兩個結點不相同時 返回上一個結點 就是第一個公共結點

舉例:
1 3 5 6 7 8
  2 4 3 7 8
epoch 1: 
1 3 5 6 7
  2 4 3 7
epoch 2: 
1 3 5 6
  2 4 3
epoch 3: 
break


*/
class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        if(!pHead1 || !pHead2)return 0;
        vector<ListNode*> v1,v2;
        auto h1 = pHead1, h2 = pHead2;
        //把兩個鏈表數據分別裝入v1 v2
        while(h1){
            v1.push_back(h1);
            h1 = h1->next;
        }
        while(h2){
            v2.push_back(h2);
            h2 = h2->next;
        }
        // 分別取到末尾
        int h1len = v1.size() - 1, h2len = v2.size() - 1;
        ListNode* res = 0; //結果指針
        //從後往前遍歷
        while(h1len>=0 && h2len>=0 && v1[h1len] == v2[h2len]){
            res = v1[h1len];
            h1len--;
            h2len--;
        }
        return res;
    }
};

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

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

/*
統計一個數字在排序數組中出現的次數
首先要找到這個數
那麼從這個數往後遍歷
累計有幾個這個的數 就是結果
查找這個數用二分查找 時間複雜度可以表示O(h)=O(log2n)
一定要找這個數第一次出現的位置 所以在判斷時要>=k

舉例

11233367  找3  epoch1: l = 0 , r = 7 mid = 3
1123 epoch2: l = 0 , r = 3 mid = 1
23 epoch3: l = 2 , r = 3 mid = 2
3 epoch4: l = 3 , r = 3 mid = 3
3 epoch5: break l = 3 , r = 3
return l = 3
*/

class Solution {
public:
    int getFirstPos(vector<int> &data ,int k, int l,int r){
        while(l<r){
            int mid = (l + r)>>1;
            if(data[mid]>=k)r = mid;
            else l = mid + 1;
        }
        return l;
    }
    int GetNumberOfK(vector<int> data ,int k) {
        int n = data.size();
        if(!n)return 0;
        
        int pos = getFirstPos(data,k,0,n - 1);
        int res = 0;
        for(int i = pos; i < n; i++){
            if(k == data[i])res++;
            else break;
        }
        return res;
    }
};

38 二叉樹的深度

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

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};
輸入一棵二叉樹,求該樹的深度
其實就是dfs
每往下遞歸一層 深度++
遞歸結束 更新Depth

        1
    2        3
  4    5  6    7
 8 
 中間重複的 或者包含了的 不表示了
 epoch1:  1 2 4 7   depth = 4
 epoch2:  1 2 4   depth = 3
 epoch3:  1 2 5   depth = 3
 epoch4:  1 3 6   depth = 3
 epoch4:  1 3 7   depth = 3
 所以 4 最大 返回4
*/
class Solution {
private:
    int depth = 0;
public:
    void dfs(TreeNode* r, int d){
        if(r->left) dfs(r->left, d + 1);
        if(r->right) dfs(r->right, d + 1);
        depth = max(depth, d);
    }
    int TreeDepth(TreeNode* r)
    {
        if(!r)return 0;
        dfs(r,1);
        return depth;
    }
};

39 平衡二叉樹

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

/**
平衡二叉樹 左右子樹深度差的絕對值不超過1
如果改爲從下往上遍歷 如果子樹是平衡二叉樹
則返回子樹的高度 如果發現子樹不是平衡二叉樹
則直接停止遍歷 這樣至多隻對每個結點訪問一次
        1
    2        3
  4    5  6    7
 8 
 中間重複的 或者包含了的 不表示了
 epoch1:  1 2 3 7  結點8 return : 1 l
 epoch2:  1 2 3    結點4 return : 2 l
 epoch3:  1 2 4    結點5 return : 1 l
 epoch4:  1 2      結點2 return : 3 l
 epoch5:  1 3 5    結點6 return : 1 r
 epoch6:  1 3 6    結點7 return : 1 r
 epoch7:  1 3      結點3 return : 2 r
 epoch8:  1        結點1 return : 4 l 3 r
**/

class Solution {
public:
    
    bool IsBalanced_Solution(TreeNode* r) {
       return dfs(r) != -1;
    }
    int dfs(TreeNode* root){
        if(!root)return 0;
        int l = dfs(root->left);
        if(l<0)return -1;
        int r = dfs(root->right);
        if(r<0)return -1;
        if(abs(l-r)>1)return -1;
        return max(l,r) + 1;
    }
};

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

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

/*
又是找數  而且沒有內存申請限制 好 上map
還是原來的 key代表數 value代表次數
遍歷一次
然後遍歷map 看哪個數的value爲1
就存入指針就ok了
*/
class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        map<int,int> m;
        for(auto x : data){
            if(m.count(x))m[x]++;
            else m.insert({x,1});
        }
        int flag = 0;
        for(auto x: data){
            if(m[x] == 1){
                if(!flag){
                    num1[0] = x;
                    flag = 1;
                }
                else{
                    num2[0] = x;
                    break;
                } 
            }
        }
    }
};

41 和爲S的連續正數序列

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

/*
首先看看有沒有內存申請限制和複雜度限制
沒有 那麼可以試試O(n2)
對於這題目 不管用單指針往前找 還是雙指針往兩邊找 也是O(n2)
那麼直接試試
因爲題目說至少包括兩個數
那麼循環可以從1~n/2
每個數都自增 加入原來的數 直到超過sum
如果相等 保存序列
*/
class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        vector<vector<int>> res;
	    for(int i = 1; i <= sum/2; i++){
		    vector<int> v = get(i,sum);
		    if(v.size())
			    res.push_back(v);
	    }
	    return res;
    }
    vector<int> get(int n, int sum){
        vector<int> res,v;
        int t = 0;
        while(t < sum){
            t += n;
            res.push_back(n++);
        }
        if(t != sum) return v;
        else return res;
        
    }
};
/*
這是我之間用雙指針滑動窗口寫的 沒有優化 所以寫得比較亂
*/
import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer>> lists = new ArrayList<>();
        int size = sum/2 + 1;
        int aa[] = new int[size];
        int temp = 0;
        for(int i =0;i<size;i++){
            temp = i+1;
            aa[i] = temp;
        }
        for (int item = 1;item<size;item++){
            ArrayList<Integer> list1 = new ArrayList<>();
            int cur_index = item - 1;
            int max_index = cur_index,min_index = cur_index == 0?0:cur_index-1;
            int t_sum = 0;
            while (max_index<aa.length&&t_sum<sum){
                t_sum += aa[max_index];
                if(t_sum == sum){
                    for(int j = aa[cur_index];j<=aa[max_index];j++)list1.add(j);
                    if(!isContains(lists,list1)&&list1.size()>1)lists.add(list1);
                }
                max_index++;
            }
            max_index --;
            t_sum -=aa[max_index];
            max_index--;
            while (cur_index!=max_index&&t_sum<sum&&min_index>0){
                t_sum += aa[min_index];
                if(t_sum == sum){
                    for(int j = aa[min_index];j<=aa[max_index];j++)list1.add(j);
                    if(!isContains(lists,list1)&&list1.size()>1)lists.add(list1);
                }
                if(t_sum>sum){
                    t_sum -= aa[max_index];
                    max_index --;
                }
                min_index --;
            }
        }
        return lists;
    }
    public boolean isContains(ArrayList<ArrayList<Integer>> lists,ArrayList<Integer> list){
        for (ArrayList<Integer> a:lists) 
            if(a.get(0) == list.get(0))return true;
        return false;
    }
}

42 和爲S的兩個數

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

/*
看到遞增 看到查找 看到兩個數的和
一定想到雙指針 一個指向0 一個指向末尾
從兩頭往中間縮小
保存乘積最小的兩個數的索引
*/

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> a,int sum) {
        vector<int> res;
        if(a.size() == 0)return res;
        int l = 0, r = a.size() - 1, t = 0;
        int il = r, ir = r;
        while(l<r){
            t = a[l] + a[r];
            if(t < sum) l++;
            else if(t > sum)r--;
            else{
                if(l * r < il * ir){
                    il = l;
                    ir = r;
                }
                l ++;
                r --;
            }
        }
        if(il != ir){
            res.push_back(a[il]);
            res.push_back(a[ir]);
        }
        return res;
    }
};

43 左旋轉字符串

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

/*
這題 要麼用substr 
不過 爲了模擬左移
申請個隊列
先把字符串放進隊列
然後前n個字符出隊
出隊後立即入隊
最後把隊列中的所有字符裝入string 變量
*/
class Solution {
public:
    string LeftRotateString(string str, int n) {
        queue<char> q;
        for(auto c : str)
            q.push(c);
        while(n--){
            char c = q.front();
            q.pop();
            q.push(c);
        }
        string res;
        while(q.size()){
            res.push_back(q.front());
            q.pop();
        }
        return res;
    }
};

 

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