剑指Offer C++ --- 数组篇

唉,真的是没有办法啊,只要是稍微大一点的公司都是先笔试,再面试,笔试过不了,八股背的再多,理解的再怎么透彻也没有发挥空间,只能硬着头皮刷题,我对算法真的很抵触,很苦恼,为了工作不得不做,废话不多说,直接开搞!今日目标20个题!

1. 数组重复的数字

int findRepeatNumber(vector<int>& nums) 
{
    unordered_map<int,bool> map;
    for(auto x : nums)
    {
        if(map[x])
        {
           return x;
        }
        map[x] = true;
    }
    
    return -1;
}

利用哈希表,很容易!

map[x] 的位置没有元素时,容器会默认添加一个key = x,val = 0的元素

2.二维数组的查找

bool findNumberIn2DArray(vector<vector<int>>& matrix, int target)
{
     int i = matrix.size();
     int j = 0;

     while(i>=0 && j<matrix[0].size)
     {
         //去掉一列
         if(matrix[i][j] < target)
         {
            j++;
         }
         //去掉一行
         else if(matrix[i][j] > target)
         {
            i--;
         }
         else
         {
            return false;
         }

     }

     return true;
} 

二分查找的变形,从左下角开始,此元素大于target,向上走一行,此元素小于target向右走一列

3.BST树:非递归删除

如果通过二分查找,找到待删除结点和其双亲节点

分为:三种情况

第一种:待删除结点没有孩子结点,直接将其双亲结点指向它的地址域置nullptr

第二种:待删除结点只有一个孩子结点,那就将其挂接在双亲结点指向待删除结点本身的地址域

第三种:待删除结点有两个孩子结点,找到待删除结点左子树中最大的结点或者右子树中最小的结点,将其data覆盖待删除结点的data,这样就转化为第一种或者第二种情况(即就是处理叶子结点)

特殊情况:对于左偏树,或者右偏树,删除根节点,需要重置根节点为其第一个非空孩子结点

bool  n_remove(Node& val)
{
   Node* cur = root_;
   Node* parent = nullptr;
   
   //空树直接返回
   if(root_ == nullptr)
   {
      return false;
   }
   
   while(cur != nullptr)
   {
       if(cur—>data < val.data)
       {
            parent = cur;
            cur = cur->right;
       }
       else if(cur->data > val.data)
       {
            parent = cur;
            cur = cur->left;
       }
       else 
       {
            break;  //找到待删除结点
       }
   }

   if(cur == nullptr)
   {
       return false;
   }

   //先处理第三种情况
   if(cur->left != nullptr && cur->right != nullptr)
   {
       //找到待删除结点左子树中最大的结点
       parent = cur;
       Node* pre = cur->left
       while(pre->right!=nullptr)
       {
          parent = pre;
          pre = pre->right;
       }
       cur->data = pre->data;
       cur = pre;
   }

   //合并只有一个孩子是左孩子,还是右孩子,或者没有孩子的情况
   Node* child = cur->left;
   if(child == nullptr)
   {
      child = cur->right;
   }
   
   //处理左偏树或者右偏树
   if(parent == nullptr)
   {
      root_ = child;
   }
   else
   {
      //用待删除结点的孩子结点,覆盖其在双亲结点的地址域
      if(parent->left == cur)
      {
        parent->left = child;
      }
      else
      {
        parent->right = child;
      }

   } 
   delete cur;
   return true;
}

4.BST树递归层序遍历

//计算树高
int heigh(Node* node)
{
    if(node == nullptr)
    {
       return 0;
    }

    int left = heigh(node->left);
    int right = heigh(node->right);
    return left>right ? left+1 : right+1;
}

//计算树的结点个数
int nums(Node* node)
{
    if(node == nullptr)
    {
       return 0;
    }

    int left = nums(node->left);
    int right = nums(node->right);
    
    return left + right + 1;
}


void levelOrder(Node* node,int i)
{
    if(node == nullptr)
    {
       return;
    }

    if(i == 0)
    {
       cout << node->data << "  ";
       return;
    }

    levelOrder(node->left,i-1);
    levelOrder(node->right,i-1);

}


void levelOrder()
{
    int n = heigh(node);
   
    //用i来控制递归的层数
    for(int i = 0;i<n;++i)
    {
       levelOrder(node,i);
    }

    cout << endl; 
}

5.BST树递归插入

Node* insert(Node* node,int val)
{
   if(node == nullptr)
   {
      return new Node(val);
   }
   
   //不插入相同的元素
   if(node->data == val)
   {
      return node;
   }
   else if(node->data < val)
   {
      node->right = insert(node->right,val);
   }
   else
   {
      node->left = insert(node->left,val);
   }

   return node;
}

6.BST树的递归删除

Node* remove(Node* node, int val)
{
    if(node == nullptr)
    {
       return nullptr;
    }
    
    //找到待删除结点
    if(node->data == val)
    {
        //三种情况处理
        if(node->left != nullptr && node->right != nullptr)
        {
           Node* pre = node->left;
           //待删除结点左子树中最大的结点
           while(pre->right != nullptr)
           {
               pre = pre->right;
           }

           //递归直接删除---待删除结点
           node->data = pre->data;
           node->left = remove(node->left,pre->data);
        }
        else
        {
            //只有一个孩子结点
            if(node->left != nullptr)
            {
               Node* left = node->left;
               delete node;   
               return left;     
            }
            else if(node->right != nullptr)
            {
               Node* right = node->right;
               delete node;
               return right;
            }
            //没有孩子结点
            else
            {
               delete node;
               return nulllptr;
            }
        }
    }

    else if(node->data < val)
    {
        node->right = remove(node->right,val);
    }

    else
    {
        node->left = remove(node->left,val);
    }

    return node;
}

