题目描述
现在有一个整数类型的数组,数组中素只有一个元素只出现一次,其余的元素都出现两次。
注意:
你需要给出一个线性时间复杂度的算法,你能在不使用额外内存空间的情况下解决这个问题么?
解题思路:
所有值异或,相同值异或为0,最后剩下的就是唯一的不同元素
代码:
class Solution {
public:
int singleNumber(int A[], int n) {
int num = 0;
for(int i = 0; i < n; i++) {
num ^= A[i];
}
return num;
}
};
题目描述
求给定二叉树的最大深度,
最大深度是指树的根结点到最远叶子结点的最长路径上结点的数量。
解题思路:
先序遍历的方法,先找根,如果根为NULL就放回,不为空就遍历左右子树,这里分开记录左右子树的深度,最后直接比较大小。需要注意的是不能直接在递归的时候比较,因为在递归的时候再用三目运算,增大了时间复杂度。
代码:
class Solution {
public:
int maxDepth(TreeNode *root) {
if (root == NULL) {
return 0;
}
int a = 1 + maxDepth(root->left);
int b = 1 + maxDepth(root->right);
return a > b?a:b;
}
};
题目描述
给出两个二叉树,请写出一个判断两个二叉树是否相等的函数。
判断两个二叉树相等的条件是:两个二叉树的结构相同,并且相同的节点上具有相同的值。
解题思路:
如果两个树均为空,返回true;如果只有一个树为空,返回false;如果两个树的根节点的值不相等,返回false;如果两个树的根节点的值相等,则分别比较它们的左子树、右子树是否相等(可采用递归的方法)
代码:
class Solution {
public:
bool isSameTree(TreeNode *p, TreeNode *q) {
if(p == NULL && q == NULL) {
return true;
}
if(p == NULL || q == NULL) {
return false;
}
if(p->val != q->val) {
return false;
}
return isSameTree(p->left, q->left)&&isSameTree(p->right, q->right);
}
};
题目描述
求给定的二叉树的前序遍历。
代码:
递归
解题思路:前序、中序、后序都是可以通过递归来实现的,只需要不断调用自身函数,但是问题就是带来的空间复杂度太高。因此最好的方案是迭代。
时间复杂度:O(n)
空间复杂度:最坏情况下需要空间O(n),平均情况为O(logn)。
class Solution {
vector<int> vec;
public:
vector<int> preorderTraversal(TreeNode *root) {
if(root == NULL) {
return vec;
}
vec.push_back(root->val);
preorderTraversal(root->left);
preorderTraversal(root->right);
return vec;
}
迭代
解题思路:
前序遍历的顺序为根-左-右,具体算法为:
把根节点push到栈中
循环检测栈是否为空,若不空,则取出栈顶元素,保存其值
看其右子节点是否存在,若存在则push到栈中
看其左子节点,若存在,则push到栈中。
原博地址:https://www.jianshu.com/p/88af12725af8
时间复杂度:O(n)
空间复杂度:O(n)
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> nums;
if (root == NULL){
return nums;
}
stack<TreeNode*> s;
s.push(root);
TreeNode* p = NULL;
while (!s.empty()){
p = s.top();
s.pop();
nums.push_back(p->val);
if (p->right != NULL){
s.push(p->right);
}
if (p->left != NULL){
s.push(p->left);
}
}
return nums;
}
};
题目描述
给出一棵二叉树,返回这棵树的中序遍历
代码
//方法三 递归
class Solution {
public:
vector<int>res;
vector<int> inorderTraversal(TreeNode *root) {
if(root==NULL)
return res;
if(root->left!=NULL)
inorderTraversal(root->left);
res.push_back(root->val);
if(root->right!=NULL)
inorderTraversal(root->right);
return res;
}
};
//方法二 用栈模拟递归过程
vector<int> inorderTraversal(TreeNode *root) {
vector<int>res;
stack<TreeNode*>st;
while(root || !st.empty()){
if (root==nullptr){
root = st.top();
st.pop();
res.push_back(root->val);
root = root->right;
}
else{
st.push(root);
root = root->left;
}
}
return res;
}
//方法三:morris中序遍历空间复杂度O(1),时间复杂度O(n)
class Solution {
public:
vector<int>res;
vector<int> inorderTraversal(TreeNode *root) {
vector<int>res;
TreeNode*temp = nullptr;
while (root){
if (root->left==nullptr){
res.push_back(root->val);
root = root->right;
}
else{
temp = root->left;
while(temp->right!=nullptr && temp->right!=root){
temp = temp->right;
}
if (temp->right==nullptr){
temp->right = root;
root=root->left;
}
else{
res.push_back(root->val);
temp->right = nullptr;
root= root->right;
}
}
}
return res;
}
};
题目描述
删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次
例如:
给出的链表为1->1->2,返回1->2.
给出的链表为1->1->2->3->3,返回1->2->3.
解题思路1:双指针法。从链表头开始构造一快一慢两个指针:slow和fast,fast先移动,当遇到和slow元素值不同的节点时停下来,然后将slow指向fast并对slow和fast进行更新。
代码
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head==nullptr){
return nullptr;
}
ListNode* slow = head;
ListNode* fast = head->next;
while(fast!=nullptr){ // 注意终止条件
while(fast!=nullptr && fast->val==slow->val){
fast = fast->next;
}
slow->next = fast;
slow = fast;
if(fast!=nullptr){ // 注意判断是否为空
fast = fast->next;
}
}
return head;
}
};
解题思路2:
时间复杂度:O(n)
遍历链表一边。
空间复杂度:O(1)
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head==nullptr){
return nullptr;
}
ListNode* curNode = head;
while(curNode!=nullptr && curNode->next!=nullptr){
if(curNode->val==curNode->next->val){
curNode->next = curNode->next->next;
}else{
curNode = curNode->next;
}
}
return head;
}
};
题目描述
判断给定的链表中是否有环
扩展:
你能给出不利用额外空间的解法么?
解题思路:
快慢指针遍历链表,快指针步距为2,慢指针步距为1,如果链表带环,两指针一定会在环中相遇。
判断极端条件,如果链表为空,或者链表只有一个结点,一定不会带环,直接返回NULL。
创建快慢指针,都初始化指向头结点。因为快指针每次都要步进2个单位,所以在判断其自身有效性的同时还要判断其next指针的有效性,在循环条件中将两语句逻辑与并列起来。
单次循环中,如果快指针与慢指针相等,即指向的相同的地址(同一结点),则说明有环,返回true。否则到达链表结尾跳出循环后返回false。
为什么快指针步距为2,慢指针步距为1,如果有环就一定会相遇呢?
这是一个数学问题,因为能被2整除的数,一定会被1整除,所以二者一定会相遇 ~
我们可以猜想一下如果快指针步距为3,慢指针步距为1,还肯定会相遇吗?其实不然,用数学的角度来看,他们也许永不相遇:原文链接:https://blog.csdn.net/qq_42351880/article/details/88958508
代码:
class Solution {
public:
bool hasCycle(ListNode *head) {
if (!head)
return 0;
auto fast = head, slow = head;
while(fast&&fast->next){
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
return 1;
}
return 0;
}
};
题目描述
假设你有一个数组,其中第i个元素表示某只股票在第i天的价格。
设计一个算法来寻找最大的利润。你可以完成任意数量的交易(例如,多次购买和出售股票的一股)。但是,你不能同时进行多个交易(即,你必须在再次购买之前卖出之前买的股票)。
解题思路:只要有利益就卖出股票,不断累积利益,直至最后的结果;
代码:
class Solution {
public:
int maxProfit(vector<int> &prices) {
if(prices.size()==0)
return 0;
int res0=0,res1=0,min=prices[0];
for(int i=1;i<prices.size();i++)
{
if(prices[i]>min)
{
res0=prices[i]-min;
res1+=res0;
min=prices[i];
}
else
min=prices[i];
}
return res1;
}
};
题目描述
假设你有一个数组,其中第i个元素是某只股票在第i天的价格。
如果你最多只能完成一笔交易(即买一股和卖一股股票),设计一个算法来求最大利润。
解题思路:只可以交易一次,则找出差值最大的即可
代码:
方法1
class Solution {
public:
int maxProfit(vector<int> &prices) {
int len=prices.size();
if(len==0||len==1)
return 0;
int maxProfit=0;
for(int i=0;i<len-1;i++){
int price=prices[i];
for(int j=i+1;j<len;j++){
int profit=prices[j]-price;
if(profit>maxProfit){
maxProfit=profit;
}
}
}
return maxProfit;
}
};
方法2
int maxProfit(vector<int> &prices) {
int len=prices.size();
if(len==0||len==1)
return 0;
int maxProfit=0;
int minPrice=prices[0];
for(int i=1;i<len;i++){
minPrice=min(minPrice,prices[i]); //找出最低价格
maxProfit=max(maxProfit,prices[i]-minPrice); //计算最大差值
}
return maxProfit;
}
股票问题:说你有一个数组,其中第i个元素是第i天给定股票的价格。设计一个算法来找到最大的利润,最多可以完成两个交易,原博地址:股票
解题思路:
方法1
用四个变量来表示俩次交易的买入卖出,遍历数组,比较每次交易的利益,保存最大的
方法2
左右扫描 分别计算出 i 之前 pre[]和 之后 post[] 的最大利润 ,在 i 处为分割 求出最大交易
题目描述
给定一个二叉树
struct TreeLinkNode {
TreeLinkNode *left;
TreeLinkNode *right;
TreeLinkNode *next;}
填充所有节点的next指针,指向它右兄弟节点。如果没有右兄弟节点,则应该将next指针设置为NULL。
初始时,所有的next指针都为NULL
注意:
你只能使用常量级的额外内存空间
可以假设给出的二叉树是一个完美的二叉树(即,所有叶子节点都位于同一层,而且每个父节点都有两个孩子节点)。
解题思路1:因为是完美二叉树,因此一个节点只有两种情况,为父节点时有2个孩子,为叶子结点时没有孩子。因此可以用递归的方式先序遍历所有节点,将左孩子的next指向右孩子,右孩子的next分情况讨论:
若node->next不存在,则右孩子的next设为NULL
若node->next存在,则右孩子的next指向父节点next的左孩子,即设为node->next->left
按照上述步骤遍历二叉树即可。
注意:递归会有个O(lgn)的默认递归栈空间,无法满足O(1)的空间复杂度要求
因为使用层次遍历的思路,递归不纳入常量空间限制,在leetcode中有说明
代码:
class Solution {
public:
void connect(TreeLinkNode *root) {
if (root == NULL)
return;
if (root->left!=NULL && root->right!=NULL)
root->left->next = root->right;
if (root->next != NULL &&root->right!=NULL){
root->right->next = root->next->left;
}
connect(root->left);
connect(root->right);
}
};
解题思路2 非递归
代码
class Solution {
public:
void connect(TreeLinkNode *root) {
if (!root)
return;
TreeLinkNode *p = root, *q;
while (p->left) {
q = p;
while (q) {
q->left->next = q->right;
if (q->next)
q->right->next = q->next->left;
q = q->next;
}
p = p->left;
}
}
};
题目描述
判断给定的二叉树是否是平衡的
在这个问题中,定义平衡二叉树为每个节点的左右两个子树高度差的绝对值不超过1的二叉树
解题思路:
平衡二叉树:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
解题思路:
主要方法是递归,平衡二叉树不仅左右两个子树的深度差不大于1,其左右子树也都是平衡二叉树。
判断以当前节点为根节点的树,其左右子树的深度差是否大于1
如果大于1,则证明不是平衡二叉树,直接返回false;
如果不大于1,则需要判断其左子树是否是平衡二叉树以及右子树是否是平衡二叉树(递归);
代码
class Solution {
public:
bool isBalanced(TreeNode *root) {
int depth = 0;
return isBalanced_helper(root, depth);
}
bool isBalanced_helper(TreeNode *root, int &depth) {
if(root == NULL){
depth = 0;
return true;
}
int left, right;
if(isBalanced_helper(root->left, left) && isBalanced_helper(root->right, right)){
if(abs(left - right) <= 1){
depth = 1 + max(left, right);
return true;
}
}
return false;
}
};
题目描述
给定一个二叉树,返回该二叉树层序遍历的结果,(从左到右,一层一层地遍历)
例如:
给定的二叉树是{3,9,20,#,#,15,7},
该二叉树层序遍历的结果是
[↵ [3],↵ [9,20],↵ [15,7]↵]
其中{1,#,2,3}的含义,用如下方法将二叉树序列化:
二叉树的序列化遵循层序遍历的原则,”#“代表该位置是一条路径的终结,下面不再存在结点。
例如:
1↵ / ↵ 2 3↵ /↵ 4↵ ↵ 5
上述的二叉树序列化的结果是:"{1,2,3,#,#,4,#,#,5}".
解题思路:层次遍历--使用队列
循环,一层一层的访问该层的所有节点,并保存到res里面
代码
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode *root) {
vector<vector<int>>res;
if (root==NULL)
return res;
queue<TreeNode*>store;
vector<int>cur;
store.push(root);
while(!store.empty()){
cur.clear();
int n = store.size();
for (int i = 0; i < n;++i){
TreeNode*node = store.front();
store.pop();
cur.push_back(node->val);
if (node->left!=NULL)
store.push(node->left);
if (node->right!=NULL)
store.push(node->right);
}
res.push_back(cur);
}
return res;
}
};
题目描述
给定一个值n,能构建出多少不同的值包含1...n的二叉搜索树(BST)?
例如
给定 n = 3, 有五种不同的二叉搜索树(BST)
解题思路:
二叉搜索树有个性质,就是左边的数都比根小,右边的数都比根大。另外,题目说明二叉树的节点是从1到n,所以我们能确定如果根为k,则根左边的数是1到k-1,根右边的数是k+1到n。还有一点技巧是,对于通过一个根来说,唯一二叉树的数量是其左子树的数量乘以右子树的数量,这是简单的乘法原理。并且,左右子树的形态数量是跟具体的数无关的,只跟这个树里有多少节点有关。而根可以选择从1到n的任意的数,唯一二叉树的总数,就是根为1到n的树相加。所以该问题化简为以k为根,其唯一左子树和右子树各有多少,这就是个动态规划的问题了。我们建立一个数组dp[i],代表节点数为i的唯一子树有多少个。显然dp[0]=dp[1]=1
思路解释:
考虑根节点,设对于任意根节点k,有f(k)种树的可能。
比k小的k-1个元素构成k的左子树。则左子树有f(k-1)种情况。
比k大的n-k个元素构成k的右子树。则右子树有f(n-k)种情况。
易知,左右子树相互独立,所以f(k)=f(k-1)f(n-k)。
综上,对于n,结果为k取1,2,3,...,n时,所有f(k)的和。
代码思路:
根据上述思路可以用简单的递归方法快速解决。
现在考虑非递归解法,用数组记录每个f(i)的值,记f(0)=1,f(1)=1。
根据公式:f(k)=f(k-1)f(n-k),访问数组中的元素。
循环求和,结果更新到数组中。
代码
//选择一个节点,它的左右子树个数的乘积就是总的个数,可以递归解决,注意纯递归时间复杂度太大了
class Solution {
public:
int numTrees(int n) {
if(n <= 1)
return 1;
int uniqueBST = 0;
for(int i = 1; i <= n; i++){
uniqueBST += numTrees(i-1)*numTrees(n-i);
}
return uniqueBST;
}
};
参考地址:leetcode牛客网148