1.从下往上按层打印二叉树
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode * root) {
vector<vector<int>> result;
if (root == NULL)
{
return result;
}
queue<TreeNode*> q;
q.push(root);
while (!q.empty())
{
vector<int> v;
int size = q.size();//精华所在,当前层需要出队列的元素个数直接可得到
for (int i = 0; i < size; i++)
{
TreeNode* tmp = q.front();
q.pop();
v.push_back(tmp->val);
if (tmp->left != NULL)
q.push(tmp->left);
if (tmp->right!=NULL)
q.push(tmp->right);
}
result.push_back(v);
}
reverse(result.begin(), result.end());//灵魂操作,使用函数直接翻转
return result;
}
};
70,爬楼梯
第一种方法:动态规划
class Solution {
public:
int climbStairs(int n) {
vector<int> dp(n+1,-1);
dp[0]=1;
dp[1]=1;
for(int i=2;i<=n;i++)
dp[i]=dp[i-1]+dp[i-2];
return dp[n];
}
};
343,整数切分
方法一:函数递归+记忆化搜索
函数递归+记忆化搜索
从上到下的思路,将拿到的整数n分成 1-> (n-1),在计算每一种情况的余下的部分拆分后乘积的最大值
完美的递归思路,
class Solution {
public:
vector<int> memo;//因为是递归函数,所以在函数外定义
int max3(int x,int y,int z)
{
int tmp=0;
tmp=x>y?x:y;
return tmp>z?tmp:z;
}
int _integerBreak(int n)
{
if(n==1)//划分到 1了,那就不用划分了
return 1;
if(memo[n]!=-1)//记忆化搜索,和斐波那契数列一样,存在很多重复计算,如果已经计算过了就直接返回。
return memo[n];
int res=-1;//这个值用来保存最大的乘积和。
for(int i=1;i<n;i++)
{
res=max3(res,i*(n-i),i*_integerBreak(n-i));
}
memo[n]=res;//每次计算完一个数的最大乘积和就保存起来。
return res;//返回这次递归的计算值
}
int integerBreak(int n) {
memo=vector<int>(n+1,-1);//虽然在函数外声明vector,在函数内定义的方式
return _integerBreak(n);//
}
};
方法二:动态规划
//动态规划,自底向上分析法
class Solution {
public:
int max3(int x,int y,int z)
{
return max(x,max(y,z));
}
int integerBreak(int n) {
assert(n>=2);
vector<int> dp(n+1,-1);//个数定为n+1 才有dp[n],方便一一对应,dp[n]表示数字n的最大组合乘积
dp[1]=1;//1不同切割,直接返回1
for(int i=2;i<=n;i++)//这层循环用来将2——n中所有的数字遍历,
{
for(int j=1;j<=i-1;j++)//拿到2--n中的一个数字i,就划分出1 -- i-1 种情况
{
dp[i]=max3(dp[i],j*(i-j),j*dp[i-j]);//每一种情况都可能会改变dp[i],
//所以将现有的dp[i],当前情况切分一次的乘积
//,和当前情况切分多次的乘积比较找出最大值
//把每种情况都遍历完,就能找到最大值。
}
}
return dp[n];
}
};
198,打家劫舍
方法一:递归+记忆
//函数递归,记忆化搜索法
class Solution {
public:
vector<int> dp;//下标为i的元素表示[i,n-1]区间内最大价值
int _rob( vector<int> nums,int index) //这个函数就是求[index,n-1]的最大价值
{
if(index>=nums.size())//没有可以偷盗的房间了,那么可偷盗的价值是0
return 0;
if(dp[index]!=-1)//如果[index,n-1]的区间已经计算过了,就不用计算了
return dp[index];
int res=0;
for(int i=index;i<nums.size();i++)//[index,n-1]的所有情况
{
if(i<=index+1) //如果是区间[index,n-1],那么必定会从index号房间,或者index+1号房间选择一个
//所以只需处理这两种情况,并递归处理两种情况对应的选择
res=max(res,nums[i]+_rob(nums,i+2) );
}
dp[index]=res;
return res;
}
int rob(vector<int>& nums) {
int n=nums.size();
dp=vector<int>(n+1,-1);//申请n+1个空间,那么才有dp[n]
return _rob(nums,0);
}
};
方法二:动态规划
//动态规划方法
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size()==0)//如果数组没有值,那抢劫的价值就是0
return 0;
int n=nums.size();
vector<int> dp(nums.size(),-1);//下标为index的元素的值表示在区间[index,n-1]内,抢劫的最大价值
dp[n-1]=nums[n-1];//定义初始状态
for(int i=n-2;i>=0;i--)//这层循环表示房间数逐渐增加,我们需要从简到繁,所以倒着来
{
for(int j=i;j<n;j++)//将[i,n-1]区间内的所有盗窃情况罗列出来
{
if(j<=i+1)//实际上只需要罗列盗窃i号房间或者i+1号房间的情况,因为最大值定会出现在这两种情况内
{
dp[i]=max(dp[i],nums[j]+ (j+2<n ? dp[j+2]:0) );//存在j+2越界的情况,所以判断一下
}
}
}
return dp[0];//最后返回[0,n-1]的盗窃最大值
}
};
75.颜色分类
一个数组中只有012,请排序
//中心思想是三路快排法
class Solution {
public:
void sortColors(vector<int>& nums) {
int n=nums.size();
int zero=-1;//[0,zero]全部存放0,所以一开始zero等于-1,表示区间内没有0
int two=n; //[two,n-1]全部存放2,所以一开始two等于n,表示区间内没有2
for(int i=0;i<two;)//注意这里不用i++
{
if(nums[i]==1)//先不用管往后走,找到0再进行交换就行
i++;
else if(nums[i]==0)
{
zero++;
swap(nums[i],nums[zero]);
i++;//交换后[0,i]都是有序的
}
else
{
two--;
swap(nums[i],nums[two]);//将没有处理的数交换过来了,所以不能i++,继续检查i
}
}
}
};
167.两数之和 II - 输入有序数组
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
- 返回的下标值(index1 和 index2)不是从零开始的。
- 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
方法一
//主要的思想是 首先遍历一遍数组,并且在当前元素的以后的数组中利用二分查找寻找 target-numbers[i]
//在这里肯定会想,我怎么只管后面的,不管前面的数字呢
//事实上,如果前面的数字和当前数字可以满足条件,那么在遍历前面数字的时候就找到了。不会到后面来了
//二分查找的时间复杂度为O(lgn),所以总的时间复杂度为O(nlgn)
//暴力查找的时间复杂度为O(N^2)
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int n=numbers.size();
vector<int> result;
for(int i=0;i<n-1;i++)//最后一个数就不用检查了
{
int ret=BinarySearch(numbers,i+1,n,target-numbers[i]);//区间[i+1,n)里面找
if(ret!=-1)
{
result.push_back(i+1);
result.push_back(ret+1);
return result;
}
}
return result;
}
int BinarySearch(vector<int>& numbers,int left,int right,int target)
{
while(left<right)
{
int mid=left+(right-left)/2;
if(numbers[mid]==target)
return mid;
else if(numbers[mid]>target)
right=mid;
else
left=mid+1;
}
return -1;
}
};
方法二
//中心思想:因为数组是有序的,将左右端点相加,满足就返回,
// 和大于target,那就缩小和,将right--
和小于target,那就增大和,将left++
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
vector<int> result;
int n =numbers.size();
int left=0;
int right=n-1;
while(left<right)//不需要等于号,left和right相等时,表示一个数字,肯定错误
{
if(numbers[left]+numbers[right]==target)
{
result.push_back(left+1);
result.push_back(right+1);
return result;
}
else if(numbers[left]+numbers[right]>target)
right--;
else
left++;
}
return result;
}
};
199,二叉树的右侧视图
//主要的思想是:利用从上到下按层遍历的思想,但只是将每层的最后一个节点入vector
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
vector<int> result;
if(root==NULL)return result;
TreeNode* pCur=NULL;
queue<TreeNode*> q;
q.push(root);
while(!q.empty())//层序遍历只要一个条件
{
int size=q.size();//将本层要遍历的节点数拿到
for(int i=0;i<size;i++)
{
pCur=q.front();
q.pop();
if(pCur->left!=NULL)
q.push(pCur->left);
if(pCur->right!=NULL)
q.push(pCur->right);
}
result.push_back(pCur->val);//出了for循环,pCur刚好是最后一节点
}
return result;
}
};
1.两数之和
给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。
你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> record;
for(int i=0;i<nums.size();i++)//遍历每个元素,如果直接将每个元素放入map中
//重复的元素会被覆盖,如目标=8,两个元素为4
//所以每次加入一个元素前
//先在map中寻找有没有满足的元素,如果有就返回
//如果没有即便是覆盖了也没事
{
int other=target-nums[i];
if(record.find(other)!=record.end())//find函数遍历map,找不到就到end位置了
{
int array[2]={record[other],i};//找到了就把两个索引放入数组中
vector<int>result(array,array+2);
return result;
}
record[nums[i]]=i;//在map中插入 nums[i]:i这个键值对
}
}
};
209.长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。
如果不存在符合条件的连续子数组,返回 0。
示例:
输入:s = 7, nums = [2,3,1,2,4,3]
输出: 2 解释: 子数组[4,3]
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int n =nums.size();
if(n==0)return 0;
int left=0;
int right=-1;//left和right组成滑动窗口,所以一开始right=-1表示区间为空
int sum=0;//
int res=n+1;//用来记录最短子数组
while(left<n)//当左指针到达最后元素时,才算结束
{
if(right+1<n && s>=sum)//条件1 为了防止数组越界
{
right++;//注意。先加加
sum+=nums[right];//使滑动窗口增大
}
else
{
sum-=nums[left];//注意先减掉左端元素,使滑动窗口缩小
left++;
}
if(sum>=s)//每次滑动窗口改变都检查一下是否可以更新最小子数组长度
res=min(res,right-left+1);//参数2表示当前窗口长度
}
if(res==n+1)//要是没有解,就返回0
return 0;
return res;
}
};
3.无重复字符的最长子串
给定一个字符串,找出不含有重复字符的最长子串的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 无重复字符的最长子串是 "abc",其
长度为 3。
//利用的是滑动窗口
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n =s.size();
int left=0;
int right=-1;
int res=0;
int freq[256]={0};//保存所有的字符在滑动窗口中的出现情况
while(left<n)//外层循环的条件还是左端点到达最右边的时候
{
if(right+1<n && freq[s[right+1]]==0)//右端点也要防止越界,并且下一个元素不在滑 //动窗口中
{
right++;
freq[s[right]]++;
}
else
{
freq[s[left]]--;//下一个元素在滑动窗口中,那么就只能左端点向前移动去掉相同的 //元素
left++;
}
res=max(res,right-left+1);//更新最大不同子数组长度
}
return res;
}
};
454.四数相加 II
给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l)
,使得 A[i] + B[j] + C[k] + D[l] = 0
。
为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。
例如:
输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
输出:
2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
//主要的思路:暴力层层遍历的方式时间复杂度为O(n^4)
//将一个数组放入unodered_map中,层层遍历其他三个数组,时间复杂度为O(N^3)
//也可以选择将两个数454.
四数相加 II组的所有组合都放入unordered_map中,层层遍历其他两个数组
//这样的时间复杂度为O(N^2)对于500*500的数量级来说是easy的
//空间复杂度为O(N^2)
class Solution {
public:
int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
int n=A.size();
int res=0;
unordered_map<int,int>mymap;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
mymap[C[i]+D[j]]++;//将两个数组的所有组合加入map中
}
}
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if( mymap.find(0-A[i]-B[j])!=mymap.end() )
{
res+=mymap[0-A[i]-B[j]];
}
}
}
return res;
}
};
447.回旋镖的数量
给定平面上 n 对不同的点,“回旋镖” 是由点表示的元组 (i, j, k)
,其中 i
和 j
之间的距离和 i
和 k
之间的距离相等(需要考虑元组的顺序)。
找到所有回旋镖的数量。你可以假设 n 最大为 500,所有点的坐标在闭区间 [-10000, 10000] 中。
示例:
输入:
[[0,0],[1,0],[2,0]]
输出:
2
解释:
两个回旋镖为 [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]]
//主要的思路是:使用层层遍历的方式,时间复杂度为O(N^4),接受不了
// 每次拿出一个点,再循环拿出其它点,计算其他点与该点的距离
// 并将距离作为键,将该距离出现的次数定为值,作为键值对存入map
// 对于当前点来说,满足条件的三元点个数就是各个距离的长度对应的次数*次数-1的和
// 比如距离为5的点有4个,那么就从这四个中选择两个就行了所以是=4*3,再将所有距离加和
class Solution {
public:
int numberOfBoomerangs(vector<pair<int, int>>& points) {
int res=0;
//首先遍历整个vector
for(int i=0;i<points.size();i++)
{
//定义一个map,键=当前元素与其他元素的距离,值=同一距离的其他元素的个数
unordered_map<int,int>record;//每次循环执行到这里就对map重新初始化,相当于清空
for(int j=0;j<points.size();j++)
{
if(i!=j)//不是相同的点的时候
{
record[ dis(points[i],points[j]) ]++;//计算出一个距离,放入map,并++, // 始值为0
}
}
for(unordered_map<int,int>::iterator iter=record.begin();//遍历map
iter!=record.end();iter++)
{
res+=(iter->second)*(iter->second-1);
}
}
return res;
}
long dis(pair<int,int>& p1,pair<int,int>& p2 )
//计算两点间的距离((x1-x2)^2)+((y1-y2)^2)开根号
//因为开根号会导致出现浮点数,但我们只是就算距离是否相等,所以这里不开方,也能比较
{
return (p1.first-p2.first)*(p1.first-p2.first)+
(p1.second-p2.second)*(p1.second-p2.second);
}
};
104,二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
//要深刻明白二叉树本身就是递归结构
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root==NULL)
return 0;
int left=maxDepth(root->left);
int right=maxDepth(root->right);
return max(left,right)+1;//+1是加上当前节点
}
};
226,翻转二叉树
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root==NULL)
return NULL;
invertTree(root->left);
invertTree(root->right);
swap(root->left,root->right);
return root;
}
};
112,路径总和
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。
示例:
给定如下二叉树,以及目标和 sum = 22
,
5
/ \
4 8
/ / \
11 13 4
/ \ \
7 2 1
返回 true
, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2
。
//中心的思想:将当前的节点值减掉后交给左右节点去比较
class Solution {
public:
bool hasPathSum(TreeNode* root, int sum) {
if(root==NULL)//要确保第一次不为空
return false;
if(root->left==NULL && root->right==NULL)//确保是叶子节点
{
return sum==root->val;
}
if(hasPathSum(root->left,sum-root->val))
return true;
if(hasPathSum(root->right,sum-root->val))
return true;
return false;//前面没有返回,那么就是不符合
}
};
111,二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回它的最小深度 2.
//容易出错的点是,遇到一个节点为NULL就返回0,其实应该在叶子节点处才返回0,其他的null节点不用管
class Solution {
public:
int minDepth(TreeNode* root) {
if(root==NULL)
return 0;
if(root->left==NULL)//若左节点为NULL,那么就只用管右节点了
return minDepth(root->right)+1;
if(root->right==NULL)//若右节点为NULL,就只管左节点,记住要+1,表示当前节点
return minDepth(root->left)+1;
if(root->left==NULL && root->right==NULL)//左右都是NULL,返回当前节点1就行
return 1;
//若左右都不为NULL,就将小的返回
int left=minDepth(root->left);
int right=minDepth(root->right);
return min(left,right)+1;//记住要加1
}
};
275,二叉树的所有路径
给定一个二叉树,返回所有从根节点到叶子节点的路径。
说明: 叶子节点是指没有子节点的节点。
示例:
输入:
1
/ \
2 3
\
5
输出: ["1->2->5", "1->3"]
解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3
//一般的递归方式,难点是返回的是vector
//to_string函数将数字变成字符
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> res;//每次递归函数的res都是新的
if(root==NULL)
return res;
if(root->left==NULL && root->right==NULL)//只有叶子节点才入vector
{
res.push_back(to_string(root->val));
return res;
}
vector<string> lefts=binaryTreePaths(root->left);
for(int i=0;i<lefts.size();i++)//将左子树的所有路径遍历,给前面加上当前节点值
res.push_back(to_string(root->val)+"->"+lefts[i]);
vector<string> rights=binaryTreePaths(root->right);
for(int i=0;i<rights.size();i++)//将右子树的所有路径遍历,给前面加上当前节点值
res.push_back(to_string(root->val)+"->"+rights[i]);
return res;
}
};
110,平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
示例 1:
给定二叉树 [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回 true
。
示例 2:
给定二叉树 [1,2,2,3,3,null,null,4,4]
1
/ \
2 2
/ \
3 3
/ \
4 4
返回 false
。
方法一:递归每个节点,并且计算出每个节点的最大深度,进行比较,但是这种方法导致多次重复计算,不可取
public:
bool isBalanced(TreeNode* root) {
if(root==NULL)
return true;
if(! (isBalanced(root->left) && isBalanced(root->right)) )
return false;
int left=_isBalanced(root->left);
int right=_isBalanced(root->right);
return (left-right>=-1 && left-right<=1);
}
int _isBalanced(TreeNode* root)
{
if(root==NULL)
return 0;
int left= _isBalanced(root->left);
int right=_isBalanced(root->right);
return max(left,right)+1;
}
};
103,锯齿状打印二叉树(之字形)
//中心思想:大体还是层序遍历,但是使用栈存储节点,只能从栈顶出,并且每层入栈的左右顺序不一样
class Solution {
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
vector<vector<int>> res;//要返回的二维数组
if(root==NULL)
return res;
bool flag=true;//标记位
stack<TreeNode*> s1;//因为需要一边入栈,一边出栈,所以定义两个栈,一个只入,一个只出
stack<TreeNode*> s2;
s1.push(root);//先将根节点入栈,层序遍历的老规矩
while(!s1.empty())
{
s2=s1;//交接班
int n=s1.size();//这波很关键,因为下面要删栈元素,所以先将栈的元素个数保存
for(int i=0;i<n;i++)//将只入的栈清空,为了入下层节点准备
{
s1.pop();
}
vector<int>tmp;//自动刷新
int size=s2.size();//必须先保存个数
for(int i=0;i<size;i++)
{
TreeNode* pCur=s2.top();
s2.pop();
tmp.push_back(pCur->val);//将节点的值加入vector
if(flag==true)//两种状态,先入左边,将来从右向左打印
{
if(pCur->left!=NULL)
s1.push(pCur->left);
if(pCur->right!=NULL)
s1.push(pCur->right);
}
else//先入右边,将来从左向右打印
{
if(pCur->right!=NULL)
s1.push(pCur->right);
if(pCur->left!=NULL)
s1.push(pCur->left);
}
}//for
flag=(!flag);//遍历一层就改变一次状态
res.push_back(tmp);//加入二维数组
}//while
return res;
}
};
53,最大子虚和
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int max=nums[0];//必须要将max置为nums[0],否则当数组只有一个-1时,就会返回0
int sum=0;
int n=nums.size();
for(int i=0;i<n;i++)
{
sum+=nums[i];
if(sum>max)
max=sum;
if(sum<0)
sum=0;
}
return max;
}
};