文章目录
- leetcode 3 无重复字符的最长子串 【经典】
- leetcode 11 盛最多水的容器
- leetcode 160 相交链表
- 两数相加 I &&两数相加 II && 面试题 02.05 链表求和
- leetcode 1884 鸡蛋掉落 M
- 统计一个数字在有序数组中出现的次数
- leetcode 101 对称二叉树
- leetcode 121/122 买卖股票的最佳时机 I、II
- leecode 409 最长回文串
- leetcode 19 剑指offer 021 删除链表倒数第N个节点
- 返回链表倒数第k个节点
- leetcode 3 剑指offer 48 无重复字符的最长子串
- 剑指offer 34 二叉树中和为某一值的路径 & leetcode 257 二叉树的所有路径 && leetcode 113 路径总和 II`【经典】`
- leetcode 112 路径总和 I
- leetcode 437 【路径总和 III】
- leetcode 687 【二叉树中的最长同值路径】
- leetcode 124 【最长的路径和 H】
- 二叉树的直径
- leetcode 257 二叉树的所有路径
- leetcode 46 全排列 I && 47 全排列 II
- 字符串的全排列
- leetcode 78 子集问题
leetcode 3 无重复字符的最长子串 【经典】
题目要求:
给定一个字符串 s,请你找出其中不含重复字符的最长字串的长度;
例如:
输入:s="abcabcbb"
输出:3
无重复字符的最长子串为"abc" 长度为3
代码实现:
哈希解法–滑动窗–左右指针
无重复的意思就是你找到这个字符串里的字符只出现一次
;
小注:
关于如何实现无重复字符子串是最大的?
在进入while循环之前,res始终在保持更新为一个最大的值;
举个例子 给定字符串是 1 2 3 2在遍历到第二个2之前,res已经更新为3这是一个最大的长度,因为之后再遍历就会遇到和之前相同的数字,然后进入while循环,开始递减出现的次数
,直到那个出现次数大于1的元素的出现次数小于1为止;
思路上的理解:
可以近似的认为 win是个滑动窗口 left是滑窗的左边界,right是滑窗的有边界;这个滑窗内的元素只允许出现一次
;
每当这个滑窗内出现的元素大于一次时,就开始减少其出现的次数,直到只出现一次;
两个相同的元素不可能同时出现在一个滑窗内,滑窗的大小是两个相同元素之间存在的可能的最大值
;
//无重复字符的最长子串
class Solution
{
public:
int lengthOfLongestSubstring(string s)
{
unordered_map<char,int>win;//可以近似的认为 win是个滑动窗口 left是滑窗的左边界,right是滑窗的右边界;这个滑窗内的元素只允许出现一次;
int left = 0, right = 0;
int res = 0;
while (right < s.size())//用right指针遍历数组,将所有元素出现的次数在win中记录下来;
{
char c = s[right];
right++;
win[c]++;
while (win[c] > 1)//每遇到重复的元素 就开始缩小窗口的大小
{
char d = s[left];//
left++;
win[d]--;
}
res = max(res, right - left);//在遇到重复的元素之前维护一个最长的子串长度
}
return res;
}
};
leetcode 11 盛最多水的容器
左右指针
解法
leetcode 160 相交链表
题目要求:
给你两个单链表的头节点 headA 和 headB
,请你找出并返回两个单链表相交的起始节点
。如果两个链表不存在相交节点,返回 null 。
注意:函数返回结果后,链表必须保持其原始结构;
思路:
链表相交就是在某个位置,两链表有相同的节点;
创建两个指针分别指向两链表的头节点,让这两个指针分别遍历各自的链表,当遍历完自己的链表后,开始遍历另一个链表,当这两个指针指向的位置相同时,返回这个相同的节点即可
;
struct ListNode
{
int val;
ListNode* next;
ListNode(int x):val(x),next(NULL){}
};
class Solution
{
public:
ListNode* getInstersectNode(ListNode* headA, ListNode* headB)
{
ListNode* a = headA;//a指针用来遍历A链表
ListNode* b = headB;//b指针用来遍历B链表
while (a != b)//两指针没相遇的时候 各自遍历自己的链表
{
a = a!=nullptr ? a->next :headB;
b = b!= nullptr ? b->next : headA;
}
return a;
}
};
两数相加 I &&两数相加 II && 面试题 02.05 链表求和
相似题目 两数之和 II
两数相加 I 和链表求和是一样的。
题目要求:
给定两个用链表表示的数字,每个节点包含一个数位;这些数位是反向存放
的,也就是个位排在链表首部;编写函数求这两个整数的和,并用链表的形式返回结果;
输入:(7->1->6)+(5->9->2) 即 617+295
输出:2->1->9 即912
两数之和 I 的简单解法 - 直接相加减
class Solution
{
public:
ListNode*addTwoNumbers(ListNode*l1,ListNode*l2)
{
auto dummyHead=new ListNode(-1);
ListNode*p=dummyHead;//p指针将相加之后的数字依次连接
int sum=0;
while(l1||l2||sum)
{
if(l1)sum+=l1->val;l1=l1->next;
if(l2)sum+=l2->val;l2=l2->next;
p->next=new ListNode(sum%10);//用余数创建新的节点
p=p->next;
sum/=10;//有进位则将进位数带入下一次的相加;没有进位则为0重新进行相加计算
}
return dummyHead->next;
}
};
算法思想:
先将个位上的数字进行逐位相加,并用一个指针指向相加后的数字。最后用一个指向新链表头节点的指针将这些数字串联起来;
C++实现:
struct ListNode
{
int val;
ListNode* next;
ListNode(int x):val(x),next(NULL){}
};
//链表中的节点反向存储
class Solution
{
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2)
{
ListNode* dummyHead = new ListNode(-1);//定义一个新链表
ListNode* cur = dummyHead;//创建cur指针指向头节点
int sum = 0;//表示和 这个和是每一个对应位置上的数字相加的结果
int carry = 0;//表示进位
int res=0;//表示余数
while (l1 || l2 || carry)
{
sum = 0;//每一次对应位上相加 都先将sum置为0 确保每次计算都是针对一个对应位置上的数字
if (l1)
{
sum += l1->val;
l1 = l1->next;
}
if (l2)
{
sum += l2->val;
l2 = l2->next;
}
sum += carry;//求和
res=sum%10;//求余数
ListNode* temp = new ListNode(res);//以余数创建新的节点 表示相加后的结果
carry = sum / 10;//求进位
cur->next = temp;//用cur指针依次将相加后的数字连接在一起
cur = cur->next;//继续向下遍历
}
return dummyHead->next;// head指针最后指向新链表的最后一个数字;
}
};
//=========================================================================================
//链表中的节点正向存储--也就是两数相加II的做法
class Solution
{
public:
ListNode* addInList(ListNode* head1, ListNode* head2)
{
//先用栈实现节点的反向 再按反向存储的那样进行处理
stack<int>s1, s2;
while (head1 || head2)
{
if (head1)
{
s1.push(head1->val);
head1 = head1->next;
}
if (head2)
{
s2.push(head2->val);
head2 = head2->next;
}
}
int sum=0;//储存和
int carry = 0;//存储进位
int cur=0;//存储余数
ListNode*temp=nullptr;//初始化一个空指针
while (!s1.empty() || !s2.empty() || carry != 0)
{
int a=s1.empty()?0:s1.top();
int b=s2.empty()?0:s2.top();
if(!s1.empty())s1.pop();
if(!s2.empty())s2.pop();
int sum=a+b+carry;
carry=sum/10;
cur=sum%10;
auto node=new ListNode(cur);
node->next=temp;
temp=node;
}
return temp;
}
};
leetcode 1884 鸡蛋掉落 M
题目要求:
给你两枚相同的鸡蛋 ,和一栋从1层到n层楼的建筑;
已知存在楼层f,
满足0<=f<=n ,任何从高于f的楼层落下的鸡蛋都会碎,从f楼层或比它低的楼层落下的鸡蛋都不会碎
;
每次操作,你可以取一枚没有碎的鸡蛋并把它从任意楼层x扔下(1<=x<=n) 如果鸡蛋碎了,你就不能再次使用它
,如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中重复使用这枚鸡蛋;
请你计算并返返回要确定f
确切的值的最小操作次数是多少??
算法思想–动态规划:
用dp[i][j]表示有i+1枚鸡蛋时,验证j层楼需要的最小操作次数
,我们可以分开i=0和i=1两种情况分析:
i=0
即只剩一枚鸡蛋,此时我们必须从第一层逐层往上试
,确保在找到目标楼层f前鸡蛋不会碎,因此在找到f前我们会操作f次,因此总是存在 dp[0][j]=j;
i=1
即剩两枚鸡蛋 ,对于任意j,第一次操作可以选在[1,j]范围内的任意楼层,如果鸡蛋在k层丢下后摔碎,接下来转化为i=0时验证k-1层需要的次数,即dp[0][k-1] ;总操作次数为dp[0][k-1]+1;
如果鸡蛋在k层摔下后没碎,接下来问题转换成i=1时验证j-k层需要的次数,即dp[i][j-k] 总操作次数为dp[1][j-k] ;
代码实现:
class Solution {
public:
int twoEggDrop(int n) {
vector<vector<int>> dp(2, vector<int>(n + 1, INT_MAX));
dp[0][0] = dp[1][0] = 0;//需要验证的楼层为0时 操作次数为0
for (int i = 1; i <= n; ++i) { //只剩一枚鸡蛋时 验证楼层i需要的操作次数
dp[0][i] = i;
}
for (int i = 1; i <= n; ++i) { //剩两枚鸡蛋的情况
for (int j = 1; j <= i; ++j) {
dp[1][i] = min(dp[1][i], max(dp[0][j - 1] + 1, dp[1][i - j] + 1));
}
}
return dp[1][n];
}
};
将dp[0][j]优化为一维dp
class Solution {
public:
int twoEggDrop(int n) {
vector<int> dp(n + 1, INT_MAX);
dp[0] = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= i; ++j) {
dp[i] = min(dp[i], max(j, dp[i - j] + 1));
}
}
return dp[n];
}
};
统计一个数字在有序数组中出现的次数
题目要求:给定一个有序数组nums和一个指定的数字k
,请统计在数组中这个数字出现的次数,返回出现的次数即可;
代码实现:
//统计一个数字在有序数组中出现的次数
class Solution
{
public:
int singleNumber(vector<int>& nums, int k)
{
int m = 0;//创建一个变量用于存储出现的次数;
for (int num : nums)
{
while (num = k)
{
m++;
break;
}
}
return m;
}
};
leetcode 101 对称二叉树
题目要求:
给定一颗二叉树 判定是不是对称二叉树
代码实现:
//二叉树节点的定义方式
struct TreeNode
{
int val;
TreeNode* left;
TreeNode*right;
TreeNode(int x):val(x),left(nullptr),right(nullptr){}
};
//判断一棵树是对称二叉树
//对称的二叉树只要有一个子树为空 就不可能是对称的
class Solution
{
public:
//递归函数
bool compare(TreeNode* left, TreeNode* right)
{
//首先排除空节点的情况
if (left == NULL && right != NULL)return false;
else if (left != NULL && right == NULL)return false;
else if (left == NULL && right == NULL)return true;
//排除了节点为空的情况 再排除数值不相等的情况
else if (left->val != right->val)return false;
//此时就是左右节点都不为空且数值相同的情况,然后进行递归;
bool outside = compare(left->left, right->right); //递归判断外侧树是否相等
bool inside = compare(left->right, right->left); //递归判断内侧树是否相等
bool isSame = outside && inside;//内外侧都相等,才对称
return isSame;
}
bool isSymmetric(TreeNode* root)
{
if (root == NULL)return true;
return compare(root->left, root->right);
}
};
leetcode 121/122 买卖股票的最佳时机 I、II
这个题目的要求是买了股票之后,可以在同一天买入也可以在当天卖出
;统计最大利润;
在每一天,你可能会决定购买和/或出售股票。购买和出售的次数可以是多次
;
你在任何时候最多只能持有 一股 股票。你也可以购买它,然后在同一天出售
。
返回 你能获得的 最大 利润 ;
例如:
输入:prices=[7,1,5,3,6,4]
输出:7
即 在第二天(价格为1)
的时候买入,在第三天(价格为5)
的时候卖出,随后在第四天(价格为3)
的时候买入,在第五天(价格为6)
的时候卖出 ,最大利润为 (5-1)+(6-3)=7
C++实现:
class Solution
{
public:
int maxProfit(vector<int>& prices)
{
int res = 0;//初始化一个变量用于统计利润
for (int i = 1; i < prices.size(); i++)//从1开始遍历
{
res += max(prices[i] - prices[i - 1], 0);//累加每一个正的利润值,累加的时候遇到负数不累加
}
return res;//返回最终的利润
}
};
题目要求:
给定一个数组 prices
,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格
。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。只能购买一次和出售一次
;
设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
实例:
输入:[7,1,5,3,6,4]
输出:5
在第二天买入,在第五天卖出 最大利润: 6-1 = 5;
算法思路:
代码实现:
class Solution {
public:
int maxProfit(vector<int>& prices)
{
int res=0;//初始化一个变量用于统计利润
for(int i=1;i<prices.size();i++)//从1开始遍历
{
res=max(prices[i]-prices[i-1],res);//prices[i]是遍历到的值,是会时刻更新的,prices[i-1]始终是前面已经遍历到的元素的最小值
prices[i]=min(prices[i],prices[i-1]);//始终保证一个最小值向后推移,来和后面的较大值进行比较;
}
return res;//返回最后的利润
}
};
leecode 409 最长回文串
题目要求:
给定一个包含大写字母和小写字母的字符串s
,返回通过这些字母构造成的最长的回文串的长度;有大小写区别;
例如:
输入:s = "abccccdd"
输出:7
解释:
我们可以构造的最长的回文串是"dccaccd", 它的长度是 7
。
代码实现:
//最长回文串
//假设某个字符出现了v次,我们可以使用该字符v/2*2次;在回文串的左右两侧分别放置v/2个字符;
//例如一个字符出现了5次,那么我们可以使用这个字符4次,左右各使用2次;
class Solution
{
public:
int longestPalindrome(string s)
{
unordered_map<char, int>count;//哈希表
int ans = 0;//用ans存储回文串的长度
for (char c : s)
{
++count[c];//将这个字母的出现次数加一
}
//以上已经完成了对所有字符出现次数的统计
//加下来对这些字符出现的次数进行判断 一个字符要么出现奇数次要么出现偶数次
for (auto p : count)
{
int v = p.second;// 用v标识某个字符出现的次数
ans += v / 2 * 2;// 回文串每次增加v/2*2 ,ans一直是偶数 ;
//这里同时处理了字符出现偶数次数的情况;直接加上这个偶数次作为回文串的长度即可
if (v % 2 == 1 && ans % 2 == 0)//处理某个字符出现奇数次的情况 ;在此之前,回文串的长度一直是偶数;
{
++ans;//在原先就是偶数的基础上加一变成奇数;加的这一次是奇数个字符中间的那个字符,奇数字符比偶数多一个
}//理解一下:回文串里只能有一个奇数个数的字母,若有两个以上奇数个数的字母 肯定构不成回文串
}
return ans;
}
};
leetcode 19 剑指offer 021 删除链表倒数第N个节点
题目要求:
给定一个链表,删除链表的倒数第n个节
点,并且返回原链表的头节点;
例如
输入:head=[1,2,3,4,5] n=2
输出: [1,2,3,5]
C++实现
//删除链表倒数第n个节点
//快慢指针方法
class Solution
{
public:
ListNode* removeNthFromEnd(ListNode* head, int n)
{
ListNode* dummyhead = new ListNode(0);//因为不知道真正的头节点 所以设置一个伪头节点之后 会方便以后的操作
dummyhead->next = head;
ListNode*slow= head;//快慢指针
ListNode*fast = head;
for (int i = 0; i < n; i++)//快指针先移动到链表第n个位置
{
fast = fast->next;
}
fast = fast->next;//此时fast指针指向第n+1个位置的元素
while (fast != NULL)//从第n+1个位置开始快慢指针同时前进 当快指针指向nullptr时 慢指针指向的下一个位置就要删除的节点;可以理解是数学中找规律找到的;
{
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;//slow指针指向的下一个指针就是我们要删除的节点
return dummyhead->next;//返回头节点即可
}
};
返回链表倒数第k个节点
快慢指针
解法
class Solution
{
public:
int kthToLast(ListNode* head, int k)
{
ListNode* fast= head, * slow = head;//因为不需要返回真正的节点 因此也就不需要设置一个伪头节点来指示真正的头节点
for (int i = 0; i < k; i++)
{
fast = fast->next;//快指针先走k个节点
}
while (fast!= nullptr)//快慢指针一起前进 当快指针指向空的时候 返回慢指针指向的节点值即可;
{
fast = fast->next;
slow = slow->next;
}
return slow->val;
}
};
leetcode 3 剑指offer 48 无重复字符的最长子串
题目要求:
给定一个字符串 s ,请你找出其中不含重复字符的最长子串的长度;
例如
输入:s="abcabcbb"
输出:3
无重复字符的最长子串是"abc"
C++ 实现:
//不含重复字符的最长子串
class Solution
{
public:
int lengthOfLongestSubstring(string s)
{
unordered_map<char, int>win;//哈希集合
int left = 0, right = 0;
int res = 0;
while (right < s.size())
{
char c = s[right];//c 表示右边界元素
right++;
win[c]++;
while (win[c] > 1)//遇到出现次数大于1次的数字,将其在滑窗内出现的次数减一;
{
char d = s[left];
left++;
win[d]--;
}
res = max(res, right - left);//res始终保持一个较大值
}
return res;
}
};
剑指offer 34 二叉树中和为某一值的路径 & leetcode 257 二叉树的所有路径 && leetcode 113 路径总和 II【经典】
题目要求:
给你一个二叉树的根节点**root
和一个整数目标和targetSum
**,找出所有从根节点到叶子节点路径总和为给定目标和的路径;
例如:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]
C++ 实现
//二叉树中和为某一值的路径
//为什么需要回溯操作 ?
//因为path不能一直加入节点,当到达二叉树中的一条边的尽头时,还要删除节点,然后才能加入新的节点;
class Solution
{
private:
vector<vector<int>>res;//二维数组存放多条路径
vector<int>path;//一维数组存放单条路径的各个节点
public:
vector<vector<int>>pathSum(TreeNode* root, int target)
{
if (!root)return {};
recursion(root, target);//递归实现
return res;
}
void recursion(TreeNode* root, int target)//递归函数
{
if (!root)return;//递归终止
path.push_back(root->val);
target -= root->val;//每存入一个节点 就在总目标和中减去这个节点
if (target == 0 && !root->left && !root->right)//找到符合条件的一条路径
{
res.push_back(path);//将该路径存放到容器中
}
recursion(root->left, target);//递归遍历左子树
recursion(root->right, target);//递归遍历右子树
path.pop_back();//回溯 因为我们需要记录路径 所以需要回溯操作来回退一条路径从而进入另一条路径
}
};
leetcode 112 路径总和 I
给定二叉树的根节点root
和一个表示目标和的整数targetSum
,判断该树中是否存在根节点到叶子节点的路径,使得这条路径上所有根节点值相加等于目标和targetSum,存在得话返回true即可;
//比较容易理解的做法
//参考上个题目的解题思路进行求解
//判断一棵树中是否存在从根节点到叶子节点的路径使得路径之和等于给定的值
class Solution
{
public:
bool hasPathSum(TreeNode* root, int targetSum)
{
if (root == nullptr)return false;
return dfs(root, targetSum);
}
bool dfs(TreeNode* root, int target)
{
if (root == nullptr)return false;
target -= root->val;
if (target == 0 && root->left == nullptr && root->right == nullptr)
{
return true;
}
if(dfs(root->left, target))return true;//在左子树中可以找到这样的路径
if(dfs(root->right, target))return true;//在右子树中可以找到这样的路径
return false;//因为不是一个个向容器中添加路径节点 因此是不需要回溯的
}
};
//=================代码随想录解法============================================================
class Solution
{
private:
bool traversal(TreeNode*node, int target)//树的遍历函数
{
if (!node->left && !node->right && target == 0)return true;//遇到符合条件的叶子节点 直接返回
if (!node->left && !node->right)return false;//到达叶子节点 但是没找到和为targetSum的路径
if (node->left)// 遍历左子树
{
target -= node->left->val;//在目标值里减去当前遍历到左子树的节点
if (traversal(node->left, target))return true;//继续向下递归
target += node->left->val;//回溯
}
if (node->right)//遍历右子树
{
target -= node->right->val;//在目标值里减去当前遍历到的右子树的节点
if (traversal(node->right, target))return true;//继续向下递归
target += node->right->val;//回溯
}
return false;
}
public:
bool hasPathSum(TreeNode* root, int targetSum)
{
if (root == nullptr)return false;
return traversal(root, targetSum - root->val);
}
};
leetcode 437 【路径总和 III】
这个题目和下面的最长同值路径和最大路径和是同类型
的;
给你一个二叉树的根节点root
,和一个整数targetSum
,求该二叉树里节点值之和等于targetSum 的路径的数目
;
注意 : 路径不需要从根节点开始,也不需要在叶子节点结束,但是路径必须是向下的;
这里是和上面两个题的区别
例如:
输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示。
核心思路----递归里面嵌套递归
以遍历到的每一个节点为节点进行深度搜索,将得到的符合条件的路径进行累加 ,返回累加之后的结果即可;
两个递归遍历
组成整体框架:
大的递归遍历是统计整个树中符合路径总和为val的路径数目;
小的遍历是统计以大树中遍历到的每个节点为根节点,那些小的树中符合路径总和为val的路径数目;所有小的树的符合条件的路径加起来就是大的遍历需要返回的结果;
class Solution
{
public:
int pathSum(TreeNode* root, int target)
{
if (!root)return 0;
int res = dfs(root, target);//以root为起点向下找满足路径总和为target的路径
res += pathSum(root->left, target);//以root->left为起点向下找满足路径总和为target的路径
res += pathSum(root->right, target);//以root->right为起点向下找满足路径总和为target的路径
return res;//返回最终的路径数目
}
int dfs(TreeNode* root, int target) //对于单个起点的处理逻辑
{
if (!root)return 0;
int ret = 0;
if (root->val == target)ret++;//找到一条符合条件的路径
ret += dfs(root->left, target - root->val);
ret += dfs(root->right, target - root->val);
return ret;
}
};
//比较容易理解的做法
///=======和上面一样的思路 但是超时 ===================================================================
//双重递归的解法
//判断在一棵树中存在路径等于给定值的数目
//注意:这些路径可以从树中的任意节点出发 任意节点结束,没有必须要求是从根节点到叶子节点
class Solution
{
int ans = 0;
public:
int pathSum(TreeNode* root, int targetSum)
{
if (!root)return 0;
dfs(root, targetSum);//以root为路径起点进行查找
pathSum(root->left, targetSum);//以root->left为路径起点进行查找
pathSum(root->right, targetSum);//以root->right为路径起点进行查找
return ans;
}
void dfs(TreeNode* root, int targetSum)
{
if (!root)return;
targetSum -= root->val;
if (targetSum == 0)ans++;//目标和为0的时候表示找到符合条件的一条路径
dfs(root->left, targetSum);
dfs(root->right, targetSum);
}
};
leetcode 687 【二叉树中的最长同值路径】
给定一个二叉树的 root
,返回 最长的路径的长度
,这个路径中的 每个节点具有相同值
。 这条路径可以经过也可以不经过根节点。
两个节点之间的路径长度 由它们之间的边数表示。
代码实现:
//687 最长同值路径
//最长路径的长度
//对于任意一个节点都从根节点开始比较 比较根节点和左右子树节点的值是否相等
class Solution
{
int ans = 0;
public:
int longestUnivaluePath(TreeNode* root)
{
if (!root)return 0;
dfs(root);
return ans;
}
int dfs(TreeNode* root)
{
if (!root)return;
int ansl = dfs(root->left);//左子树的同值路径值
int ansr = dfs(root->right);//右子树的同值路径值
if (root->left && root->val == root->left->val)ansl++;//左子树存在并且左子树节点值等于根节点值
else ansl= 0;
if (root->right && root->val == root->right->val)ansr++;//右子树存在并且右子树节点值等于根节点值
else ansr = 0;
ans = max(ans, ansl+ansr);//最终的最长同值路径是左子树最长同值路径和右子树最长同值路径之和;
return max(ansl,ansr);//每次向上返回的是左子树最长同值路径和右子树最长同值路径中的较大值;
}
};
leetcode 124 【最长的路径和 H】
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。
该路径 至少包含一个 节点,且不一定经过根节点
。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
//二叉树的最大路径和 路径是任意指定起点和终点的
class Solution
{
public:
int max_sum = INT_MIN;
//后序遍历树,返回经过root的单边最大路径和,并维护整棵树的最大路径和
int dfs(TreeNode* root)
{
if (!root) return 0;
//深度优先,获取左右分支的最大路径和,本结点作为联络点
int left_max = max(0, dfs(root->left));//对于负数 直接舍弃 不计入
int right_max = max(0, dfs(root->right));
int temp_sum = left_max + root->val + right_max;由于路径最大的一种可能为left->node->right,而不向root的父结点延伸
max_sum = max(max_sum, temp_sum);//更新最大值,全是负数结点的情况,也会在这里更新最大的负数
return root->val + max(left_max, right_max); //向node的父结点返回经过node的单边分支的最大路径
}
int maxPathSum(TreeNode* root)
{
dfs(root);
return max_sum;
}
};
二叉树的直径
给定一颗二叉树,计算其直径长度;
一颗二叉树的直径长度是是任意两个节点路径长度的最大值
,这条路径可能穿过也可能不穿过根节点。
class Solution
{
public:
int res = 0;//全局变量 两个函数里共享这个变量
int diameterOfBinaryTree(TreeNode* root)
{
maxPath(root);
return res;
}
int maxPath(TreeNode* root)
{
//递归终止条件是到达叶子节点
if (!root->left && !root->right)return 0;//这里的递归终止条件不是 !root 因为单个节点的路径长也是0
int left = root->left ? maxPath(root->left) + 1 : 0;//判断左子树节点是否为空 从而更新左边最长路径/每向下遍历一个节点 深度就加一
int right = root->right ? maxPath(root->right) + 1 : 0;
res = max(res, left + right);//更新全局变量 维护一个最大值;每遇到一个节点 就计算其左子树深度+右子树深度;
return max(left, right);//每一层的向上的返回值是左右路径中的较大值
}
};
leetcode 257 二叉树的所有路径
题目要求 :
给你一个二叉树的根节点root
,按任意顺序返回所有从根节点到叶子节点的路径;
节点之间用->
进行连接;
C++实现:
//返回二叉树所有的路径
class Solution
{
vector<string>res;//全局变量
string path;
public:
vector<string>binaryTreePaths(TreeNode* root)
{
dfs(root);
return res;
}
void dfs(TreeNode* root)
{
if (root != nullptr)
{
path += to_string(root->val);
if (root->left == nullptr && root->right == nullptr)
{
res.push_back(path);
}
else
{
path += "->";
dfs(root->left);
dfs(root->right);
}
}
}
};
//===============================================================================
//返回二叉树中的所有路径
class Solution
{
public:
vector<string>res;
vector<string>findallpath(TreeNode* root)
{
if (!root)return {};
dfs(root,"");
return res;
}
void dfs(TreeNode* root,string path)
{
if (!root)return;
path += to_string(root->val);
if (root->left == nullptr && root->right == nullptr)
{
res.push_back(path);
return;
}
if (root->left)dfs(root->left, path += "->");
if (root->right)dfs(root->right, path += "->");
}
};
leetcode 46 全排列 I && 47 全排列 II
题目要求:
给定一个不包含重复数字的序列nums
按任意顺序返回所有不重复的全排列;
例如
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
算法图解:
C++实现:
//递归+回溯 的解法
//因为是不包含重复元素的 所以用一个used数组来标记在所找的一个集合中当前元素是否被使用过
class Solution
{
public:
vector<vector<int>>res;
vector<int>path;
void backtracking(vector<int>& nums, vector<bool>& used) //used数组用来标记当前元素是否被使用过
{
//递归终止的条件
if (path.size() == nums.size())//找到符合条件的数组 将其放入容器中
{
res.push_back(path);
return;//当深度搜索到底时 开始向上回溯 一直向上 每上一层 就进行一层的回溯操作---删除尾节点元素,将used数组置为false
}
for (int i = 0; i < nums.size(); i++)//for循环 横向遍历 分别遍历得到 以1 2 3开头 的数字组合
{
if (used[i] == true)continue;//当前元素已经使用过 直接跳出当前循环 continue语句的作用是跳过本次循环体中余下尚未执行的语句,立即进行下一次的循环条件判定,可以理解为仅结束本次循环。
used[i] = true;//每使用一个元素就将其标记为true 标识已经用过了
path.push_back(nums[i]);
backtracking(nums, used);//递归 纵向遍历
path.pop_back();//回溯 继续向上遍历
used[i] = false;
}
}
vector<vector<int>>permute(vector<int>& nums)//排列函数
{
vector<bool>used(nums.size(), false);//初始化数组的大小和初始值 used数组用来标记当前遍历到的元素是不是被使用过;
backtracking(nums, used);//递归
return res;
}
};
47
给定一个可能包含重复元素的序列nums
,按任意顺序返回所有不重复的全排列;
相比46题 涉及一个去重的问题;
代码实现:
class Solution
{
private:
vector<vector<int>>res;
vector<int>path;
public:
void backtracking(vector<int>& nums, vector<bool>used)
{
if (path.size() == nums.size())
{
res.push_back(path);
return;
}
for (int i = 0; i < nums.size(); i++)
{
// used[i - 1] == true,说明同一树枝nums[i - 1]使用过
// used[i - 1] == false,说明同一树层nums[i - 1]使用过
// 如果同一树层nums[i - 1]使用过则直接跳过
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false)continue;//去重操作 对于重复的元素 直接将重复的前一个元素跳过 不遍历 不做任何操作
if (used[i] == false)
{
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
}
vector<vector<int>>permuteUnique(vector<int>& nums)
{
sort(nums.begin(), nums.end());//排序之后更有利于去重
vector<bool>used(nums.size(), false);
backtracking(nums, used);
return res;
}
};
字符串的全排列
例如:
输入 abc
返回 abc acb bac bca cab cba
C++递归和回溯实现:
//字符串的全排列
class Solution
{
vector<string>res;
string path="";
public:
vector<string>permutation(string s)
{
if (s.size() == 0)return res;
vector<bool>used(s.size(), false);
sort(s.begin(), s.end());
dfs(s, used);
return res;
}
void dfs(const string& s, vector<bool>& used)
{
if (path.size() == s.size())
{
res.emplace_back(path);
return;
}
for (int i = 0; i < s.size(); i++)
{
if (used[i] == true)continue;//标记在该集合中当前元素是否被使用过
if (i > 0 && s[i] == s[i - 1] && used[i - 1] == false)continue;//对数组中的元素去重
used[i] = true;
path += s[i];
dfs(s, used);
path.pop_back();
used[i] = false;
}
}
};
leetcode 78 子集问题
题目要求:给定一个整数数组nums
,数组中的元素互不相同,返回该数组所有可能的子集;
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
C++实现:
//返回无重复元素数组里面所有可能的子集
class Solution
{
private:
vector<vector<int>>res;//创建一个二维数组存储 所有的子集
vector<int>path;//存储某一个集合
void backtracking(vector<int>& nums, int startIndex) //子集问题通常有startIndex来标记子集起始元素的下标
{
res.push_back(path);//对于任意一个子集 都进行存储
if (startIndex >= nums.size())return;//递归终止条件
for (int i = startIndex; i < nums.size(); i++)
{
path.push_back(nums[i]);//存储一个子集的元素
backtracking(nums, i + 1);
path.pop_back();//回溯
}
}
public:
vector<vector<int>>subsets(vector<int>& nums)//返回一个二维数组
{
res.clear();
path.clear();
backtracking(nums, 0);//递归实现
return res;
}
};