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);
}
};
二.二叉树的最近公共祖先
力扣链接:二叉树的最近公共祖先
可以借这题总结一下一般的递归模型。
- 递归结束条件
- 继续递归
- 本层递归要做的处理
- 本层递归要向上层传递的信息
其中 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];
}
};
五.数组中数字出现的次数
力扣链接:数组中数字出现的次数
在做这题之前需要先对位运算有一定的了解。
摘自力扣:
- 任何数与 0 做异或运算结果都是它本身。
- 任何数与自身做异或运算结果都是 0。
- 异或运算满足交换律与结合律。
先让所有数做异或运算,这样得到的结果一定是那两个只出现了一次的数的异或结果。找到其中一位为一的比特位用来做标记比特位把数据分为两组。具体的分法为判断每个数的标记比特位是否为一,如果为一则为一组,为零为另一组。这样分成的两组中一定各自拥有一个只出现了一次的数。再每组各自进行异或运算就能得到那两个只出现了一次的数。
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};
}
};