劍指offer(五)C++版

1.棧的壓入、彈出序列

  輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否可能爲該棧的彈出順序。假設壓入棧的所有數字均不相等。例如序列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) {
        std::stack<int> st;
         
        int index = 0;
        int outdex = 0;
         
        while(index < pushV.size())//元素是否全部入棧
        {
            if(st.empty() || st.top() != popV[outdex])
            {
                st.push(pushV[index]);
                ++index;
            }
            else{//元素相同就出棧
                st.pop();
                ++outdex;
            }
        }
        while(!st.empty())
        {
            if(st.top() != popV[outdex++])
            {
                return false;
            }
            st.pop();
        }
        return true;
    }
};
2.從上往下打印二叉樹

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

這道題也就是二叉樹的層序遍歷,使用具體例子分析,可以得到解決它的方法。每次打印一個結點時,若該結點有子結點則將其放到有個隊列的末尾,然後從隊列的頭部取出最先進入隊列的結點,重複之前操作,直至隊列爲空。
無論是廣度優先遍歷一個有向圖還是一棵樹,都需要用到隊列。首先把起始結點(根結點)放入隊列中;然後每一次從隊列的頭部取出一個結點,遍歷這個結點之後,把它能到達的結點都依次放入隊列。重複這個過程,直到隊列中的結點全部被遍歷完。

