第一遍复习时间:08-04
第二遍复习时间:09-20
错误while()那句话
class Solution {
public:
int lengthOfLastWord(string s)
{
int i =s.size()-1;
int target=0;
while(i>=0 && s[i]==' ') i--;
while(i>=0 && s[i]!=' ')
{
target++;
i--;
}
return target;
}
};
这个有一个想法上错误就是我想的是便利map但是实际上我们遍历字符串 每次在map中找它的值是不是==1就好了
第二遍复习
还是步骤没有记住清楚
第一步一定是while取出前置空格
第二部是如果不是正负号不是数字 是字母 直接返回
第三部就是如果是负号设置标志否则为正数
第四步就是如果是字母要 i++ 跳过
第五部 开始处理数字 while遍历知道超过或者遇到非数字
第二遍复习
class Solution {
public:
string longestCommonPrefix(vector<string>& word) {
int len = word.size();
if (!len) return "";
// 只需要比较最大和最小的公共前缀就是整个数组的公共前缀
vector<string> temp = word;
sort(temp.begin(), temp.end());
string max = temp[len - 1], min = temp[0];
string res = "";
for (int i = 0;i < max.size() && i < min.size();i++){
if (max[i] == min[i])
res += max[i];
else break;
}
return res;
}
};
作者:neaya
链接:https://leetcode-cn.com/problems/longest-common-prefix/solution/qiao-jie-by-neaya-qbrl/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution {
public:
string reverseStr(string s, int k) {
for (int i = 0; i < s.size(); i += (2 * k)) {
// 1. 每隔 2k 个字符的前 k 个字符进行反转
// 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
if (i + k <= s.size()) {
reverse(s.begin() + i, s.begin() + i + k );
continue;
}
// 3. 剩余字符少于 k 个,则将剩余字符全部反转。
reverse(s.begin() + i, s.begin() + s.size());
}
return s;
}
};
class Solution {
public:
string reverseStr(string s, int k)
{
int i;
for(i=0;i+2*k<s.size();i=i+2*k)
{
reverse(s.begin()+i,s.begin()+i+k);
}
if(i+k < s.size()) reverse(s.begin()+i,s.begin()+i+k);
else
{
reverse(s.begin()+i,s.end());
}
return s;
}
};
就是整体翻转 再局部翻转
第一步 while 避开空白点
第二步 定义j=i 遇到字母移动j
第三步 通过i和j翻转单词
第四步 单词前移 需要变量k
第五步 单词后补零
第六步 遍历结束 通过k我们知道实际字母的具体 删除后面的所有字母 忘记了 还有记得-1 最后一个不同补0
其实这个题最重要的是知道i++ 到底表示的是什么 空白处的下一个 我们要特别清楚
第二遍复习: 整体想法还是简单,大的翻转,再小的翻转,就是要注意一些细节的处理
这个题倒是没什么
需要注意有两种方法,或者说三种方法
第二遍复习:
class Solution {
public:
bool isAnagram(string s, string t)
{
if(s.length()!=t.length()) return false;
vector<int> vec(26,0);
for(int i =0;i<s.length();i++)
{
vec[s[i]-'a']++;
vec[t[i]-'a']--;
}
for(int i =0;i<26;i++)
{
if(vec[i]!=0) return false;
}
return true;
}
};
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> ans;
if(p.size()>s.size()||s.size()==0) return ans;
vector<int> need(26);//这个是用来比较的
vector<int> windows(26);//这个是用来放入取出的
for(int i=0;i<p.size();i++)
{
need[p[i]-'a']++;
}
for(int i=0;i<p.size()-1;i++)
{
windows[s[i]-'a']++;
}
int l=0,r=p.size()-1;//起始点下标
for(l,r;r<s.size();r++,l++)
{
windows[s[r]-'a']++;
if(windows==need) ans.push_back(l);
windows[s[l]-'a']--;
}
return ans;
}
};
一开始的for循环如果不好搞 就用while循环就好
题目要求只考虑字母和数字字符 那就说明你不能==‘ ’ 来跳过了 需要用到isalnum
还有这种跳过什么字母的 我们在每一个判断都在l《r 这种东西
还有就是题目没说只有小写 大小写默认一样 那就tolower 或者 toupper转成一样的
第二遍复习
这个也简单 就是分成两种情况 继续迭代 结果返回 ||
第二遍复习 换一种写法,挺容易写错的
class Solution {
public:
bool validPalindrome(string s)
{
if(s.empty()||s.size()==1)
return true;
int size=s.size();
int l=0;
int r=size-1;
for(l,r;l<=r;l++,r--)
{
if(s[l]!=s[r]) break;
else continue;
}
int flat = true ;
int flat_ =true;
for(int l_new=l+1,r_new=r;l_new<=r_new;l_new++,r_new--)
{
if(s[l_new]!=s[r_new])
{
flat=false;
break;
}
}
for(int l_new=l,r_new=r-1;l_new<=r_new;l_new++,r_new--)
{
if(s[l_new]!=s[r_new])
{
flat_=false;
break;
}
}
return flat||flat_;
}
};
第二次看到 第一个想到的,两种 :这个长度的字符串的最长回文子串,以这个为结尾的回文子串。 还是第二种靠谱。看了眼题解,跟我想的都不一样,dp【】【】 代表的分别是两个坐标。
主要是他往回文子串怎么判断上面想了,我们可以从最短的长度 每次增加左右两个 判断是不是回文子串 数组内放的判断 。
外层循环控制长度 内层循环控制的是左边的坐标 巧妙的方法
代码写的比较成功 就是题目要求返回子串 要用到substr
(起始点坐标 ,长度) 我们在遍历的时候遇到true记得随时保存
第二遍复习
class Solution {
public:
string longestPalindrome(string s)
{
int n=s.size();
//特判
if(n<2)
{
return s;
}
int maxlen=1;//
int begin=0;
vector<vector<int>> dp(n,vector<int>(n));
//确定dp的含义和下标的含义
//确定递推公式
//初始化
for(int i=0;i<n;i++)
{
dp[i][i]=true;
}
for(int l=2;l<=n;l++)//遍历长度
{
for(int i=0;i<n;i++)
{
int j= i+l-1;
if(j<=n-1)
{
if(j-i>1) dp[i][j]=dp[i+1][j-1] && s[i]==s[j];
else dp[i][j]=s[i]==s[j];
if(dp[i][j]==true && j-i+1>maxlen)
{
maxlen=j-i+1;
begin=i;
}
}
else
{
break;//这边可以提前退出循环
}
}
}
return s.substr(begin,maxlen);
}
};
class Solution {
public:
string longestPalindrome(string s) {
vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
int maxlenth = 0;
int left = 0;
int right = 0;
for (int i = s.size() - 1; i >= 0; i--) {
for (int j = i; j < s.size(); j++) {
if (s[i] == s[j]) {
if (j - i <= 1) { // 情况一 和 情况二
dp[i][j] = true;
} else if (dp[i + 1][j - 1]) { // 情况三
dp[i][j] = true;
}
}
if (dp[i][j] && j - i + 1 > maxlenth) {
maxlenth = j - i + 1;
left = i;
right = j;
}
}
}
return s.substr(left, right - left + 1);
}
};
区别一下回文子序列 回文子串的区别
就是要求可以删除部分字母 不改变相对位置找到最长的
思考一下沿用上面的思想 可不可取
怎么说了 当左右两边相等 毫无疑问+2
当左右两边不想等 就是max() 每次放入一个,怎么理解呢 我们两个不能匹配 喷别看看能不能和里面的人匹配 反正长度肯定是比我当前小 那肯定一定已知了
然后就是考虑边界问题了
初始化肯定 斜线那一排肯定是1
如果长度=2 不想等那就是1(不对 好像也不用特判 max 求的的结果一样)
注意最后返回结果就好
第二遍复习
class Solution {
public:
int longestPalindromeSubseq(string s)
{
if(s.size()<=1) return s.size();
int n = s.size();
vector<vector<int>> dp(n,vector<int>(n));
//初始化
for(int i=0;i<n;i++)
{
dp[i][i]=1;
}
//确定递推公式 就是确定一共几种情况
for(int l=2;l<=n;l++)
{
for(int i=0;i<n;i++)
{
int j=i+l-1;
if(j >= n) break;
if (s[i] != s[j])
{
dp[i][j] = max(dp[i+1][j],dp[i][j-1]);
}
else
{
if(j-i==1)
{
dp[i][j] = 2;
}
else
{
dp[i][j]=dp[i + 1][j - 1]+2;
}
}
}
}
return dp[0][s.size()-1];
}
};
最长 毫无以为 bp了
没啥思路 看了两分钟
这个要记住两个比对
dp[i][j]=dp[i−1][j−1]+1, 当 text1[i - 1] == text2[j - 1];text1[i−1]==text2[j−1];
dp[i][j]=max(dp[i−1][j],dp[i][j−1]), 当 text1[i - 1] != text2[j - 1]text1[i−1]!=text2[j−1]
怎么理解两个不想等的情况,我记得做了很多次了,就是我们两个元素不匹配,我去看看能不能和你之前的匹配上(不考虑你了),都是这样
。然后我之前的有结果了
还有一个考虑的就是习惯了第一个for是长度,他这个正常的两个for,然后我想那怎么每次取两个 其实是a取一个 b已经更换了一遍了 就是每次最后两个元素比较 懂吧
在想初始化出现了问题 比如 a ab dp【0】【1】=max(dp【-1】【1】,dp【0】【0】,而且这种出现0的也不好特判啊 比如 ab a |cb e 也分可能性的 总不能把长的每一个元素比对把,这时候我们用长度表示下标 空间m+1 n+1 并且i 和 j就可以从1开始 初始化全为0 就可以把那些长度为0的初始化为0
特别注意i 和 j从1 开始太经典了
子序列的太喜欢 max了 记住了
第二遍复习
class Solution {
public:
int longestCommonSubsequence(string text1, string text2)
{
//如果下标表示实际坐标
//情况一 如果相等 那就dp【】【】+1
//情况二 如果不想等 那就 max dp【i-1】【】 dp【】【j-1】
//考虑初始化 dp【0】【..】 和 dp【...】【0】 也不是不能初始化 就是两个for循环(一点点麻烦)
//如果下标表示长度 长度从1开始 初始化带0的都为0 确实方便了
int m=text1.size();
int n=text2.size();
vector<vector<int>> dp(m+1,vector<int>(n+1,0)) ;
for(int i=0;i<=m;i++)
{
for(int j=0;j<=n;j++)
{
if(i==0 || j==0)
{
dp[i][j]=0;
continue;
}
if(text1[i-1]==text2[j-1]) dp[i][j]=dp[i-1][j-1]+1;
else
{
dp[i][j]= max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[m][n];
}
};
(不是让你选择哪一个能够成功 而是每一个都可以达到目的 就是哪一个步数最少懂吧)
class Solution {
public:
int numDistinct(string s, string t) {
vector<vector<uint64_t>> dp(s.size() + 1, vector<uint64_t>(t.size() + 1));
for (int i = 0; i < s.size(); i++) dp[i][0] = 1;
for (int j = 1; j < t.size(); j++) dp[0][j] = 0;
for (int i = 1; i <= s.size(); i++) {
for (int j = 1; j <= t.size(); j++) {
if (s[i - 1] == t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[s.size()][t.size()];
}
};
这边还是使用长度 因为确实需要考虑长度为0的情况,他这边比较特殊的就是i=0 判断可不可以,如果全是* 那当然可以 一开始我想着全部遍历一遍 判断字母。但是答案写的就比较巧妙,他是初始化一开始都为0 不可以,然后遍历从长度1开始 如果是* 就放入1 如果不是 那我后面的也不判断了 不用开都不是了
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost)
{
//dp[i]=min();
//dp 表示走到当前这个格子需要的花费
int n=cost.size();
vector<int> dp(n);
dp[0]=cost[0];
dp[1]=cost[1];
for(int i=2;i<n;i++)
{
dp[i]=min(dp[i-1],dp[i-2])+cost[i];
}
return min(dp[n-1],dp[n-2]);
}
};
这个题我还是想到了 就是下标表示以当前坐标为结尾的子序列最大值,你想想是不是就两种情况 一个和之前那一段接起来 要么重新开始 单独一个 。max取大不就好了
唯一要注意的就是我们是在所有dp中找最大值 所以可以再遍历的时候顺便比较 不断更新最大值
第二遍复习
class Solution {
public:
int maxSubArray(vector<int>& nums)
{
if(nums.size()==0) return 0;
int n=nums.size();
int max_=nums[0];
vector<int> dp(n);
dp[0]=nums[0];
for(int i=1;i<n;i++)
{
dp[i]=max(dp[i-1]+nums[i],nums[i]);
max_=max(max_,dp[i]);
}
return max_;
}
};
这个简单题 然我想到了一开始做的时候无从下手 其实没什么差别好吧 遇到非字母的++ --就好了,还有就是跳过啥玩意 用while
第二遍复习
class Solution {
public:
string reverseOnlyLetters(string S)
{
int n=S.size();
int l=0;
int r=n-1;
while(l<r)
{
while(!((S[l]>='a'&&S[l]<='z')|(S[l]>='A'&&S[l]<='Z')) && l<r)
{
l++;
}
while(!((S[r]>='a'&&S[r]<='z')|(S[r]>='A'&&S[r]<='Z')) && l<r)
{
r--;
}
if(l<r)
{
swap(S[l],S[r]);
l++;
r--;
}
}
return S;
}
};
class Solution {
public:
bool isIsomorphic(string s, string t) {
for(int i = 0; i < s.size(); ++i)
if(s.find(s[i]) != t.find(t[i]))
return false;
return true;
}
}
。
class Solution {
public:
bool isIsomorphic(string s, string t)
{
if(s.length()!=t.length()) return false;
unordered_map<char,char> map1;
unordered_map<char,char> map2;
for(int i=0;i<s.size();i++)
{
if(map1.count(s[i])==0) map1[s[i]]=t[i];
if(map2.count(t[i])==0) map2[t[i]]=s[i];
if(map1[s[i]]!=t[i] || map2[t[i]]!=s[i]) return false;
}
return true;
}
};
还是比较少见的 一个字符串两个for循环来解决的 少见
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n= nums.size();
vector<int> dp(n,1);//最短都是1 不用想都知道
int max_=1;
for(int i = 0 ; i < n ; i++)
{
for(int j=0;j<i;j++)
{
if(nums[i]>nums[j])
{
dp[i]=max(dp[i],dp[j]+1);
}
}
max_=max(max_,dp[i]);
}
return max_;
}
};
class Solution {
public:
int lengthOfLIS(vector<int>& nums)
{
int n =nums.size();
if(n<=1) return n;
vector<int> dp(n,1);//全部初始化为1
dp[0]=1;
//
int result=1;
for(int i=1;i<n;i++)
{
for(int j=0;j<i;j++)
{
if(nums[i]>nums[j])
{
dp[i]=max(dp[i],dp[j]+1);
}
}
result=max(result,dp[i]);
}
return result;
}
};
下面第一个放了一个好理解的版本 可以学习
class Solution {
public:
int numDecodings(string s) {
if (s[0] == '0') return 0;
vector<int> dp(s.size()+1);//
dp[0]=1;dp[1]=1;
for (int i =2; i <= s.size(); i++) {
if (s[i-1] == '0')//1.s[i]为0的情况
if (s[i - 2] == '1' || s[i - 2] == '2') //s[i - 1]等于1或2的情况
dp[i] = dp[i-2];//由于s[1]指第二个下标,对应为dp[2],所以dp的下标要比s大1,故为dp[i+1]
else
return 0;
else //2.s[i]不为0的情况
if (s[i - 2] == '1' || (s[i - 2] == '2' && s[i-1] <= '6'))//s[i-1]s[i]两位数要小于26的情况
dp[i] = dp[i-1]+dp[i-2];
else//其他情况
dp[i] = dp[i-1];
}
return dp[s.size()];
}
};
class Solution {
public:
int numDecodings(string s) {
if (s[0] == '0') return 0;
int pre = 1, curr = 1;//dp[-1] = dp[0] = 1
for (int i = 1; i < s.size(); i++) {
int tmp = curr;
if (s[i] == '0')
if (s[i - 1] == '1' || s[i - 1] == '2') curr = pre;
else return 0;
else if (s[i - 1] == '1' || (s[i - 1] == '2' && s[i] >= '1' && s[i] <= '6'))
curr = curr + pre;
pre = tmp;
}
return curr;
}
};
第二次复习
一开始 我让下标表示真实的下标 那么必然就要确定dp【0】 和 dp【1】 但是此时 dp【1】就需要分很多种情况进行讨论 所以 更换下标含义为长度
重写了代码,没啥问题 基本一次过 就是注意判断的是‘ 0’ 字符,还有一开始的特判
class Solution {
public:
int numDecodings(string s)
{
//特判
if(s[0]=='0') return 0;
int n=s.size();
vector<int> dp(n+1);//
dp[0]=1;
dp[1]=1;
for(int i=2;i<=n;i++)
{
if(s[i-1]=='0')
{
if(s[i-2]=='1' || s[i-2]=='2')
{
dp[i]=dp[i-2];//最后两个解法固定了 解法数量取决与前面了
}
else
{
return 0;
}
}
else//最后一个不为0
{
if(s[i-2]=='1')//固定两种解法
{
dp[i]=dp[i-1]+dp[i-2];
}
else if(s[i-2]=='2' && s[i-1]<='6')
{
dp[i]=dp[i-1]+dp[i-2];
}
else
{
dp[i]=dp[i-1];
}
}
}
return dp[n];
}
};
也是一个计数排序,第一眼看好像不是,其实认真看 真的是,稍稍变动一下,按照指定的顺序,那我们就按照那个顺序找到数组输出不就好了,多加一个循环 题目还要求把没有考虑到的输出 这个就正常输出就好了
记得用while来取出数
第二遍复习
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2)
{
vector<int> vec(1001,0);
vector<int> result;//用来存放结果的
for(int i=0;i<arr1.size();i++)
{
vec[arr1[i]]++;
}
for(int i=0;i<arr2.size();i++)
{
while(vec[arr2[i]]!=0)
{
result.push_back(arr2[i]);
vec[arr2[i]]--;
}
}
for(int i =0;i<1001;i++)
{
while(vec[i]!=0)
{
result.push_back(i);
vec[i]--;
}
}
return result;
}
};
重新温习一下,他这个左边界是不断变化的 所以要取出设置为temp,不断更新左边界。 如果不满足要求退出,重新设置下一个左右边界,所以需要两层循环,外层循环
有一个时刻需要注意的就是,下一次的右边界是上一次左边界的下一个所以如果用for循环 +while循环 外层for循环 i不要++ 切记
第二遍复习
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals)
{
vector<vector<int>> ans;
sort(intervals.begin(),intervals.end());
int n= intervals.size();
int start=intervals[0][0];
int end =intervals[0][1];
for(int i=1;i<n;i++)
{
if(intervals[i][0]<=end)
{
end=max(end,intervals[i][1]);//更新一下范围
}
else
{
ans.push_back({start,end});
start=intervals[i][0];
end=intervals[i][1];
}
}
ans.push_back({start,end});//最后一次忘记存储了
return ans;
}
};
class Node
{
public:
int value;
int key;//本来没有这个的 为了方便删除尾巴节点 得到key 从map也删除这个
Node* right;
Node* left;
Node(int key_,int value_)
{
key=key_;
value=value_;
}
};
class LRUCache {
public:
//如果我要在这个类的函数实现功能 是不是把需要调用的设为成员 方便使用呢 对吧
//四个成员
Node* L;//空白头结点方便插入 有点想岗哨
Node* R;//尾巴 是容量慢了 方便从尾巴删除
int n;//表示容量
unordered_map<int,Node*> hash;//初始化一个hash表
LRUCache(int capacity)
{
n = capacity;
L = new Node(-1,-1),R = new Node(-1,-1);//不插入hash
L->right=R;//串起来
R->left=L;
}
//链表节点的删除操作,并不是真的释放空间 特别注意
void remove(Node* p)//传入需要删除的节点
{
p->left->right=p->right;//断开指向自己的 指向我后面的
p->right->left=p->left;//同理
}
void insert(Node* p)//传入需要插入的节点
{
//先来处理L右边的
//箭头指出去的在左边 a=b 也可以叫上a挂上b
p->right=L->right;
L->right->left=p;
L->right=p;
p->left=L;
}
//作用:通过key 得到元素的value
//实际事情:map查找节点 删除节点关系 插入(现在的关系)(get 无需考虑容量 put才要)
int get(int key)
{
//查询 首先在map查询
if(hash.count(key)==1)
{
auto p= hash[key];//取出节点
remove(p);
insert(p);
return p->value;
}
return -1;
}
//作用放入:存在则变更value 不存在就插在头
//实际:1 存在 修改后 删除 插入
//2 不存在 容量慢了 容量没满
void put(int key, int value)
{
if(hash.count(key)==1)//存在
{
auto p=hash[key];
p->value = value;//修改
remove(p);
insert(p);
}
else
{
if(hash.size() == n) //如果缓存已满,则删除双链表最右侧的节点
{
auto p= R->left;
remove(p);
hash.erase(p->key); //更新哈希表,这个很容易忘记
delete p;//这个也很容易忘记
}
//处理好了 插入新节点
auto p=new Node(key,value);
hash[key] = p;//两个 不要忘记了
insert(p);
}
}
};
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
这个题也是利用 x &(x-1)看看是不是消除一次 就是0了 怎么说呢 没做过还真不一定想的起来
第二遍复习
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
uint32_t ans=0;
for(int i=0;i<32;++i)
{
//这个不能放在后面 否贼会多出一个0少掉一个数
ans<<=1;//最开始是0 移动等于没有变化
ans = ans|(n&1);
n>>=1;//理解一下 去除取出的数字 几个数字就要去除几次
}
return ans;
}
};
class Solution {
public:
uint32_t reverseBits(uint32_t n)
{
int result=0;
for(int i=0;i<32;i++)
{
result=result<<1;
result=result|(n&1);
n=n>>1;
}
return result;
}
};
vector result(n+1);这种方式初始化大小初始值都是0 你如果在用pushback就会多出很多
要么你就用reserve 第二段代码
class Solution {
public:
int countOnes(int x) {
int ones = 0;
while (x > 0) {
x &= (x - 1);
ones++;
}
return ones;
}
vector<int> countBits(int n) {
vector<int> result(n+1);
for(int i=0;i<=n;i++)
{
//int number = countOnes(n);
//统计结果
result[i]=countOnes(i);
//result.push_back(number);
}
return result;
}
};
class Solution {
public:
int countOnes(int x) {
int ones = 0;
while (x > 0) {
x &= (x - 1);
ones++;
}
return ones;
}
vector<int> countBits(int n) {
vector<int> bits;
bits.reserve(n+1);
for (int i = 0; i <= n; i++) {
int count = countOnes(i);
bits.push_back(count);
}
return bits;
}
};
class Solution {
public:
int mySqrt(int x) {
int l = 0, r = x;
while (l <= r)
{
int mid =l+(r-l)/2;
if((long long)mid*mid < x)
{
l= mid+1;
}
else if((long long)mid*mid > x)
{
r=mid-1;
}
else
{
return mid;
}
}
return r;
}
};
class Solution {
public:
bool isPerfectSquare(int num)
{
int l=0;
int r=num;
while(l<=r)
{
int mid = l+(r-l)/2;
if((long long) mid*mid < num)
{
l=mid+1;
}
else if((long long) mid*mid > num)
{
r=mid-1;
}
else
{
return true;
}
}
//非正常退出
return false;
}
};
<=
if(nums[0]<=nums[mid])
< 会判定错误 举例是 【3,1】 1第二个错误要特别记住 有序 小于等于 mid 会取到前面的那个数
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = (int)nums.size();
if (!n)//长度为0
{
return -1;
}
if (n == 1) //长度为1
{
if (nums[0]==target) return 0;
else -1;
}
int l = 0, r = n - 1;//确定左边界 和右边界
while (l <= r)
{
int mid= l+(r-l)/2;
if(nums[mid]==target) return mid;
if(nums[0]<=nums[mid])
{
if(nums[0] <=target && target <nums[mid])
{
r=mid-1;
}
else
{
l=mid+1;
}
}
else
{
if (nums[mid] < target && target <= nums[n - 1])//并且在这个区间
{
l = mid + 1;
} else
{
r = mid - 1;//不在这个区间
}
}
}
return -1;
}
};
class Solution {
public:
int search(vector<int>& nums, int target)
{
int n = (int)nums.size();
if (!n)//长度为0
{
return -1;
}
if (n == 1) //长度为1
{
if (nums[0]==target) return 0;
else -1;
}
int l=0;
int r=n-1;
while(l<=r)
{
int mid =l+(r-l)/2;
if(nums[mid] == target) return mid;
if(nums[0]<=nums[mid])//表示左边有序
{
//情况一 在有序范围内
if(nums[0]<=target && target<nums[mid])
{
r=mid-1;//mid不用包括 一开始判断了
}
//情况二 不在有序范围内
else
{
l=mid+1;
}
}
else//右边有序
{
if(nums[mid]<target && target<=nums[n-1])
{
l=mid+1;//mid不用包括 一开始判断了
}
//情况二 不在有序范围内
else
{
r=mid-1;
}
}
}
return -1;//退出循环 还没找到 就是没有
}
};
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) { // 二分法,核心思想是把矩阵拉成一条数组。
// 可行的原因是每一行开头的数值比上一行末尾的大。可以把下一行拼接到上一行,最后将矩阵变成一行。
int m = matrix.size();
int n = matrix[0].size();
int l=0;
int r=m*n-1;
while(l<=r)
{
int mid = l+(r-l)/2;
if(target==matrix[mid/n][mid%n]) return true;
else if(matrix[mid/n][mid%n]>target)
{
r=mid-1;
}
else
{
l=mid+1;
}
}
return false;
}
};
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target)
{
if(matrix.size()==0) return false;
int m=matrix.size();
int n=matrix[0].size();
int l=0;
int r=m*n-1;
// 11 2 3 11/4=2 11%4=3
while(l <=r )
{
int mid = l+(r-l)/2;
if(matrix[mid/n][mid%n]==target) return true;
else if(matrix[mid/n][mid%n] < target)
{
l=mid+1;
}
else
{
r=mid-1;
}
}
return false;
}
};
看看这个题解 关于边界的判断说的很好
nums[mid] < nums[r]
有序就 r=mid
。举个例子 7890123 恰好在中间 我们要把它归到另外一边
。跳出说明只有一个元素 就是我们要的了延伸思考一下 最大值怎么着 不就是最小值左边那个 如果是-1 那就是最后一个 代码不变 这样不用再考虑别的情况
class Solution {
public:
//dp表示走到当前这个地方需要多少步数 下标表示实际坐标
//递推公式就是 dp【i】【j】=dp[i][j-1]+dp[i-1][j]
//初始化问题 一般其实就是考虑 0的那边 然后遍历从1开始 也确实是这样
int uniquePaths(int m, int n) {
//m行 n列
vector<vector<int>> dp(m,vector<int>(n));
for(int i=0;i<m;i++)
{
dp[i][0]=1;
}
for(int i=0;i<n;i++)
{
dp[0][i]=1;
}
for(int i=1;i<m;i++)
{
for(int j=1;j<n;j++)
{
dp[i][j]=dp[i][j-1]+dp[i-1][j];
}
}
return dp[m-1][n-1];
}
};
class Solution {
public:
int uniquePaths(int m, int n)
{
vector<vector<int>> dp(m,vector<int>(n));
//初始化
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
if(i==0 || j==0)
{
dp[i][j]=1;
continue;
}
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m-1][n-1];
}
};
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m= obstacleGrid.size();
int n= obstacleGrid[0].size();
vector<vector<int>> dp(m,vector<int>(n,0));//一开始初始化都是障碍
for(int i=0;i<m;i++)
{
if(obstacleGrid[i][0]==0)
{
dp[i][0]=1;//不是障碍 放入1
}
else
{
break;
}
}
for(int i=0;i<n;i++)
{
if(obstacleGrid[0][i]==0)
{
dp[0][i]=1;//不是障碍 放入1 一种方案
}
else
{
break;
}
}
for(int i=1;i<m;i++)
{
for(int j=1;j<n;j++)
{
if(obstacleGrid[i][j]!=1)//如果不是障碍
{
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
//如果是障碍 其实不用else 我们要放入0 但是初始化就是0了 不用修改
}
}
return dp[m-1][n-1];
}
};
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid)
{
int m = obstacleGrid.size();
int n= obstacleGrid[0].size();
vector<vector<int>> dp(m,vector<int>(n,0));//全都初始化为0
for(int i=0;i<m;i++)
{
if(obstacleGrid[i][0]==0)
{
dp[i][0]=1;
}
else
{
//遇到障碍直接跳出 后面全都是初始化的0
break;
}
}
for(int i=0;i<n;i++)
{
if(obstacleGrid[0][i]==0)
{
dp[0][i]=1;
}
else
{
//遇到障碍直接跳出 后面全都是初始化的0
break;
}
}
for(int i=1;i<m;i++)
{
for(int j=1;j<n;j++)
{
if(obstacleGrid[i][j] == 1)//障碍
{
continue;
}
else//不是障碍
{
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
}
return dp[m-1][n-1];
}
};
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int m= triangle.size();
int n= triangle.back().size();
vector<vector<int>> dp(m,vector<int>(n));
for(int i=m-1;i>=0;i--)
{
for(int j=0;j<=i;j++)
{
if(i==m-1)
{
dp[i][j]=triangle[i][j];
}
else
{
dp[i][j]=min(dp[i+1][j],dp[i+1][j+1])+triangle[i][j];
}
}
}
return dp[0][0];
}
};
就像下面第二个代码 找个要好好学习,就是可以优化的地方
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int n = triangle.size();
vector<vector<int>> dp(n, vector<int>(n));
//初始化
dp[0][0] = triangle[0][0];
for(int i=1;i<n;++i)
{
dp[i][0]=dp[i-1][0]+triangle[i][0];
}
for(int i=1;i<n;++i)
{
dp[i][i]=dp[i-1][i-1]+triangle[i][i];
}
for (int i = 1; i < n; ++i) {
for (int j = 1; j < i; ++j)
{
dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j];
}
}
return *min_element(dp[n - 1].begin(), dp[n - 1].end());
}
};
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int n = triangle.size();
vector<vector<int>> f(n, vector<int>(n));
f[0][0] = triangle[0][0];
for (int i = 1; i < n; ++i) {
f[i][0] = f[i - 1][0] + triangle[i][0];
for (int j = 1; j < i; ++j) {
f[i][j] = min(f[i - 1][j - 1], f[i - 1][j]) + triangle[i][j];
}
f[i][i] = f[i - 1][i - 1] + triangle[i][i];
}
return *min_element(f[n - 1].begin(), f[n - 1].end());
}
};
//好像一样不分情况 然后 最后求一下 最后一行的最小值
//可能需要判断越界
//也可以分一下情况 正常情况
dp[i][j]=nums[i][j]+min(dp[i-1][j],dp[i-1][j-1])
//异常情况 在边界上(当i==j) 我只能
dp[i][j]=nums[i][j]+dp[i-1][j-1];
//异常情况二 (j==0) 在第一排
dp[i][j]=nums[i][j]+dp[i-1][j];
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int m = triangle.size();
vector<vector<int>> dp(m,vector<int>(m));
//初始化
dp[0][0]=triangle[0][0];
for(int i=1;i<m;i++)//从第二行开始
{
for(int j=0;j<=i;j++)
{
//情况一
if(j==0)
{
dp[i][j]=triangle[i][j]+dp[i-1][j];
continue;
}
if(j==i)
{
dp[i][j]=triangle[i][j]+dp[i-1][j-1];
continue;
}
dp[i][j]=triangle[i][j]+min(dp[i-1][j],dp[i-1][j-1]);
}
}
return *min_element(dp[m - 1].begin(), dp[m - 1].end());
}
};
代码随想录还是一考虑的房间数,然后分了两种情况 当前偷还是不偷 取max,他把情况叫做状态 ,习惯一下 联想一下,对了记得特判一下
注意 如果把dpi 认为最后一个必定偷 然后下面代码是错的 为什么呢 因为可能连续两个都是空的对吧 不好改 要i-3了
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.size() == 0) return 0;
if (nums.size() == 1) return nums[0];
vector<int> dp(nums.size());
dp[0] = nums[0];
dp[1] = nums[1];
for (int i = 2; i < nums.size(); i++) {
dp[i] = dp[i - 2]+nums[i];
}
return max(dp[nums.size()-1],dp[nums.size()-2]);
}
};
股票很像我们把一维的转变成二维的 很经典 和后面一个字符串很像(自己和自己比较)
时间复杂度有点拉夸,修改一下,其实分析一下其实只是用到前一天的 0 和前一天的 1 保存这两个就好了
class Solution {
public:
int maxProfit(vector<int>& prices)
{
int n= prices.size();
//vector> dp(n,vector(2));
int dp_0=-prices[0];
int dp_1=0;
for(int i=1;i<n;i++)
{
dp_0=max(dp_0,-prices[i]);//表示持有股票
dp_1=max(dp_1,dp_0+prices[i]);//表示不持有股票
}
return dp_1;
}
};
class Solution {
public:
int maxProfit(vector<int>& prices)
{
int len =prices.size();
if(len==0) return 0;
vector<vector<int>> dp(len,vector<int>(2));
dp[0][0]= -prices[0];
dp[0][1]= 0;
for(int i=1;i<len;i++)
{
dp[i][0]=max(dp[i-1][0],-prices[i]);
dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]);
}
return dp[len-1][1];
}
};
dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i]);
没啥特别的 就一句话不一样
原本今天持有
今天第一次买入
二种情况现在 今天持有
昨天不持有今天不一定买入(不一定第一次)
二种情况第二遍复习 没什么问题
// 版本一
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len =prices.size();
vector<vector<int>> dp(len,vector<int>(2));
dp[0][0]=-prices[0];//第一天持有股票
dp[0][1]=0;//第一天不持有股票
for(int i=1;i<len;i++)//从第二天开始
{
dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i]);
dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]);
}
return dp[len - 1][1];//注意看这边是返回最后一天不持有股票的利润
}
};
其实分成四种个人觉的更好理解,这样分反而和之前匹配起来(但是有一个坏处就是破坏了规律 参考第四题)
,// 版本一
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<vector<int>> dp(prices.size(), vector<int>(5, 0));
dp[0][1] = -prices[0];
dp[0][3] = -prices[0];
for (int i = 1; i < prices.size(); i++) {
//dp[i][0]=dp[i-1][0];//表示肯定昨天也啥事没干
dp[i][1]=max(dp[i-1][1],-prices[i]); //昨天就买入了 或者今天才买入 昨天啥事没干
dp[i][2]=max(dp[i-1][2],dp[i-1][1]+prices[i]);//昨天酒卖出了 今天才卖出昨天有
dp[i][3]=max(dp[i-1][3],dp[i-1][2]-prices[i]);//昨天酒第二次买入了 昨天不存在只走到了2今天买入
dp[i][4]=max(dp[i-1][4],dp[i-1][3]+prices[i]);
}
return dp[prices.size() - 1][4];
}
};
简单的说就是这个状态是重复的 2个2个重复了
加一个循环遍历一下 j从1开始 <=2k结束,这边想不起来就用k=2的例子套进去看看对不对就这样
初始化也是这样子
我是在3的代码上修改的 挺简单的
第二遍复习 没什么问题 直接那之前代码 3 4改就好
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2, 0));
dp[0][0] -= prices[0]; // 持股票
for (int i = 1; i < n; i++) {
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
}
//return max(dp[n - 1][0], dp[n - 1][1]);
return dp[n-1][1];
}
};
class Trie {
public:
/** Initialize your data structure here. */
Trie* next[26];//26个孩子
bool isEnd;//判断是不是到单词尾部
Trie() {
isEnd=false;
memset(next,0x0,sizeof(next));//清空
}
/** Inserts a word into the trie. */
void insert(string word) {
Trie* node =this;//之所以要创建这个 主要是要一层一层进去
for(int i=0;i<word.size();i++)
{
if(node->next[word[i]-'a']!=NULL)//表示之前有人填充过
{
node=node->next[word[i]-'a'];//进入下一层
}
else//为空 那就要创建一个新的节点挂上去
{
node=node->next[word[i]-'a']=new Trie();
}
}
//最后node指向最后一个字母节点 循环结束修改isend
node->isEnd=true;
}
/** Returns if the word is in the trie. */
bool search(string word) {
Trie* node = this;
for (int i=0;i<word.length();i++)
{
node = node->next[word[i] - 'a'];
//记住了先进入 再判断 (开始第一次next 才是抽象放入字母的地方)
if(node == NULL)
{
//表示当前指向的字母就开始没有了
return false;
}
}
//循环走完了 指向最后一个字母了 看看它的isend
return node->isEnd;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
bool startsWith(string prefix) {
Trie* node = this;
for (int i=0;i<prefix.length();i++) {
node = node->next[prefix[i]-'a'];
if (node == NULL) {
return false;
}
}
return true;
}
};
好好看这个 01背包问题
认真参考这个
(它是用到正上面的数据和正上面前面的数据 一共两个)
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
还有一个就是一维的dp遍历顺序只能从后向前,为什么呢这个很好理解,(原本二维的是本层需要上一层(正上方和左边的数据)不存在覆盖什么的 从那一边都可以)现在只有一层 就是右边的数据需要左边的。等走到右边需要左边的值的时候都变化的不行。
void test_1_wei_bag_problem() {
vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagWeight = 4;
// 初始化
vector<int> dp(bagWeight + 1, 0);
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
cout << dp[bagWeight] << endl;
}
int main() {
test_1_wei_bag_problem();
}
void test_2_wei_bag_problem1() {
vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagWeight = 4;
// 二维数组
vector<vector<int>> dp(weight.size() + 1, vector<int>(bagWeight + 1, 0));
// 初始化
for (int j = bagWeight; j >= weight[0]; j--) {
dp[0][j] = dp[0][j - weight[0]] + value[0];
}
// weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
if (j < weight[i]) dp[i][j] = dp[i - 1][j];
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
cout << dp[weight.size() - 1][bagWeight] << endl;
}
int main() {
test_2_wei_bag_problem1();
}
s[index] >= g[i]
大于等于 if(index >= 0 && s[index] >= g[i] ) 还有一个就是这个的顺序 如果把index放在前面可能存在越界的问题 所以必须先对index进行判断
非常重要class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
//既然是用饼干去喂 那就把人作为循环 因为人是要走一遍的 但是饼干不一定
//如果先满足大的孩子 我们可能拿最大的饼干发现每一个人符合 那就结束了 所以一个循环就好了
sort(g.begin(),g.end());
sort(s.begin(),s.end());
int index= s.size()-1;
int result=0;
for(int i=g.size()-1;i>=0;i--)
{
if(index >= 0 && s[index] >= g[i] )
//if(s[index] >= g[i] && index >= 0 )
{
result++;
index--;
}
}
return result;
}
};
1 初始化变量 就两个 5块钱的 10块钱
进入循环
如果是5块钱 变量++
如果是10块钱 1 存在5块钱 -- 2 不存在5块钱 直接false
其他(也就是20块钱) 1 如果同时存在10块钱和5块钱 那就一起-- 2 如果存在3张或以上5块钱 -3 3 否则其他情况都返回false
这个题看不出是贪心,其实更像是考核代码和分析的能力,比较经典的
为什么设置是北东南西 是因为一直向右旋转 变化 每次+1,如果是向左旋转 那就是北西南东每次正向+3变化你自己看看
代码中有几个比较巧妙的地方比如设置set是为了到时候没走一步查看是否存在障碍。也能用map但是也一样要把键设置成pair的形式
遇到一个错误就是把set 修改成unorderset 报错 查询是pair无法hash
for(int j=0;j
第三个就是 我增加了一个break 就是遇到障碍计算距离就退出了
还有一个就是 我是遇到障碍回退 不再是用临时变量 结构更清楚了
class Solution {
public:
int robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
int dir_x[4]={0,1,0,-1}; //两个合起来看 每次走一步
int dir_y[4]={1,0,-1,0};
int x=0 ;//定义一个实际坐标 当前走到的位置
int y=0;
int status=0; //遇到-1(右转) 就+1 %4 遇到 -2(左转)就+3%4
int max_distance=0;//当前的最大距离是0
//因为我们要判断障碍 随意放在set方便查询
set<pair<int,int>> obstaclesSet;
for(int i=0;i<obstacles.size();i++)
{
obstaclesSet.insert(make_pair(obstacles[i][0],obstacles[i][1]));//逐个插入 用 make_pair的方式 也可以匿名对象
}
for(int i=0;i<commands.size();i++)
{
if(commands[i] == -1)
{
status=(status+1)%4;
}
else if(commands[i] == -2)
{
status=(status+3)%4;
}
else //正常 正数只走了
{
for(int j=0;j<commands[i];j++)
{
//每次走一步
x=x + dir_x[status];
y=y + dir_y[status];
if(obstaclesSet.find(make_pair(x,y))!=obstaclesSet.end())//是障碍
{
x=x - dir_x[status];//回退
y=y - dir_y[status];//回退
max_distance = max(max_distance, x*x + y*y);
break;//跳出循环 其实不跳也可以就是一直判断是障碍 重复
}
else//如果不是障碍
{
max_distance = max(max_distance, x*x + y*y);//正常处理
}
}
}
}
return max_distance;
}
};
有一个非常需要注意的就是 它要求是走到最后一个元素就好了,所以遍历不需要走到最后一个元素 比较特殊。
题目要求竟可能少的步数,不用判断不能到的情况 肯定能到
其实这个我还是记得他的思想的就是 第一步走的是固定的。第二步只能从就是从第一步走的范围选,那么走了几步 就是最小距离是多少。
它这个简便的代码 也只有一层for循环正常遍历, 然后设置了一个标志位end(表示这一步的范围 初始肯定是0(0下标),然后每次i==end 表示这个范围走完了就更新end,并且数量++。你可以考虑最初是的情况没问题
做了点修改 多了个判断 如果当前的距离已经够了 那就直接跳出 记得数量++ 这种自己举个例子就知道了
class Solution {
public:
int jump(vector<int>& nums) {
int ans=0;
int end=0;
int maxpos=0;
for(int i=0;i<nums.size()-1;i++)
{
maxpos=max(maxpos,nums[i]+i);
if(maxpos >= nums.size()-1)
{
ans++;
return ans;
}
if(i == end)//如果走到这一步范围的结尾了
{
end= maxpos;
ans++;//数量+1
}
}
return ans;
}
};
class Solution {
public:
void inorder(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
//进入左子树 可以想象单独跟节点的情况 (左退出 输出 右退出 外层函数退出 结束)
inorder(cur->left,vec);
//输出根节点
vec.push_back(cur->val);
//进入右子数
inorder(cur->right,vec);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
inorder(root, result);//传入的数组是引用切记
return result;
}
};
如果用颜色标记法,注意auto的使用还不错 或者 first second 取出两个元素。
自己写了一遍 挺简单的
第二遍复习
-一样的
/*
// Definition for a Node.
class Node {
public:
int val;
vector children;
Node() {}
Node(int _val) {
val = _val;
}
Node(int _val, vector _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public:
void inorder(Node* node,vector<int>& result)
{
if(node == NULL) return;
for(int i=0;i<node->children.size();i++)
{
inorder(node->children[i],result);
}
result.push_back(node->val);
}
vector<int> postorder(Node* root)
{
vector<int> result;
inorder(root,result);
return result;
}
};
复习一下 就是把两个数组 标记 做成成员比较好
初始还是把根节点放入
循环开始
第二遍复习
/*
// Definition for a Node.
class Node {
public:
int val;
vector children;
Node() {}
Node(int _val) {
val = _val;
}
Node(int _val, vector _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public:
vector<int> ans;//这个是用来存放放一层的数据 会随时清空
vector<vector<int>> result;//这个是用来存放汇总的数据
queue<Node*> que;//广度优先 使用队列
vector<vector<int>> levelOrder(Node* root)
{
if(root ==NULL) return {};//特判 如果为空 直接返回
//Node* p = root;//在循环开始前 总是把根节点加进去队列
que.push(root);
int flag =1;//初始标记 1 表示当前队列放入了一个
while(que.empty()!=true)//如果队列不为空
{
auto p = que.front();//那我就每次从队列头部取出一个(最旧的元素 )
que.pop();//老规矩 取出 就要删除
ans.push_back(p->val);//取出就要放入数组内
for(int i=0;i<p->children.size();i++)
{
que.push(p->children[i]);
}
--flag; //用于记录当前层次的节点个数
if(flag == 0)
{
flag = que.size();//这边就是统计队列剩余的数量 也就是下一层的数目了
result.push_back(ans);//把当前层的数组加入总结果
ans.clear();//一定要记得及时清空
}
}
return result;
}
};
这种选择的 非a就是b的回溯挺常见的把(就是多剪枝)
之前写的挺好的,
经典的题目 写了一遍没啥问题
第二遍复习
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* invertTree(TreeNode* root)
{
//终止条件
if (root == nullptr)
{
return nullptr;
}
//当前层的逻辑处理
TreeNode* temp=root->left;
root->left=root->right;
root->right=temp;
//进入下一层 分别进入左子树和右子树
invertTree(root->right);
invertTree(root->left);
return root;
}
};
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
//先序处理
void dfs(TreeNode* root)
{
//递归终止条件
if(root == NULL) return;
//先序处理
TreeNode* temp = root->left;
root->left=root->right;
root->right=temp;
dfs(root->left);
dfs(root->right);
}
TreeNode* invertTree(TreeNode* root)
{
dfs(root);
return root;
}
};
代码随想录
class Solution {
private:
vector<int> vec;
void traversal(TreeNode* root) {
if (root == NULL) return;
traversal(root->left);
vec.push_back(root->val); // 将二叉搜索树转换为有序数组
traversal(root->right);
}
public:
bool isValidBST(TreeNode* root) {
vec.clear(); // 不加这句在leetcode上也可以过,但最好加上
traversal(root);
for (int i = 1; i < vec.size(); i++) {
// 注意要小于等于,搜索树里不能有相同元素
if (vec[i] <= vec[i - 1]) return false;
}
return true;
}
};
class Solution {
public:
TreeNode* pre = NULL; // 用来记录前一个节点
bool isValidBST(TreeNode* root) {
if (root == NULL) return true;
bool left = isValidBST(root->left);
if (pre != NULL && pre->val >= root->val)
{
return false;//前一个值一定比后一个值小 如果大 那就return
}
pre = root; // 记录前一个节点
bool right = isValidBST(root->right);
return left && right;
//return true;
}
};
class Solution {
public:
TreeNode* pre = NULL; // 用来记录前一个节点
bool isValidBST(TreeNode* root) {
if (root == NULL) return true;
bool left = isValidBST(root->left);
if (pre != NULL && pre->val >= root->val)
{
return false;//前一个值一定比后一个值小 如果大 那就return
}
if(left==false)
{
return false;
}
pre = root; // 记录前一个节点
bool right = isValidBST(root->right);
if(right==false)
{
return false;
}
//return left && right;
return true;
}
};
class Solution {
public:
TreeNode* pre =NULL;
int flat=0;
void dfs(TreeNode* root)
{
if(flat==1) return;
if(root==NULL) return;
dfs(root->left);
if(pre!=NULL && pre->val >= root->val)
{
flat=1;
return;
}
else//要么是pre为空 要么正常情况
{
pre = root;
}
dfs(root->right);
}
bool isValidBST(TreeNode* root)
{
dfs(root);
if(flat==1) return false;
else return true;
}
};
class Solution {
public:
int maxDepth(TreeNode* root)
{
if(root == nullptr)//想想初始点
{
return 0;
}
//处理当前层的逻辑
int left = maxDepth(root->left);
int right = maxDepth(root->right);
int ret=max(left,right)+1;
return ret;
}
};
代码随想录
class Solution {
public:
int getDepth(TreeNode* node) {
if (node == NULL) return 0;
int leftDepth = getDepth(node->left); // 左
int rightDepth = getDepth(node->right); // 右
// 中
// 当一个左子树为空,右不为空,这时并不是最低点
if (node->left == NULL && node->right != NULL) {
return 1 + rightDepth;
}
// 当一个右子树为空,左不为空,这时并不是最低点
if (node->left != NULL && node->right == NULL) {
return 1 + leftDepth;
}
int result = 1 + min(leftDepth, rightDepth);
return result;
}
int minDepth(TreeNode* root) {
return getDepth(root);
}
};
class Solution {
public:
int minDepth(TreeNode* root)
{
if(root == NULL) return 0;
int leftDepth = minDepth(root->left); // 左
int rightDepth = minDepth(root->right); // 右
if(leftDepth == 0 && rightDepth !=0)
{
return rightDepth+1;
}
else if(leftDepth!=0 && rightDepth==0)
{
return leftDepth+1;
}
else if(leftDepth==0 && rightDepth==0)
{
return 1;
}
else
{
return min(leftDepth,rightDepth)+1;
}
}
};
代码回想录
求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从低向上的遍历方式。
在回溯的过程中,必然要遍历整颗二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。
要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果。
后续 就是从底向上 先左边后右边 一层一层向上,这个要明确
(452 673 1)后(左右跟) 只要左边还有就继续深入
(4 2 3 1 6 3 7)中(左跟右)只要左边还有继续深入否则输出3个为一个结构
终止条件:遇到p 或者遇到q 或者为空 就开始返回(当然空 就返回空 非空返回节点地址)
当前层的逻辑就是:
(情况一:确实只找到一个) 情况二:找到了两个 它的祖先就是当前的一边而已
不管什么情况最后返回到跟的就是结果(这种遍历方式是需要走一遍的)好像也有那种遇到就一路返回的,我之前的那种方式,主要现在这种方式 你没办法判断返回的是哪一种情况 只能确定返回到跟的必然是结果
进入下一层 包含在逻辑里面了
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
{
if (root == q || root == p || root == NULL) return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (left != NULL && right != NULL) return root;
if (left == NULL && right != NULL) return right;
else if (left != NULL && right == NULL) return left;
else { // (left == NULL && right == NULL)
return NULL;
}
}
};
preorder_left > preorder_right 关注一下为什么以这个作为终止条件 其实你看传入的参数 前序的左边界一定是上一次左边界+1(也就是下一个)右边界是上一次左边界加长度(为空=+0 ) 所以就这样了
终止条件 就是元素只有一个的时候
先序序列的第一个作为根节点 找到在中序的下标
先序遍历 构建出第一个跟节点
通过中序序列 求得左子树长度 记得+1
root->left=递归函数 左边(两个序列,前序边界(+1,加上长度-1)中序边界(left 和中间-1))
同理右边 ()
dfs主要分为几步
第二遍复习
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)//简单的说就是序列长度为0
{
return nullptr;
}
// 前序遍历中的第一个节点就是根节点
int preorder_root = preorder_left;
// 在中序遍历中定位根节点
int inorder_root = index[preorder[preorder_root]];
// 先把根节点建立出来
TreeNode* root = new TreeNode(preorder[preorder_root]);
// 得到左子树中的节点数目-1 少一个
int size_left_subtree = inorder_root - inorder_left + 1;
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree-1, inorder_left, inorder_root - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root->right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree-1 + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
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);
}
};
代码回想录
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(int n, int k, int startIndex) {
if (path.size() == k) {
result.push_back(path);
return;
}
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) { // 优化的地方
path.push_back(i); // 处理节点
backtracking(n, k, i + 1);
path.pop_back(); // 回溯,撤销处理的节点
}
}
public:
vector<vector<int>> combine(int n, int k) {
backtracking(n, k, 1);
return result;
}
};
作者:carlsun-2
链接:https://leetcode-cn.com/problems/combinations/solution/dai-ma-sui-xiang-lu-dai-ni-xue-tou-hui-s-0uql/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution {
public:
vector<vector<int>> combine(int n, int k) {
dfs(n,k,1);
return Res;
}
vector<int> res;//这边的两个设为全局变量
vector<vector<int>> Res;
void dfs(int n, int k,int start)
{
//终止条件
if(res.size()==k)
{
Res.push_back(res);
return;
}
for(int i= start;i<=n;i++)
{
//处理当前层的逻辑
res.push_back(i);
dfs(n,k,i+1);//进入下一层
res.pop_back();//回溯
}
}
};
代码回想录
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking (vector<int>& nums, vector<bool>& used) {
// 此时说明找到了一组 终止条件
if (path.size() == nums.size()) {
result.push_back(path);
return;
}
for(int i=0;i<nums.size();i++)//解释说 12 21 发现1用了两次和组合不同 所以必须从1开始遍历 要不出现不了 21 这种 start必然从小到大排列的
{
if(used[i]==true) continue;
used[i]=true;
path.push_back(nums[i]);
backtracking(nums,used);
path.pop_back();//回退
used[i]=false;//状态也回退
}
}
vector<vector<int>> permute(vector<int>& nums) {
result.clear();
path.clear();
vector<bool> used(nums.size(), false);//其实这个也可以设置成全局这样 就不用传入作为引用了
backtracking(nums, used);
return result;
}
};
补充一个 就是把括号和全排列 组合联想一起 for循环不就等于几个单独的if 区间么
class Solution {
public:
vector<int> t;//全局变量
vector<vector<int>> ans;//全局变量
void dfs(int cur, vector<int>& nums) {
if (cur == nums.size())//终止条件 当前位置等于总长度
{
ans.push_back(t);//cur 表示的是当前位置
return;
}
//选择考虑当前位置
t.push_back(nums[cur]);
dfs(cur + 1, nums);//进入下一层(下一个位置)
t.pop_back();//什么时候删除,走出来函数就删除
//考虑不走当前位置
dfs(cur + 1, nums);
}
vector<vector<int>> subsets(vector<int>& nums) {
dfs(0, nums);
return ans;
}
};
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex) {
result.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) {
result.clear();
path.clear();
backtracking(nums, 0);
return result;
}
};
上面这个 你认真看他在退出前收集了 不是在退出的时候收集 认真对比一下这个代码和组合的区别
比如说排序 先排2个 再排 4 再排8个
比如这个题 求x 八次方 我们一个先求x 2 再 x4 再 x8 这种问题就是后续
然后我们要确定参数和返回值,我们就思考这个是一层一层往下 从下返回结果,参数就是x 还有 n的阶层 这两个都简单,然后返回值因为是通过下层的结果返回上来 所以返回值肯定是具体的次方结果
class Solution {
public:
double quick(double x,int n)
{
//1终止跳进
if(n == 1)
{
return x;
}
//2进入下一层
double y = quick(x,n/2);
//3分治
if(n % 2==0)
{
return y*y;
}
else
{
return y*y*x;
}
}
double myPow(double x, int n) {
long long N=n;
double res;
if( n == 0)
{
return 1;
}
if(N>0)
{
res =quick(x,N);
}
else
{
res =1.0/quick(x,-N);
}
return res;
}
};
就是元素个数大于1/2的数 叫做多数
方法比较多 用哈希 或者取巧的方法都可以 递归也行,这边复习一下递归的写法
根据上一题总结的思路 重新写了一遍 加上注释没啥问题 很完美 这种序列二分我们要执行两个递归函数 并传入边界(时间复杂度o(nlogn) 空间复杂度 0(logn))
class Solution {
public:
//构建了一个辅助函数
int count_number(vector<int>&nums,int target,int l,int r)
{
int count=0;
for(int i=l;i<=r;i++)
{
if(nums[i]==target)
{
count++;
}
}
return count;
}
//递归函数
int helper(vector<int>&nums,int l,int r)
{
//终止条件
if(l==r)//说明只有一个元素 就是众数
{
return nums[l];
}
int mid=l+(r-l)/2;
int left = helper(nums,l,mid);//后序 先递归
int right =helper(nums,mid+1,r);
//后序的第三部分 或者说分治 left 和 right都是下面(子的结果)
if(left == right) return left;
else//否则分别在当前层 统计他们的个数
{
int left_count= count_number(nums,left,l,r);
int right_count =count_number(nums,right,l,r);
if(left_count > right_count) return left;
else return right;
}
}
int majorityElement(vector<int>& nums)
{
int ret =helper(nums,0,nums.size()-1);
return ret;
}
};
下一个格子只能在比当前字母大的选 所以用了一个start
选过的都可以选
所以用了一个状态的数组和每次从0遍历达到一个效果自己写了一遍 没啥问题
class Solution {
public:
unordered_map<char,string> phone
{
{'2', "abc"},
{'3', "def"},
{'4', "ghi"},
{'5', "jkl"},
{'6', "mno"},
{'7', "pqrs"},
{'8', "tuv"},
{'9', "wxyz"}
};
//返回值没啥好说的觉就是void
void helper(vector<string>& result,string& ans,string& digits ,int index)
{
//终止条件 下标越界 ==长度
if(index == digits.length())
{
result.push_back(ans);
return;
}
//特别指出 在这 做一些准备操作
char digit = digits[index];//取出数字
string letters = phone[digit];//取出对应的字符串
for(int i=0;i<letters.length();i++)
{
ans.push_back(letters[i]);
helper(result,ans,digits,index+1);
ans.pop_back();
}
}
vector<string> letterCombinations(string digits) {
vector<string> result;
if(digits.empty())
{
return result;
}
//构建一个map 键就是数字(char类型)
string ans;//这个存放的一维的数据
helper(result,ans,digits,0);//回溯函数
return result;
}
};
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> largestValues(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);//老样子 初始把值放入
vector<int> result;//这次不需要二维了
while (!que.empty())
{
int size = que.size();
int maxValue = INT_MIN; // 取每一层的最大值 这个也是临时变量 每次for循环之前都要初始化
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
if(node->val>maxValue)
{
maxValue=node->val;
}
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
result.push_back(maxValue); // 把最大值放进数组
}
return result;
}
};
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> largestValues(TreeNode* root)
{
if(root == nullptr) return{};
int flat =1;
int max_value= INT_MIN;
vector<int> result;
queue<TreeNode*> que;
que.push(root);
while(que.empty()!=true)
{
auto p = que.front();
que.pop();
max_value=max(max_value,p->val);
if(p->left) que.push(p->left);
if(p->right) que.push(p->right);
flat--;
if(flat == 0)
{
flat=que.size();
result.push_back(max_value);
max_value=INT_MIN;
}
}
return result;
}
};
其实这个题最重要的还是理清楚为什么要用广度搜索,其他倒是没什么
一个容易错的就是 两个for循环 为了防止修改多个位置,所以需要 在一个for循环创建一个临时变量
第二个就是 两个for循环嵌套的第一个if判断就是这个单词是不是word end 如果是返回path+1 记得+1。 第二个if如果在set出现(说明是字典集的)不在map出现(说明没有重复,这个是这题的精髓 存在重复的)那就把这这个单词放在队列 并且把他和path+1 放入到map中
需要注意为什么初始放入path为1 他这个path是指序列的长度 而不是变化的次数 注意读题
第二遍复习
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
unordered_set<string> wordset(wordList.begin(),wordList.end());//创建一个set用来放入字典的字符串方便查询
unordered_map<string,int> visitmap;//创建一个map方便取出对应的步数+1
//进行特判
if(wordset.find(endWord)==wordset.end())
{
return 0; //直接返回结果
}
//标准创建一个队列
queue<string> que;
que.push(beginWord);
//这边还需要放入map中
visitmap[beginWord]=1;
//这边不需要设置标志位了 因为确实不需要一层一层处理了
while(que.empty()!=true)
{
string word = que.front();//取出队列哪一个
que.pop();//删除哪一个
int path=visitmap[word];//取出路径
//这边正常层次遍历做的应该是一个for循环 把孩子都放进去.本题这边是两个for循环
//特别容易错的
for(int i=0;i<word.size();i++)
{
string newword= word;//这边是必须的 每次操作都是在全新的单词上操作 否则就变成叠加操作了
for(int j=0;j<26;j++)
{
newword[i]=j+'a';
if(newword == endWord) return path+1;
if(wordset.find(newword)!=wordset.end() && visitmap.find(newword) == visitmap.end())
{
//两个同时满足
visitmap[newword]=path+1;//记得+1
que.push(newword);
}
}
}
}
return 0; //都遍历一遍都找不到 那就返回01把
}
};
他就是遇到岛屿先把自己标记成遍历过的 再向四周扩散 注意顺序
class Solution {
public:
bool inArea(vector<vector<char>>& grid,int r,int c)
{
//进行边界判断
bool a = (0 <= r && r <= grid.size()-1);
bool b =(0 <=c && c<=grid[0].size()-1);
return a && b;
}
//传入网格 传入坐标
void dfs(vector<vector<char>>& grid,int r,int c)
{
//终止条件判断 越出网格边界
if(inArea(grid,r,c)==false)
{
return;
}
//处理当前层的逻辑 先序
if(grid[r][c]!='1')//表示标记过了 或者是海 不能写==0
{
return;
}
grid[r][c]=2;//把他变换成’海‘
//进入下一层
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c - 1);
dfs(grid, r, c + 1);
}
int numIslands(vector<vector<char>>& grid)
{
int nr = grid.size();
if (!nr) return 0;
int nc = grid[0].size();
int num_islands = 0;
for (int r = 0; r < nr; ++r)
{
for (int c = 0; c < nc; ++c)
{
if (grid[r][c] == '1')//这边判断和递归函数内部的判断不冲突 递归函数内的判断是扩散的判断
{
++num_islands;
dfs(grid, r, c);
}
}
}
return num_islands;
}
};
class Solution {
public:
int num_islands=0;
bool inArea(vector<vector<int>>& grid,int r,int c)
{
//进行边界判断
bool a = (0 <= r && r <= grid.size()-1);
bool b =(0 <=c && c<=grid[0].size()-1);
return a && b;
}
//传入网格 传入坐标
void dfs(vector<vector<int>>& grid,int r,int c)
{
//终止1条件判断 越出网格边界
if(inArea(grid,r,c)==false)
{
num_islands++;
return;
}
//终止2
if(grid[r][c]==2)//表示标记过了 或者是海 不能写==0
{
return;
}
//终止3
if(grid[r][c]==0)
{
num_islands++;
return;
}
//当前层的逻辑
grid[r][c]=2;//把他变换成’海‘
//进入下一层
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c - 1);
dfs(grid, r, c + 1);
}
int islandPerimeter(vector<vector<int>>& grid) {
int nr = grid.size();
if (!nr) return 0;
int nc = grid[0].size();
for (int r = 0; r < nr; ++r)
{
for (int c = 0; c < nc; ++c)
{
if (grid[r][c] == 1)//这边判断和递归函数内部的判断不冲突 递归函数内的判断是扩散的判断
{
dfs(grid, r, c);
return num_islands;
}
}
}
return num_islands;
}
};
这个题和之前有一点点不一样,这个是深度遍历 先序把把走过的标记
后序 统计子的面积也可以说是分治进行一个汇总 加上自己本身+1
经典先序+后续
还有一个就是遇到终止条件的设定
class Solution {
public:
bool inArea(vector<vector<int>>& grid,int r,int c)
{
//进行边界判断
bool a = (0 <= r && r <= grid.size()-1);
bool b =(0 <=c && c<=grid[0].size()-1);
return a && b;
}
//传入网格 传入坐标
int dfs(vector<vector<int>>& grid,int r,int c)
{
//终止条件判断 越出网格边界
if(inArea(grid,r,c)==false)
{
return 0;
}
// 也算是边界处理
if(grid[r][c]==2)//表示标记过了 或者是海 不能写==0
{
return 0;
}
//也算是边界处理
if(grid[r][c]==0)
{
return 0;
}
grid[r][c]=2;//把他变换成’海‘
//进入下一层
int a_ = dfs(grid, r - 1, c);
int b_ = dfs(grid, r + 1, c);
int c_ = dfs(grid, r, c - 1);
int d_ = dfs(grid, r, c + 1);
return a_+b_+c_+d_+1;
}
int maxAreaOfIsland(vector<vector<int>>& grid) {
int nr = grid.size();
if (!nr) return 0;
int nc = grid[0].size();
int max_ = 0;
for (int r = 0; r < nr; ++r)
{
for (int c = 0; c < nc; ++c)
{
if (grid[r][c] == 1)//这边判断和递归函数内部的判断不冲突 递归函数内的判断是扩散的判断
{
int max_num = dfs(grid, r, c);
max_ = max(max_num,max_);
}
}
}
return max_;
}
};
第二次写有些问题如下
void moveZeroes(int* nums, int numsSize){
int k=0;
int i = 0;
while(i<numsSize)
{
if(nums[i]!=0)
{
nums[k++]=nums[i++];
//指向下一个待填入的位置
}
else if(i<numsSize && nums[i]==0)
{
i++;
}
//如果是0 那就啥事没有等外层循环i++ 就好了
}
while(k<numsSize)
{
nums[k]=0;//变成0就好
k++;
}
}
class Solution {
public:
void moveZeroes(vector<int>& nums)
{
int k=0;
for(int i=0;i<nums.size();i++)
{
if(nums[i]!=0)
{
nums[k++]=nums[i];//K指向下一个待填入的地方
}
}
for(int i=k;i<nums.size();i++)
{
nums[i]=0;
}
}
};
class Solution {
public:
int maxArea(vector<int>& height)
{
int n = height.size();
int i = 0;
int j = n-1;
int max_=0;//这个表示最大的面积
while(i<j)
{
int max_new = (j-i)*min(height[i],height[j]);
max_=max(max_new,max_);
if(height[i]>height[j]) j--;
else i++;
}
return max_;
}
};
class Solution {
public:
int maxArea(vector<int>& height)
{
int n =height.size();
int i=0;
int j=n-1;
int max_=0;
while(i<=j)
{
max_=max(max_,(j-i)*min(height[i],height[j]));
if(height[i]>height[j])
{
j--;
}
else
{
i++;
}
}
return max_;
}
};
class Solution {
public:
int climbStairs(int n) {
if(n == 0) return 0;
if(n == 1) return 1;
int pre = 1;
int pre2 = 1;
int now;
for(int i = 2;i <= n;i++)
{
now =pre+pre2;
pre2 =pre;
pre = now;
}
return now;
}
};
res.push_back({nums[i],nums[left],nums[right]}); res.push_back(vector{nums[i],nums[left],nums[right]});
如何批量放入 这两种写法都是可以的当前值和上一个值比较的时候 要注意 i>0 易错 难找
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums)
{
int size = nums.size();
if (size < 3) return {};
vector<vector<int>> res;
sort(nums.begin(),nums.end());
for(int i = 0 ; i<size ; i++)
{
if (nums[i] > 0) return res;
if(i > 0 && nums[i]==nums[i-1]) continue;
int left = i+1;
int right = size - 1;
while(left < right)
{
if(nums[i]+ nums[left]+nums[right] > 0)
{
right--;
}
else if(nums[i]+ nums[left]+nums[right] < 0)
{
left++;
}
else
{
//res.push_back(vector{nums[i],nums[left],nums[right]});//放入
res.push_back({nums[i],nums[left],nums[right]});
right--; //收缩
left++;
while (left < right && nums[left]==nums[left-1]) left++;
while (left <right && nums[right]==nums[right+1]) right--;
}
}
}
return res;
}
};
00 1 举个例子 如果i和前一个一样那就后移 表示没找到不重复的数 直到找到了 那就放到k里面 然后k和i后移。如果前后一样 那就单独移动i继续寻找就好
主要是注意为什么两个初始值都是1
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int size =nums.size();
if (size < 2) return size;
int k = 1;
int i = 1;
while(i<size)
{
if(nums[i]==nums[i-1])
{
i++;
}
else
{
nums[k]=nums[i];
k++;i++;
}
}
return k;
}
};
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n= nums.size();
k = k % n;
reverse(nums.begin(),nums.end());
reverse(nums.begin(),nums.begin()+k);
reverse(nums.begin()+k,nums.end());
}
};
void reverse(vector<int>& nums, int start, int end) {
while (start < end) {
swap(nums[start], nums[end]);
start += 1;
end -= 1;
}
}
这个其实和归并排序考虑我问题一样 就是有剩余直接放进去 分成三个部分好像比较好考虑 其实第一个k不需要判断 只要i和j都大于0 那它不会小于0
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
//这边的 m 和 n 都表示的是元素的个数
int k = m + n-1;
int i = m-1;
int j = n-1;
while(k >= 0 && i >=0 && j >= 0)//表示当前的位置
{
if(nums1[i]>nums2[j])
{
nums1[k--]=nums1[i--];
}
else
{
nums1[k--]=nums2[j--];
}
}
while(i >= 0)
{
nums1[k--]=nums1[i--];
}
while(j >= 0)
{
nums1[k--]=nums2[j--];
}
}
};
补充一下 一遍hash的方法 他就是在插入的时候查看有没有存在target-num【i】的元素 有点巧妙 自己写一遍 代码我也自己写了一遍放在下面了
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target)
{
vector<int> ans;
int i = 0;
int j = nums.size()-1;
unordered_map<int,int> map;
for(int i = 0;i<nums.size();i++ )
{
map[nums[i]]=i;
}
sort(nums.begin(),nums.end());//排序一下
while(i < j)
{
if(nums[i]+nums[j] < target)
{
i++;
}
else if(nums[i]+nums[j] > target)
{
j--;
}
else
{
ans.push_back(map[nums[i]]);
ans.push_back(map[nums[j]]);
return ans;
}
}
return ans;
}
};
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target)
{
unordered_map<int,int> map;
vector<int> ans;
for(int i =0;i<nums.size();i++)
{
if(map.find(target-nums[i])==map.end())
{
map[nums[i]]=i;//没有匹配 那就插入
}
else
{
ans.push_back(i);
ans.push_back(map[target-nums[i]]);
break;
}
}
return ans;
}
};
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
for(int i = digits.size()-1;i >= 0 ;i--)
{
//899 变成 999
if(digits[i]+1 < 10)
{
digits[i]=digits[i]+1;
return digits;
}
else
{
digits[i] = 0;
}
//否则啥事不做 等到跳到前一个继续做判断
}
//如果走出了循环 说明是极端 999 这种
vector<int> ans(digits.size()+1,0);//比之前打一个
ans[0]=1;//把第一个变成1 就好了
return ans;
}
};
注意点一:就是循环条件是cur不为空 因为 cur 每次都是指向下一个待处理的节点(cur=next)如果它为空说明到结尾了 还有就是注意返回值是pre 这个移动的值
cur->next=pre
这句话怎么读 就是 cur的下一个节点是pre你要想想一个节点和它指出去的是一起的 当你变换它的方向 你就要保存它原本指向的值 就是这么简单
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = nullptr;
ListNode* cur =head;
ListNode* next;
while(cur!= nullptr)
{
next = cur->next;
cur->next=pre;
pre = cur;
cur = next;//我指向的是下一个待处理的值
}
return pre;
}
};
while(quick!=nullptr&&quick->next!=nullptr)
唯一会错的就是这句话,快指针在前面的 如果它为空那必然说明没有环 为什么还要判断 quit->next 如果你不判断这个 quick=quick->next->next;这句话会出错 你可以得到quick->next为空 但不可以再往下指下去/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head == NULL || head->next ==NULL)
{
return false; //
}
ListNode* slow = head;
ListNode* quick = head->next;
//while(slow != NULL&&quick!= NULL )
while(quick!=nullptr&&quick->next!=nullptr)
{
if(slow == quick)
{
return true;
}
else
{
slow=slow->next;//移动到下一个待比较的点
quick=quick->next->next;//移动到下一个待比较的点
}
}
return false;
}
};
还有个需要注意的是temp是有实际的new的 因为他要next 而且拷贝了一份 就是为了保存一开始的next关系 因为后面这个变量会产生变化不保存一开始这个地址了
下一个要加next 老是忘记
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* temp =new ListNode(0);
temp->next=head;
ListNode* temp1 =temp;//这个主要用于返回
while(temp->next!=nullptr &&temp->next->next!=nullptr)
{
//取出第二个点
ListNode* node2 = temp->next;
//取出第三个点
ListNode* node3 = temp->next->next;
//让第一个点指向(下一个)第三个点
temp->next=node3;
//让第二个点指向(下一个)第四个点
node2->next=node3->next;
//第三个点指向(下一个)第二个点
node3->next=node2;
//修改temp 想这 空 1 2 3 -> 空 2 1 3 1为新的temp 就是node2 第二个值
temp = node2;
}
return temp1->next;
}
};
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
// 1 2 3 4 1 2 3 4
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* temp =new ListNode(0);
temp->next=head;
ListNode* temp1 =temp;//这个主要用于返回
while(temp->next!=nullptr &&temp->next->next!=nullptr)
{
//取出第二个点
ListNode* node2 = temp->next;
//取出第三个点
ListNode* node3 = temp->next->next;
//取出第四个点
ListNode* node4 =temp->next->next->next;
//让第一个点指向(下一个)第三个点
temp->next=node3;
//让第三个点指向(下一个)第二个点
node3->next=node2;
//第二个点指向(下一个)第四个点
node2->next=node4;
//修改temp 想这 空 1 2 3 -> 空 2 1 3 1为新的temp 就是node2 第二个值
//修正新的值
temp=node2;
}
return temp1->next;
}
};
//0716修正写法 对比之前的快慢指针
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head==nullptr||head->next==nullptr)
{
return nullptr;
}
ListNode* slow=head;
ListNode* quick=head;
while(quick!=nullptr&&quick->next!=nullptr)
{
slow=slow->next;
quick=quick->next->next;
if(quick==slow)
{
ListNode *ptr = head;
while (ptr != slow)
{
ptr = ptr->next;
slow = slow->next;
}
return ptr;
}
}
return nullptr;
}
};
class Solution {
public:
bool isValid(string s) {
stack<char> stk;
for(int i=0;i<s.length();i++)
{
char c =s[i];
if(stk.empty()!= true)
{
char t = stk.top();
if(t=='[' && c==']' || t=='(' && c==')' || t=='{' && c=='}')
{
stk.pop();
}
else
{
stk.push(c);
}
}
else
{
stk.push(c);
}
}
return stk.empty();
}
};
这次是第三次做了
之前有一个误区 其实左右边界的定义我们是很明确了,取出的元素作为高度我们也很明确了,还有一个我们要知道的是取出元素是作为最小值,计算面积的时候它是在中间的 举个例子 比如 789 1 计算8的面积的时候 他就是在最左边 2 3 9 8 1 先弹出了9 计算8是它是在最右边
总而言之我就是向这样能考虑到所有情况么 显然是可以的
我觉的这个题最难的是数组头插和尾差0 和栈的插入0 就是不理解数组不头插入0会越界 这个是咋无法理解 ,它有一个好处就是方便计算 举个例子 一个数1 10(下标 0 1)计算面积 就会是1-0-1=0*高度 这样就无法计算开始的那个矩阵(最后不断测试找到原因了 就是如果一开始我们数组不放入0 那么栈中放入的那个0!!! 这个下标最后对应的一定是一个大于0的数 所以数组最后的零会吧这个数取出作为高度 然后再stack.top取他到左边界就会报错)
还有一个就是无论比它大还是小都要放入矩阵,这个很容易忘记
第二遍复习
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
stack<int> stack;
heights.insert(heights.begin(),0);
heights.push_back(0);//尾巴放入是让最后所有剩余的值作为高度进行计算
//栈中放入的下标
stack.push(0);//这个是方便一开始就可以比较 比较时候表示数组内第一个值
int max_result = 0;
for(int i=0;i<heights.size();i++)
{
//不能是if 判断 可以不断的取 所以用while
while(heights[i]<heights[stack.top()])
{
int h = stack.top();//高度
stack.pop();//及时删除 高度 方便取后面的
int w = i- stack.top()-1;
max_result=max(max_result,w*h);
}
//1处理好后 2 或者 一开始就大于等于
stack.push(i);
}
}
};
连接
1不同点在于这边一开始栈中防御第一个元素的下标 0
for循环从一开始
while情况处理除了 要加入的对应的值比之前的大 还要求栈不为空!!!!!! 认真看 栈不为空放前面 后面就出错了
我自己写有一个错误就是在whike循环取出凹槽的高度后 不是还要取出左边界进行计算面积么 这边需要一个判断就是在取出 pop后判断此时栈的top是否为空 为空就啥都不做 不管他 让他一起取出中间然后为空退出
765 1 9 模拟一下面积的计算 凹槽的面积是由三个三个独立的矩阵构成的 注意
class Solution {
public:
int trap(vector<int>& height) {
stack<int> stack;
//从大到小排序是正常的
stack.push(0);//把第一个元素下标放进去 到时候从第二个开始遍历
int sum=0;
for(int i=1;i<height.size();i++)
{
//要放入的(还没放)比栈大 说明形成了凹槽 进行处理
while(stack.empty()!=true && height[stack.top()] < height[i])
{
int mid = stack.top();//低点的下标
stack.pop();
if ( stack.empty()!= true)
{
//这个就是凹槽的左右两边的下标 - 中间凹处的高度
int h = min(height[i],height[stack.top()]) - height[mid];
//宽度老样子
int w = i - stack.top()-1;
sum =sum + w*h;
}
}
//特殊情况处理完 都要放入
stack.push(i);
}
return sum;
}
};
class Solution {
public:
int trap(vector<int>& height)
{
stack<int> stack;
stack.push(0);
int sum=0;
for(int i=1;i<height.size();i++)
{
//这两个empty()判断是必须的 举个例子 1234
//不用记住那么多 就是用到就判断
while(stack.empty()!=true && height[stack.top()]<height[i])
{
int mid =stack.top();//最低点
stack.pop();
if(stack.empty()!=true)
{
int h = min(height[i],height[stack.top()]) - height[mid];
int w =i-stack.top()-1;//
sum =sum + w*h;
}
//特殊情况处理完 都要放入
}
stack.push(i);
}
return sum;
}
};
- 没什么问题
- 在上一题的基础上继续改进
- 首先就是nums1构建map 方便o(1)的查找
- 特别注意初始值 如果找到到下一个比我大的应该返回多少,初始值就设置为多少 )!!!
通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。当然接雨水和 矩阵的最大面积这种抽象的 要记住也行 时间复杂度是0(n)
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures)
{
stack<int> stack;
vector<int> result(temperatures.size(),0);//
stack.push(0);//把第一个放入方便比较
for(int i=1;i<temperatures.size();i++)
{
//你想想正常肯定 从大到小因为啥都做不了 出现大的了 就可以计算答案了 合理
while(stack.empty()!=true && temperatures[stack.top()] < temperatures[i])
{
result[stack.top()] = i- stack.top();//这个就是计算间隔几天了
stack.pop();//删除顶端元素
}
stack.push(i);
}
return result;
}
};
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures)
{
stack<int> stack;
stack.push(0);//方便比较
vector<int> result(temperatures.size(),0);//保存结果
//单调减
for(int i=1;i<temperatures.size();i++)
{
while( stack.empty()!=true && temperatures[i]>temperatures[stack.top()])
{
//这边不需要想接雨水 需要上一个值 还加个if判断
result[stack.top()]=i-stack.top();
stack.pop();
}
stack.push(i);
}
return result;
}
};
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2)
{
vector<int> result(nums1.size(),-1);//初始放入-1
stack<int> stack;
if(nums1.size() == 0) return result;
unordered_map<int,int> map;//键是值 值是下标 我们要利用这个在结果集对应位置放入结果
for(int i =0;i<nums1.size();i++)
{
map[nums1[i]]=i; //题目说了不包含重复的元素
}
stack.push(0);
for(int i =1 ; i<nums2.size();i++)
{
while(stack.empty()!= true && nums2[stack.top()] < nums2[i])
{
//查看一下 比要插入小的这个值在不在map里买呢
if(map.count(nums2[stack.top()]) == 1)
{
//存在就取出他的下标
int index = map[nums2[stack.top()]];
result[index] = nums2[i];//题目要放入第一个比它大的值 所以这边把值放入
}
stack.pop();//记得及时删除 这边删除两个意思 一个是存在处理好删除 一个是不存在也删除
}
stack.push(i);
}
return result;
}
};
连接
// 版本二
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
vector<int> result(nums.size(), -1);
if (nums.size() == 0) return result;
stack<int> st;
int n = nums.size();
for (int i = 0; i < nums.size() * 2; i++) {
// 模拟遍历两边nums,注意一下都是用i % nums.size()来操作
while (!st.empty() && nums[i % n] > nums[st.top()]) {
result[st.top()] = nums[i % n];
st.pop();
}
st.push(i % n);
}
return result;
}
};
我们这边要实现的是单调递增的队列(头部(新加入的)---尾部(过去加入的)) 为什么呢 如果 1过去的值比我小 还比我早加入(下标比我小)比我2早淘汰为啥选你啊
为了维护这个递增单调队列(区别于普通的队列)我们知道普通队列的底层实现就是deque 所以我们自己构造一个
class Solution {
private:
class MyQueue
{ //单调队列(从大到小)
public:
deque<int> que; // 使用deque来实现单调队列
// 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
// 同时pop之前判断队列当前是否为空。
void pop(int value) {
if (!que.empty() && value == que.front()) {
que.pop_front();
}
}
// 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
// 这样就保持了队列里的数值是单调从大到小的了。
void push(int value) {
while (!que.empty() && value > que.back()) {
que.pop_back();
}
que.push_back(value);
}
// 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
int front() {
return que.front();
}
};
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
MyQueue que;
vector<int> result;
for (int i = 0; i < k; i++) { // 先将前k的元素放进队列
que.push(nums[i]);
}
result.push_back(que.front()); // result 记录前k的元素的最大值
for (int i = k; i < nums.size(); i++) {
que.pop(nums[i - k]); // 滑动窗口移除最前面元素
que.push(nums[i]); // 滑动窗口前加入最后面的元素
result.push_back(que.front()); // 记录对应的最大值
}
return result;
}
};
class Solution {
private:
class MyQueue
{ //单调队列(从大到小)
public:
deque<int> que; // 使用deque来实现单调队列
// 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
// 同时pop之前判断队列当前是否为空。
void pop(int value) {
if (!que.empty() && value == que.back()) {
que.pop_back();
}
}
// 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
// 这样就保持了队列里的数值是单调从大到小的了。
void push(int value) {
while (!que.empty() && value > que.front()) {
que.pop_front();
}
que.push_front(value);
}
// 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
int front() {
return que.back();
}
};
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
MyQueue que;
vector<int> result;
for (int i = 0; i < k; i++) { // 先将前k的元素放进队列
que.push(nums[i]);
}
result.push_back(que.front()); // result 记录前k的元素的最大值
for (int i = k; i < nums.size(); i++) {
que.pop(nums[i - k]); // 滑动窗口移除最前面元素
que.push(nums[i]); // 滑动窗口前加入最后面的元素
result.push_back(que.front()); // 记录对应的最大值
}
return result;
}
};
class Solution {
public:
class MyQueue
{
public:
//单调队列
deque<int> que;
MyQueue()
{}
void push(int value)//我们指定从头部放入
{
//先把比我小的都去除掉
while(que.empty()!= true && value > que.front())
{
que.pop_front();
}
//处理好后放入
que.push_front(value);
}
void pop( int value)//我们指定从尾部去除 去除旧的
{
//这边做判断 是因为 这个元素可能已经不存在了
if( que.empty()!= true && que.back() == value)
{
que.pop_back();
}
}
//返回尾巴的元素 最大值 返回值为整形
int back()
{
return que.back();
}
};
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
MyQueue que;
vector<int> result;//存放结果
//为什么两个循环呢 两个阶段被 前面k个是一直放入 一个最大值
//后面都是放入一个取出一个 最大值每次都有一个
for(int i =0 ; i<k ; i++)
{
que.push(nums[i]);
}
//记得把第一个加入进去 老是忘记
result.push_back(que.back());
for(int i =k;i<nums.size();i++)
{
//这两个顺序无所谓吧
que.push(nums[i]);
que.pop(nums[i-k]);
result.push_back(que.back());
}
return result;
}
};