7.BST树非递归前序遍历(单栈)

void preOrder()
{
    if(root_ == nullptr)
    {
       return nullptr;
    }

    stack<Node*> st;
    st.push(root_);

    while(!st.empty())
    {
        Node* node = st.top();      //v
        st.pop();
        cout << node->data << "  ";
        
        if(node->right != nullptr)
        {
           st.push(node->right);    //L
        }
        
        if(node->left != nullptr)   //R
        {
           st.push(node->left);
        }
    }

    cout << endl;
}

思路:每循环一次先将栈顶元素出栈,输出栈顶元素,再将栈顶元素的右孩子先入栈,再将其左孩子入栈

8.BST树的中序遍历(单栈)

void inOrder()
{
   if(root_ == nullptr)
   {
       return;
   }
   
   stack<Node*> st;
   Node* cur = root_;

   while(!st.empty() || cur != nullptr)
   {
      if(cur!=nullptr)
      {
         st.push(cur);
         cur = cur->left;
      }
      else
      {
         cur = st.top();
         st.pop();
         cout << cur->data << "  ";
         cur = cur->right;
      }
   }
   
   cout << endl;
}

思路:先将左侧的结点入到栈里,如果左侧结点遇到nullptr,弹出栈顶元素并输出,转而入弹出元素的右侧结点

9.BST树的后序遍历 (双栈)

思路:将LRV 转化为 VRL 输出的时候又转化为 LRV

利用栈1存储 LRV, 按顺序转入栈2后,变为 VRL

void postOrder()
{
    if(root_ == nullptr)
    {
        return;
    }
    
    stack<Node*> st1;
    stack<Node*> st2;
    st1.push(root_);
    
    while(!st1.empty())
    {
       Node* node = st1.top();
       st1.pop();
       st2.push(node);
       if(node->left != nullptr)
       {
          st1.push(node->left);
       }
       
       if(node->right != nullptr)
       {
          st2.push(node->right);
       }
    }

    while(!st2.empty)
    {
       cout <<st2.top()->data << " ";
       st2.pop();
    }
    cout << endl;
}

10.BST树的广度优先遍历

void levelOrder()
{
    if(root_ == nullptr)
    {
       return nullptr;
    }

    queue<Node*> q;
    q.push(root_);

    while(!q.empty())
    {
       Node* node = q.front();
       q.pop();
       cout << node->data << "  ";
       if(node->left!=nullptr)
       {
          q.push(node->left);
       }

       if(node->right!=nullptr)
       {
          q.push(node->right);
       }
    }
    cout << endl;
}

11.已知二叉树的前序中序遍历,重建二叉树

思路:采取分治的思想

 

i与j之间的距离要与m到n之间的距离一致

先序遍历第一个结点为根节点

在中序遍历中找到根节点,从第一个结点到根结点(不含根节点)全部都是左子树序列,从根节点(不含根节点)到末尾全部都是右子树序列

对应到先序遍历的序列中,按照相对位置计算,根节点到左子树序列的最后一个结点的距离等于中序遍历左子树序列的长度

首先可以先确定根结点的位置,那么下一个左子树的结点一定在先序序列根节点的下一个位置,对应到中序遍历应该是在中序遍历开始到根节点的位置-1之间,相应的就可以缩减先序序列的范围,起始位置+1到末尾距离要等于中序遍历开始到根节点的位置-1的距离

先序序列和中序序列,只要其中有一个错开,表示范围内没有合适的元素了

因为在中序遍历中下一个根节点一定在上一个根节点的左侧,而先序序列查找的范围距离要与中序序列查找范围的距离相等

TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) 
{
    return rebuild(preorder,0,preorder.size()-1,inorder,0,inorder.size()-1);
}


TreeNode* rebuild(vector<int>& preorder,int i,int j,vector<int>& inorder,int m,int n)
{
    if(i>j || m>n)
    {
       return nullptr;
    }
   
    TreeNode *node = new TreeNode(preorder[i]);
    for(int k = m; k<=n; k++)
    {
        node->left = rebuild(preorder,i+1,i+k-m,inorder,m,k-1);
        node->right = rebuild(preorder,i+k-m+1,j,inorder,k+1,n);
        return node;
    }
    return node;
}

12.旋转数组的最小数字

O(n)复杂度

思路:可看作两个分别递增的数组

 

   int minArray(vector<int>& numbers) 
    {
        int i = numbers.size()-1;
        if(numbers[0] < numbers[i])
        {
           return numbers[0];
        }

        while(i>0)
        {
           if(numbers[i-1]<numbers[i])
           {
               --i;
           }   
           else
           {
               return numbers[i];
           }
        } 
        return numbers[i];
    }

O(logn)

m = (i + j) / 1;

nums[m] > nums[j]    旋转点在【m+1,j】  i = m+1

nums[m] < nums[j]   旋转点【i,m】   j = m

nums[m] == nums[j]  不确定     j =  j-1

int minArray(vector<int>& numbers) 
{
    int i = 0;
    int j = numbers.size()-1;
    
     
    while(i < j)
    {
       int m = i+(j-i)/2;
       if(numbers[m] < numbers[j])
       {
          j = m;
       }   
       else if(numbers[m] > numbers[j])
       {
          i = m+1;
       }
       else
       {
          j -= 1;
       }
    }

    return numbers[i];
}

 当范围只剩下一个元素时,即是最小值

很遗憾今天只写了12道,明天继续加油吧!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值