LeetCode 笔记四

前言:主要是记录一些算法题的解题思路与技巧,纯当笔记用。

图片与部分代码来源:leetcode
图片与部分代码来源:leetcode
图片与部分代码来源:leetcode

一.重建二叉树

力扣链接:重建二叉树

在这里插入图片描述

可以用递归做这题,但要明白如何利用前序遍历与中序遍历构造二叉树。首先取前序遍历的第一个数,以它做二叉树的父节点构造二叉树。然后在中序遍历中找到该数,中序遍历左边的数为该二叉树的左子树,中序遍历右边的数为该二叉树的右子树。

到这里,基本的递归模型就构造完成了,接下来,只需要调整前序遍历与中序遍历的结构,使之再次成为和上述问题一样的问题,进入递归继续处理就行了。

class Solution {
private:

    unordered_map<int, int> index;

public:
    TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
        
        if(preorder_left>preorder_right)
           return nullptr;

        int inorder_root=index[preorder[preorder_left]];   //在中序遍历中找到前序遍历中的第一个数
        struct TreeNode* inorder_new=new TreeNode(inorder[inorder_root]);

        inorder_new->left=myBuildTree(preorder,inorder,preorder_left+1,preorder_left+(inorder_root-inorder_left),inorder_left,inorder_root-1);   //处理左子树

        inorder_new->right=myBuildTree(preorder,inorder,preorder_left+1+(inorder_root-inorder_left),preorder_right,inorder_root+1,inorder_right);   //处理右子树
        
        return inorder_new;
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        
        int n = preorder.size();
        for (int i = 0; i < n; ++i) {   //利用哈希表做预处理,便于快速在中序遍历中找到我们要找的数
            index[inorder[i]] = i;
        }

        return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
    }
};

二.二叉树的最近公共祖先

力扣链接:二叉树的最近公共祖先

在这里插入图片描述

可以借这题总结一下一般的递归模型。

  1. 递归结束条件
  2. 继续递归
  3. 本层递归要做的处理
  4. 本层递归要向上层传递的信息

其中 2,3 的顺序可以颠倒,也就是说,也可以先做某些处理再进入递归函数继续递归下去 。

某些递归不需要向上层传递消息,如果要传递,可能需要变量保存该信息,也有直接使用该信息做条件判断的 。

class Solution {
public:

    TreeNode* ret=NULL;

    bool dfs(TreeNode* root,TreeNode* p,TreeNode* q){

        if(!root)
           return false;

        bool left=dfs(root->left,p,q);
        bool right=dfs(root->right,p,q);

        if((left || right) && (root->val==p->val || root->val==q->val) || (left && right)){

            ret=root;     //找到了最近公共祖先
            return true;
        }

        if(left || right || root->val==p->val || root->val==q->val)   //左子树或右子树找到了要找的一个节点,则该子树需要一直返回 true 用作最近公共祖先的判断
           return true;

        return false;            
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        
            dfs(root,p,q);

            return ret;
    }
};

三.不用加减乘除做加法

力扣链接:不用加减乘除做加法

在这里插入图片描述

不能用加减乘除运算,就只能使用位运算了。利用 ^ 计算出没有进位的结果,利用 & 计算出进位。相加直至进位为零为止。

不理解直接开背了,哈。

class Solution {
public:
    int add(int a, int b) {
        
        while(b != 0) {    //判断进位是否为零
            
            int c = ((unsigned int)(a & b) << 1);   //计算进位
            a ^= b;    //计算没有进位的结果
            b = c; 
        }

        return a;
    }
};

四.整数拆分

力扣链接:整数拆分

在这里插入图片描述

拆分后,可以分为两部分,其中一部分我们认为只有一个数,另一部分可以是多个数,也可以是一个数。遍历第一部分的一个数,另一部分的最大乘积其实是原问题的子问题,可以使用一样的求解方式。子问题的值我们可以在前面求出用变量保存,所以考虑动态规划。

class Solution {
public:

    int integerBreak(int n) {

        vector<int> dp(n+1,0);
        dp[0]=0;
        dp[1]=1;
        for(int i=2;i<=n;++i)
           for(int j=1;j<i;++j)   //固定一个数,剩下的可以进行拆分,也可以不拆分。也可以都不拆分
              dp[i]=max(dp[i],max(dp[j]*(i-j),j*(i-j)));     

        return dp[n];
    }
};

五.数组中数字出现的次数

力扣链接:数组中数字出现的次数

在这里插入图片描述

在做这题之前需要先对位运算有一定的了解。

摘自力扣:

  1. 任何数与 0 做异或运算结果都是它本身。
  2. 任何数与自身做异或运算结果都是 0。
  3. 异或运算满足交换律与结合律。

先让所有数做异或运算,这样得到的结果一定是那两个只出现了一次的数的异或结果。找到其中一位为一的比特位用来做标记比特位把数据分为两组。具体的分法为判断每个数的标记比特位是否为一,如果为一则为一组,为零为另一组。这样分成的两组中一定各自拥有一个只出现了一次的数。再每组各自进行异或运算就能得到那两个只出现了一次的数。

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {

          int tmp=0;
          for(auto& tm:nums)    //得到那两个数异或的结果
              tmp^=tm;

          int count=1;
          while((tmp & count)==0)   //找到标记比特位
                count <<= 1;
          
          int nums_1=0,nums_2=0;
          for(auto& tm:nums){

              if(tm & count)   //分组并进行异或运算
                 nums_1^=tm;
              else             //分组并进行异或运算
                 nums_2^=tm;  
          }
                
          return vector<int>{nums_1,nums_2};
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值