《剑指offer》刷题打卡第2天

面试题3:输入一个链表的头节点,从尾到头反过来打印出每一个节点的值。
链表节点定义如下:

//单向链表的节点定义如下:
struct ListNode
{
  int m_nValue;//链表长度
  ListNode* m_pNext;//链表节点
}

基础知识:
什么是链表?
它由指针把若干个节点连接成链状结构。链表创建、插入节点、删除节点等基本操作代码量少,适合面试。
链表是一种动态的数据结构,因为在创建链表时,无须知道链表的长度。当插入一个节点时,我们只需要为新节点分配内存,然后调整指针的指向来确保新节点被链接到链表当中。内存分配不是在创建链表时一次性完成的,而是每添加一个节点分配一次内存。由于没有闲置的内存,链表的空间效率比数组高。

1、在链表的末尾添加一个节点

//往该链表的末尾添加一个节点
void AddToTail(ListNode** pHead,int value)
{
  ListNode* pNew = new ListNode();//生成新的节点
  pNew->m_nValue = value;
  pNew->m_pNext = nullptr;
  
  if(*pHead == nullptr)
  {
   *pHead = pNew;
  }
  else
  {
   ListNode* pNode = *pHead;
   
   while(pNode->m_pNext != nullptr)
   pNode = pNode->m_pNext;
   
   pNode->m_pNext = pNew;
  }
}

2、寻找目标节点并删除

//寻找目标节点并删除
void RemoveNode(ListNode** pHead,int value)
{
 if(pHead == nullptr || *pHead == nullptr)
   return;
   
   ListNode* pToBeDeleted = nullptr;
   if((*pHead)->m_nValue == value)
   {
     pToBeDeleted = *pHead;
	 *pHead = (*pHead)->m_pNext;
   }
   else
   {
     ListNode* pNode = *pHead;
	 while(pNode->m_pNext != nullptr 
	 && pNode->m_pNext->m_nValue != value)
	 pNode = pNode->m_pNext;
	 
	 if(pNode->m_pNext != nullptr && pNode->m_pNext->m_nValue == value)
	 {
	  pToBeDeleted = pNode->m_pNext;
	  pNode->m_pNext=pNode->m_pNext->m_pNext;
	 }
   }
   if(pToBeDeleted != nullptr)
   {
     delete pToBeDeleted;
	 pToBeDeleted = nullptr;
   }
}

算法思路
从头到尾输出比较简单吗?
打印是一个只读的过程,不希望修改内容,假设面试官要求不能改变链表的结构。
接下来要解决这个问题:
首先遍历链表,顺序是从头到尾,可输出的顺序是从尾到头。
“后进先出”,即第一个遍历的节点最后一个输出,而最后一个遍历的节点第一个输出。
可以用栈来实现这种顺序
每经过一个节点时,把该节点放到一个栈中。当遍历完整个链表后,再从栈顶开始逐个输出节点的值,此时输出的节点顺序已经反转过来了。

实现代码:

/**
*  struct ListNode {
*        int val;
*        struct ListNode *next;
*        ListNode(int x) :
*              val(x), next(NULL) {
*        }
*  };
*/

class Solution {
public:
  vector<int> printListFromTailToHead(ListNode* head) {        
  std::stack<int> nodes;
  vector<int> result;
  
  ListNode* pNode = head;
  
  while(pNode != nullptr)
  {
   nodes.push(pNode->val);
   pNode = pNode->next;
  }
  
  while(!nodes.empty())
  { 
      result.push_back(nodes.top());
             nodes.pop();
  } 
     return   result ;
    }
};

面试题4:重建二叉树
题目:输入某二叉树的前序和中序遍历的结果,请重建二叉树。假设输入的前序和中序遍历都不含重复数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

二叉树节点的定义:

struct BinaryTreeNode 
{
    int                    m_nValue; 
    BinaryTreeNode*        m_pLeft;  
    BinaryTreeNode*        m_pRight; 
};

基础知识:
树:除了根节点外,每个节点只有一个父节点,根节点没有父节点;除叶节点之外,所有节点都有一个或者多个子节点,叶节点没有子节点。父节点和子节点之间用指针链接。
二叉树:每个节点最多只能有两个子节点。
二叉树中最重要的操作莫过于遍历。
遍历方式有以下几种:
前序遍历:根节点->左子节点->右子节点
中序遍历:左子节点->根节点->右子节点
后序遍历:左子节点->右子节点->根节点

宽度优先遍历:先访问树的第一层节点,再访问树的第二层节点……一直访问到最后一层节点。在同一层节点中,以从左到右的顺序依次访问。

二叉树特例:
1、 二叉搜索树中,左子节点总是小于或等于根节点,而右子节点总是大于或等于根节点。
2、 堆,分为最大堆和最小堆。在最大堆中根节点的值最大,在最小堆中根节点的值最小。有很多需要快速找到最大值或者最小值的问题都可以用堆来解决。
3、 红黑树,是把树中的节点定义为红、黑两种颜色,并通过规则确保从根节点到叶节点的最长路径的长度不超过最短路径的两倍。

算法思路:
在这里插入图片描述
在这里插入图片描述
前序遍历的第一个数字就是根节点,故1就是根节点, 1的左子节点有三个,右子节点有4个。
中序遍历的根节点是1,1的左边是左子节点,1的右边是右子节点。
通过上面的分析确定前序和中序遍历的左右子节点对应的子序列后,采用递归的思想便可构建二叉树。

代码实现:

class Solution {
public:
    TreeNode* R(vector<int> a,int abegin,int aend,vector<int> b,int bbegin,int bend)
    {
        if(abegin>=aend || bbegin>=bend)
            return NULL;
        TreeNode* root=new TreeNode(a[abegin]);
        //root->val=a[abegin];
        int pivot;
        for(pivot=bbegin;pivot<bend;pivot++)
            if(b[pivot]==a[abegin])
                break;
        root->left=R(a,abegin+1,abegin+pivot-bbegin+1,b,bbegin,pivot);
        root->right=R(a,abegin+pivot-bbegin+1,aend,b,pivot+1,bend);
        return root;
    }
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        return R(pre,0,pre.size(),vin,0,vin.size());
    }
};

猜你喜欢

转载自blog.csdn.net/tpf930726/article/details/88850584