/*
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) {
        queue<TreeNode*> queuenode;
        vector<int> res;
        if (root == nullptr)
            return res;
 
        queuenode.push(root);
        while (queuenode.size())
        {
            res.push_back(queuenode.front()->val);
            if (queuenode.front()->left)
            {
                queuenode.push(queuenode.front()->left);
            }
            if (queuenode.front()->right)
            {
                queuenode.push(queuenode.front()->right);
            }
 
            queuenode.pop();
        }
 
        return res;
    }
};
3.二叉搜索樹的後序遍歷序列

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

舉個例子:如[5,7,6,9,11,10,8]返回true,首先明確後序遍歷數組的結構特點:後序遍歷的結構是LRV即最後一個數字是根結點,對於二叉搜索樹來說,數組剩下的元素可以分爲兩部分:第一部分是左子樹,比根結點的值小;第二部分是右子樹,比根結點的值大。針對這種特點,利用遞歸確定與數組每一部分對應的子樹的結構

class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        //判斷sequence是是否爲空
        if (sequence.size() == 0)
        {
            return false;
        }
 
        vector<int> aleft;
        vector<int> aright;
 
        int root = sequence[sequence.size() - 1];
         
        //二叉搜索樹的左子樹的節點小於根節點
        int pos = 0;
        for (; pos < sequence.size() - 1; pos++)
        {
            if (sequence[pos] > root)
                break;
            aleft.push_back(sequence[pos]);
        }
 
        int i = pos;
        for (; i < sequence.size() - 1; i++)
        {
            if (sequence[i] < root)
                return false;
            aright.push_back(sequence[i]);
        }
 
        //判斷左子樹
        bool left = true;
        if (pos > 0)
            left = VerifySquenceOfBST(aleft);
 
        bool right = true;
        if (pos < sequence.size() - 1)
            right = VerifySquenceOfBST(aright);
 
 
        return (left && right);
    }
};
4.二叉樹中和爲某一值的路徑

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

針對這種問題具體分析,畫出一個二叉樹查看其規律。題目有兩個要求:一是對路徑的定義爲從樹的根結點到葉節點,即路徑總是以根結點爲起始點,因此需要首先遍歷根結點,因此選擇樹的前序遍歷來訪問結點。
此題中二叉樹的結構中無指向父結點的指針,因此需要我們保存經過的路徑上的結點,每訪問一個結點時,我們就把當前的結點添加到路徑中去,若到達某個葉結點時當前值並不等於目標值,此時需要刪除路徑中的結點,即每一次當從子結點返回到父結點時,都需要考慮在路徑上刪除子結點。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
  void The_Path(TreeNode* root, int expectNumber, vector<vector<int> >& path, int& currentNumber,
      vector<int>& tmp)
  {
      currentNumber += root->val;
      tmp.push_back(root->val);//根結點入

      bool is_leaf = root->left == nullptr && root->right == nullptr;//是否是葉子結點
      if (currentNumber == expectNumber && is_leaf)//滿足要求兩個要求即可退出
      {
          path.push_back(tmp);
      }

      if (root->left != nullptr)
      {
          The_Path(root->left, expectNumber, path, currentNumber, tmp);
      }
      if (root->right != nullptr)
      {
          The_Path(root->right, expectNumber, path, currentNumber, tmp);
      }

      currentNumber -= root->val;//從子結點返回到父結點,路徑上刪除此結點
      tmp.pop_back();
  }

  vector<vector<int> > FindPath(TreeNode* root, int expectNumber) {
      vector<vector<int> > res;
      vector<int> path;
      if (root == nullptr)
          return res;

      int currentNumber = 0;
      The_Path(root, expectNumber, res, currentNumber, path);
         
  }
};

上面解法中的遞歸調用的本質就是一個壓棧和入棧的過程,所以可以利用一個輔助棧模擬這個過程,首先從根結點開始遍歷,優先將每個結點的左結點壓入棧中,直到到達最左邊的葉節點,與此同時將expectNumber減去但當前結點的值,所以最後的判別標準就是expectNumber是否爲0;然後獲取到最左邊的結點,進行條件判斷,若滿足條件則將路徑保存;其次需要考慮的就是上步的從子結點返回到父結點這一步了,出棧後,判斷此時棧頂元素的右結點是否遍歷過。

class Solution {
public:
    vector<vector<int> > FindPath(TreeNode* root, int expectNumber) {
        stack<TreeNode*> s;
        vector<int> v;
        vector<vector<int> > res;
 
        while (root || !s.empty())
        {
            while (root)
            {
                s.push(root);
                v.push_back(root->val);
                expectNumber -= root->val;
                //能左就左,否則向右
                root = root->left ? root->left : root->right;
            }
 
            root = s.top();
            if (expectNumber == 0 && root->left == NULL && root->right == NULL)
                res.push_back(v);
 
            s.pop();
            v.pop_back();
            expectNumber += root->val;
            //右子數沒遍歷就遍歷,如果遍歷就強迫出棧
            if (!s.empty() && s.top()->left == root)
                root = s.top()->right;
            else
                root = NULL;//強迫出棧
        }
        return res;
    }
};
5.複雜鏈表的複製

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

  首先明確複雜鏈表的組成中,除了有指向下一個結點的指針,還有指向任意結點的指針。
  要想完不成這個複雜鏈表的複製,需要考慮兩個因素:一是原始鏈表的每一個結點用next指針鏈接起來;二是每個結點的隨機指針random。當我們完成第一步得到一個新的鏈表後,考慮第二步時,隨機指針指向的元素可能在當前結點的前面,也可能在後面,所以需要從原始鏈表的頭結點開始找,記錄下從頭結點到當前結點的隨機指針指向的結點所走過的步數count,然後在新鏈表中也是沿着頭結點開始走count步即可。這樣對於一個含有n個結點的鏈表,定位每個結點的random都需要從頭結點開始經過O(n)在,這種方法的時間複雜度就是O(n2)。
  針對上述方法的第二步進行優化,採用空間換取時間的做法。第一步複製原始鏈表的每個結點S創建D,然後將創建出來的結點用next指針連接起來,同時將<S,D>的配對信息保存到一個哈希表中,第二步複製每個結點的random,通過哈希表查找這種對應關係的時間就是O(1),,對於n個結點的鏈表來說,所用的時間複雜度就是O(n)。
  那麼在沒有輔助空間的情況下實現O(n)的時間效率。第一步仍然是根據原始結點S創建對應的D,同時將創建好的D連接在S的後面;第二步設置複製出來的random,即若原始鏈表中的S1的random指向S3,那麼複製出來的D1的random同樣指向D3;第三步就是將這個長鏈表拆分成兩個鏈表:將奇數位置的結點用next鏈接起來就是原始鏈表;將偶數位置的結點用next鏈接起來就是複製出來的鏈表。

在這裏插入圖片描述

/*
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) 
			return NULL;
		
		RandomListNode* currNode;
		currNode = pHead;
		
		//創建新結點並放到其後
		while (currNode) 
		{
			RandomListNode *node = new RandomListNode(currNode->label);
			node->next = currNode->next;
			currNode->next = node;
			currNode = node->next;
		}

		//確定新插入節點的random
		currNode = pHead;
		while (currNode) 
		{
			RandomListNode *node = currNode->next;
			if (currNode->random) {
				node->random = currNode->random->next;
			}
			currNode = node->next;
		}

		//分拆長鏈表
		RandomListNode *pCloneHead = pHead->next;//複製鏈表
		RandomListNode *tmp;
		currNode = pHead;
		
		while (currNode->next) 
		{//一個一個的接
			tmp = currNode->next;
			currNode->next = tmp->next;
			currNode = tmp;
		}

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