概述:
最长回文子串,用动态规划思路来解,初始化状态。
组合总和,用回溯法解决,根为target,DFS
根据前中序构造二叉树,vector做参数,并不是传的指针
字符串转化成整数,用到了stringstream,对类型进行转换,也处理了溢出
盛最多水的容器,用到了双指针,指向的值较小的那个指针向较大的指针移动
三数之和,采用三指针的做法,排序,固定一个,剪枝,去重,一个都不能少
1.最长回文子串
题目描述:给定一个字符串s,假定s最大为1000,找出他的最长回文子串。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
思路:这里使用动态规划的思路来解。核心如图,
1.参数过滤,如果字符串长度为0或1,那么s本身即为回文子串,返回自身。if(len == 0||len ==1) return s;
2.对基本的回文子串(包括1个和两个的情况)赋初值。for(int i=0;i<len;++i){dp[i][i] =1;if(i<len-1&&s[i] == s[i+1]){dp[i][i+1]=1;max = 2;start = i;}}
3.状态转移。前面已经处理了长度为1和2的情况,所以这次从长度为3开始遍历。最长为整个长度len。
for(int n = 3;n<len;++n){}
4.设置好边界条件,写入核心。for(int i = 0;i+n-1<len;++i){int j = n+i-1;if(s[i] == s[j]&&dp[i+1][j-1] == 1){dp[i][j] = 1;start = i;max = n;}}
string longestPalindrome(string s) {
int len = s.size();
if(len == 0|| len==1) return s;
int start = 0,max = 1; vector<vector<int>> dp(len,vector<int>(len));
for(int i = 0;i<len;++i){ //初始化状态
dp[i][i] = 1; //赋初值
if(i<len-1 && s[i] == s[i+1]){ //中间有两个相同字符
dp[i][i+1] = 1;
max = 2;
start = i;
}
}
for(int n = 3;n<=len;++n){ //j为右边字符位置,i为左边,n为回文长度
for(int i = 0;i+n-1<len;++i){
int j = n+i-1;
if(s[i]==s[j] && dp[i+1][j-1] == 1){ //dp[i][j]表示从si到sj的字符串是否是回文子串
dp[i][j] = 1;
start = i;
max = n;
}
}
}
return s.substr(start,max);
}
2.组合总和第39题
题目描述:给定一个无重复元素的数组candidate和一个指定的数字target。 找出数组中所有可以令数字之和为target的组合。每个数字可以无限选取。
示例 1:
输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
参考:https://leetcode-cn.com/problems/combination-sum/solution/c-by-xyw-4-5/
回溯法思路:以target为根节点,每个分支做减法,减到0,结算。减到负数,剪枝。
1.对vector中元素排序 sort(candidates.begin(),candidates.end());并给自己的成员变量赋初值。
2.调用DFS,如果target被减为0,那么存储path,并返回。if(target == 0){temp.push_back(path);return ;}
3.剪枝,递归。for(int i = start;i<candidates.size()&&target-candidates[i]>=0;++i){path.push_back(candidates[i]);DFS(i,target-candidates[i]);path.pop_back();}
class Solution {
private:
vector<int> path;
vector<vector<int>>temp;
vector<int> candidates;
public:
void DFS(int start,int target){
if(target == 0){
temp.push_back(path);
return ;
}
for(int i = start;i<candidates.size()&&target - candidates[i]>=0;++i){
path.push_back(candidates[i]);
DFS(i,target-candidates[i]);
path.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
std::sort(candidates.begin(),candidates.end());
this->candidates = candidates;
DFS(0,target);
return temp;
}
};
3.根据前序中序序列构造二叉树
题目描述:根据二叉树的前序和中序序列构造二叉树,假设树中没有重复的元素。
注意:数组做形参时,自动退化为指针。。。。vector做参数时,不会。
思路:1.找出根节点在中序遍历中的位置。
2.计算左右子树的长度,并根据该长度对前序中左右子树划分。
BinaryTreeNode* ConstructCore(int* preorder, int* preOrderLast, int* inorder, int* inOrderLast) //传进来的是可变长数组
{
//前序遍历的第一个数字是根节点的值
BinaryTreeNode* root = new BinaryTreeNode();
root->data = preorder[0];
root->pLeft = root->pRight = nullptr;
if (preorder == preOrderLast)
{
if (inorder == inOrderLast && *preorder == *inorder)
{
return root;
}
else
throw std::exception("Invalid Input22222222222.");
}
int* rootinOrder = inorder;
while (rootinOrder < inOrderLast && *rootinOrder != root->data)
{
++rootinOrder;
cout << "rootInorder:" << *rootinOrder << '\n';
}
if (rootinOrder == inOrderLast && *rootinOrder != root->data)
{
throw std::exception("Invalid input111111111.");
}
int LeftLength = rootinOrder - inorder;
int* LeftPreOrderLast = preorder + LeftLength;
if (LeftLength > 0)
{
root->pLeft = ConstructCore(preorder+1,LeftPreOrderLast,inorder,rootinOrder - 1);
}
if (LeftLength < preOrderLast - preorder)
{
root->pRight = ConstructCore(LeftPreOrderLast + 1, preOrderLast, rootinOrder + 1, inOrderLast);
}
return root;
}
BinaryTreeNode* ReBuildBinaryTree(int* preOrder,int* inOrder,int Length)
{
if (preOrder != nullptr && inOrder != nullptr && Length > 0)
{
return ConstructCore(preOrder, preOrder + Length - 1, inOrder, inOrder + Length - 1);
}
else
return NULL;
}
4.字符串转换成整数
题目描述:实现一个atoi函数,将字符串转化成整数。
1.该函数会根据需要丢弃无用的开头空格,知道第一个非空格字符
2.第一个非空字符是正负号的话,将该符号与后面尽可能多的连续数字组合,形成整数
3.除了整数外,可能存在多余的字符,这些可以被忽略
4.如果第一个非空字符不是有效的整数字符,,字符串为空或仅包含空白字符。函数不需要转换
5.如果数值超过[−2^31, 2^31 − 1],返回INT_MAX (2^31 − 1) 或 INT_MIN (−2^31) 。
思路:用stringstream来读取数字。string类型常用的方法是find和substr,if(pos == string::npos)
stringstream:
1.类型转换,string到int
2.重复使用stringstream对象,每次转换前要使用clear()方法,效率很高,因为stringstream对象构造和析构非常耗费cpu时间。
class Solution {
public:
int myAtoi(string str) {
while(*str.begin() == ' ') str.erase(str.begin());
if(str == "") return 0;
stringstream ss;
ss<<str;
int n;
ss>>n;
return n;
}
};
5.盛最多水的容器
题目描述:给定n个整数,分别代表坐标中的一个点(i,ai),在坐标中话n条垂直线,每条垂直线的两个端点是(i,ai)(i,0),找出两条线,使他们与x轴共同构成的容器可以容纳最多的水
思路:双指针,时间复杂度O(n),空间O(1)
1.双指针分别指向开头和结尾,并更新面积 maxArea = max(maxArea,min(height[left],height[right])*(right-left));
2.较短的一边往较大的那边移动指针
为什么可行?网友的理解方法,感觉没问题
由于面积的大小取绝于较短边的长短m,如果想得到比当前更大的面积,边长短的必须要舍弃,如果不舍弃,高最大就是m,不管你另一边怎么增长,面积只会减小.
class Solution {
public:
int maxArea(vector<int>& height) {
int maxArea = 0;
int left = 0;
int right = height.size()-1;
while(left<right){
maxArea = max(maxArea,min(height[left],height[right])*(right-left));
if(height[left]<height[right]){left++;}
else{right--;}
}
return maxArea;
}
};
6.三数之和
题目描述:给定包含n个整数的数组,判断数组中是否存在三个元素之和为0,找出所有的满足条件的三元组。
思路:三指针解决
1.排序,从小到大,开头大于0,或者结尾小于0.和根本不可能为0;
2.三指针,先定一个。int fit = nums[i];剪枝if(fit>0) break;去重()避免同样的字符匹配多趟!if(i>0&&nums[i] == nums[i-1])continue;
3.定义剩下两个指针位置,分别位于剩下的头和尾。
4.递归存结果。while(k<r){if(...)}
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(),nums.end());
vector<vector<int>> res;
if(nums.size()<3||nums.front()>0||nums.back()<0) return {};
for(int i = 0;i<nums.size();++i){
int fit = nums[i];
if(fit>0) break;
if(i>0 && fit == nums[i-1]) continue; //避免重复
int k = i+1;
int r = nums.size()-1;
while(k<r){
if(nums[k]+nums[r] == -fit){
if(k == i+1||r == nums.size()-1){res.push_back(vector<int>{nums[i],nums[k],nums[r]});k++;r--;}
else if(nums[k] ==nums[k-1]){k++;} //避免重复
else if(nums[r] == nums[r+1]){r--;}
else{res.push_back(vector<int>{nums[i],nums[k],nums[r]});k++;r--;}
}
else if(nums[k]+nums[r]<-fit) k++;
else r--;
}
}
return res;
}
};