##每日一题##
根据前序遍历和中序遍历构造二叉树
思路:从性质出发,前序数组的开头就是根节点,然后对应到中序数组,得到左子树和右子树,分别再构造左子树和右子树
下标对应:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hrGX1T3d-1595485102474)(C:\Users\86178\Desktop\力扣\图片\2020-05-22_085059.png)]
优化:
将中序数组存入哈希表,方便找到对用的下标
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
class Solution {
public:
unordered_map<int,int> map;
TreeNode* create(vector<int>& preorder,int preLeft,int preRight,int inLeft,int inRight){
if(preLeft>preRight || inLeft>inRight)
return NULL;
int rootVal=preorder[preLeft];
TreeNode* root=new TreeNode(rootVal);
int index=map[rootVal];
root->left=create(preorder,preLeft+1,preLeft+index-inLeft,inLeft,index-1);
root->right=create(preorder,preLeft+index-inLeft+1,preRight,index+1,inRight);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int preLen=preorder.size();
int inLen=inorder.size();
TreeNode* root;
for (int i = 0; i < inLen; ++i) {
map.insert({inorder[i],i});
}
return create(preorder,0,preLen-1,0,inLen-1);
}
};
寻找两个正序数组的中位数
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。
请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
nums1 = [1, 3] nums2 = [2] 则中位数是 2.0 nums1 = [1, 2] nums2 = [3, 4] 则中位数是 (2 + 3)/2 = 2.5
归并排序的思路
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int length1 = nums1.size();
int length2 = nums2.size();
int i,j,k;
vector<int> temp(length1+length2, 0);
for(i=0,j=0,k=0;i<length1 && j<length2;k++)
{
if(nums1[i]<nums2[j])
{
temp[k] = nums1[i];
i++;
}
else
{
temp[k] = nums2[j];
j++;
}
}
while(i<length1)
temp[k++]=nums1[i++];
while(j<length2)
temp[k++]=nums2[j++];
if((length1+length2)%2 ==1)
return temp[(length1+length2)/2];
else
return (temp[(length1+length2)/2]+temp[(length1+length2)/2-1])/2.0;
}
};
二分查找的思路
太麻烦了
class Solution {
public:
int getKthElement(const vector<int>& nums1, const vector<int>& nums2, int k) {
/* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
* 这里的 "/" 表示整除
* nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
* nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
* 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
* 这样 pivot 本身最大也只能是第 k-1 小的元素
* 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
* 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
* 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
*/
int m = nums1.size();
int n = nums2.size();
int index1 = 0, index2 = 0;
while (true) {
// 边界情况
if (index1 == m) {
return nums2[index2 + k - 1];
}
if (index2 == n) {
return nums1[index1 + k - 1];
}
if (k == 1) {
return min(nums1[index1], nums2[index2]);
}
// 正常情况
int newIndex1 = min(index1 + k / 2 - 1, m - 1);
int newIndex2 = min(index2 + k / 2 - 1, n - 1);
int pivot1 = nums1[newIndex1];
int pivot2 = nums2[newIndex2];
if (pivot1 <= pivot2) {
k -= newIndex1 - index1 + 1;
index1 = newIndex1 + 1;
}
else {
k -= newIndex2 - index2 + 1;
index2 = newIndex2 + 1;
}
}
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int totalLength = nums1.size() + nums2.size();
if (totalLength % 2 == 1) {
return getKthElement(nums1, nums2, (totalLength + 1) / 2);
}
else {
return (getKthElement(nums1, nums2, totalLength / 2) + getKthElement(nums1, nums2, totalLength / 2 + 1)) / 2.0;
}
}
};
上一题的前导问题的多种解法(合并两个有序数组)
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中*,*使 nums1 成为一个有序数组。
从前到后
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
vector<int> temp(nums1);
int i,j,k;
for (i=0,j=0, k = 0; i<m && j<n ; ++k) {
if(temp[i]<=nums2[j]){
nums1[k]=temp[i];
i++;
} else{
nums1[k]=nums2[j];
j++;
}
}
while (i<m)
nums1[k++]=temp[i++];
while (j<n)
nums1[k++]=nums2[j++];
}
};
从后到前
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int i=m-1,j=n-1,k=m+n-1;
while (i>=0 && j>=0){
if(nums1[i]>nums2[j])
nums1[k--]=nums1[i--];
else
nums1[k--]=nums2[j--];
}
while (j>=0)
nums1[k--]=nums2[j--];
while (i>=0)
nums1[k--]=nums1[i--];
}
};
排序
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
for(auto x:nums2){
nums1[m++]=x;
}
sort(nums1.begin(),nums1.end());
}
};
287. 寻找重复数
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。 输入: [1,3,4,2,2] 输出: 2 说明: 不能更改原数组(假设数组是只读的)。 只能使用额外的 O(1) 的空间。 时间复杂度小于 O(n2) 。 数组中只有一个重复的数字,但它可能不止重复出现一次。
直接用set处理
class Solution {
public:
int findDuplicate(vector<int>& nums) {
unordered_set<int> set;
for (int i = 0; i < nums.size(); ++i) {
if(set.find(nums[i])!=set.end())
return nums[i];
set.insert(nums[i]);
}
return -1;
}
};
快慢指针
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int slow=0;
int fast=0;
while(1){
slow=nums[slow];
fast=nums[nums[fast]];
if(slow==fast){
fast=0;
while(1){
if(fast==slow)
return slow;
slow=nums[slow];
fast=nums[fast];
}
}
}
}
};
二分法
预备知识:抽屉原理:桌上有十个苹果,要把这十个苹果放到九个抽屉里,无论怎样放,我们会发现至少会有一个抽屉里面放不少于两个苹果。
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int left=1;
int right=nums.size()-1;
while(left<right){
int mid=left+(right-left)/2;
int count=0;
for(auto x:nums){
if(x<=mid)
count++; //小于mid的数,例如小于4的数是1,2,3,4
}
if(count>mid) //如果大于等于4个 那么重复元素一定在[1,4]的的区间
right=mid;
else
left=mid+1;
}
return left;
}
};
394. 字符串解码
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
s = "3[a]2[bc]", 返回 "aaabcbc". s = "3[a2[c]]", 返回 "accaccacc". s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef".
class Solution {
public:
string decodeString(string s) {
string res;
int count = 0;
int left,right,lr=0;
for(int i=0;i<s.size();i++){
if('a'<=s[i] && s[i]<='z'||'A'<=s[i] && s[i]<='Z'){
if(count==0)
res.push_back(s[i]);
}
else if('0'<=s[i]&&s[i]<='9'){
if(lr==0)
count = 10*count + (s[i] - '0'); //统计重复次数
}
else if(s[i]=='['){
if(lr==0) //遇到第一个 '['
left=i;
lr++;
}
else if(s[i]==']'){
lr--;
if(lr==0){ // 遇到最后一个 ']'
right=i;
while(count>0){
res += decodeString(s.substr(left+1,right-left-1));
count--;
}
}
}
}
return res;
}
};
101. 对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。 1 / \ 2 2 / \ / \ 3 4 4 3 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: 1 / \ 2 2 \ \ 3 3
思路:镜像对称的二叉树要求 一个树的左子树与右子树镜像对称,那么这个树是对称的。
那么两个树在什么情况下互为镜像?
1、他们两个根节点的值相同
2、每个树的左子树和另一棵树的右子树镜像对称
class Solution {
public:
bool check(TreeNode* leftRoot,TreeNode* rightRoot){
if(leftRoot==NULL && rightRoot==NULL) //两个都为空肯定对称
return true;
if(leftRoot==NULL || rightRoot==NULL)//一个空 一个不空肯定不对称
return false;
return (leftRoot->val==rightRoot->val) && check(leftRoot->left,rightRoot->right) && check(leftRoot->right,rightRoot->left); //只有1、他们两个根节点的值相同 2、每个树的左子树和另一棵树的右子树镜像对称 才是对称的
}
bool isSymmetric(TreeNode* root) {
return check(root,root);
}
};
238. 除自身以外数组的乘积
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
输入: [1,2,3,4]
输出: [24,12,8,6]
嘿嘿
思路就是计算左边 计算右边 乘一下
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int sum=1;
vector<int> res;
int left[nums.size()];
int right[nums.size()];
left[0]=1;
for (int i = 1; i < nums.size(); ++i) {
left[i]=left[i-1]*nums[i-1];
}
right[nums.size()-1]=1;
for (int i = nums.size()-2; i >=0; --i) {
right[i]=right[i+1]*nums[i+1];
}
for (int i = 0; i < nums.size(); ++i) {
res.push_back(left[i]*right[i]);
}
return res;
}
};
大佬的精简代码
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int sum=1;
vector<int> res(nums.size(),1);
vector<int> left(nums.size(),1);
vector<int> right(nums.size(),1);
for (int i = 0; i < nums.size(); ++i) {
left[i]*=sum;
sum*=nums[i];
}
sum=1;
for (int i = nums.size()-1; i >=0; --i) {
right[i]*=sum;
sum*=nums[i];
res[i]=left[i]*right[i];
}
return res;
}
};
面试题29. 顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]] 输出:[1,2,3,6,9,8,7,4,5]
模拟
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> res;
if(matrix.size()==0)
return res;
int x=0,y=0;
vector<vector<int>> visited(matrix.size(),vector<int>(matrix[0].size(),0));
res.push_back(matrix[0][0]);
visited[0][0]=1;
while(res.size()<matrix.size()*matrix[0].size()){
while(y+1<matrix[0].size() && !visited[x][y+1]){
res.push_back(matrix[x][++y]);
visited[x][y]=1;
}
while(x+1<matrix.size() && !visited[x+1][y]){
res.push_back(matrix[++x][y]);
visited[x][y]=1;
}
while(y-1>=0 && !visited[x][y-1]){
res.push_back(matrix[x][--y]);
visited[x][y]=1;
}
while(x-1>=0 && !visited[x-1][y]){
res.push_back(matrix[--x][y]);
visited[x][y]=1;
}
}
return res;
}
};
128. 最长连续序列
给定一个未排序的整数数组,找出最长连续序列的长度。
要求算法的时间复杂度为 O(n)。
输入: [100, 4, 200, 1, 3, 2] 输出: 4 解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。
哈希法
思路: 重复元素就很烦,先用set去重,然后用set也方便查看序列是否包含某个元素
1、核心思路是对于每个元素x查看x+1,x+2…x+y是否存在,并且在断链之后更新res
但是这样的话极端情况下,复杂度会到N^2
继续思考,其实没必要对于每个元素都查看一下的,对于元素链上的每一个元素 其实都已经在产生了对应的currRes 所以 1操作加以限制
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
if(nums.size()==0){
return 0;
}
unordered_set<int> num_set;
for(auto x:nums)
num_set.insert(x);
int res=0;
for (auto x:num_set) {
if(!num_set.count(x-1)){
int currNum=x;
int currRes=1;
while(num_set.count(currNum+1)){
currNum++;
currRes++;
}
res=max(res,currRes);
}
}
return res;
}
};
并查集
思路:
- 初始化的时候先把数组里每个元素初始化为他的下一个数;
- 并的时候找他能到达的最远的数字就可以了。
- 太强了
class Solution {
public:
unordered_map<int,int> a;
int find(int x){
return a.count(x)?a[x]=find(a[x]):x;
}
int longestConsecutive(vector<int>& nums) {
for(auto i:nums)
a[i]=i+1;
int ans=0;
for(auto i:nums){
int y=find(i+1);
ans=max(ans,y-i);
}
return ans;
}
};
面试题46. 把数字翻译成字符串
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
输入: 12258 输出: 5 解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
动态规划
class Solution {
public:
int translateNum(int num) {
string strNum=to_string(num);
vector<int> dp(strNum.size()+1,0);
dp[0]=1;
dp[1]=1;
for (int i = 2; i < dp.size(); ++i) {
dp[i]+=dp[i-1];
if(strNum[i-2]!='1' && strNum[i-2]!='2')
continue;
if(strNum[i-2]=='2' && strNum[i-1]>'5')
continue;
dp[i]+=dp[i-2];
}
return dp[strNum.size()];
}
};
滑窗
739. 每日温度
根据每日 气温 列表,请重新生成一个列表,对应位置的输出是需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。
单调栈
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& T) {
int n=T.size();
vector<int> res(n);
stack<int> stack;
for (int i = 0; i < n; ++i) {
while(!stack.empty() && T[i]>T[stack.top()]){
auto top=stack.top();
res[top]=i-top;
stack.pop();
}
stack.push(i);
}
return res;
}
};
1300. 转变数组后最接近目标值的数组和
给你一个整数数组 arr 和一个目标值 target ,请你返回一个整数 value ,使得将数组中所有大于 value 的值变成 value 后,数组的和最接近 target (最接近表示两者之差的绝对值最小)。
如果有多种使得和最接近 target 的方案,请你返回这些整数中的最小值。
请注意,答案不一定是 arr 中的数字。
输入:arr = [4,9,3], target = 10
输出:3
解释:当选择 value 为 3 时,数组会变成 [3, 3, 3],和为 9 ,这是最接近 target 的方案。
暴力解决
思路:先排序,接着就是遍历,维系一个sumLeft 是之前已经判断过的数,sum是sumLeft+以当前数为value 的一个结果,若是sum>=target则,这是一个可行解,且由于经过了排序,这就是所求解
接着计算这个value的确切值
class Solution {
public:
int findBestValue(vector<int>& arr, int target) {
sort(arr.begin(), arr.end());
int n = arr.size();
int sumLeft = 0;
for(int i = 0; i < n; i ++){
int sum = sumLeft + arr[i] * (n - i);
if(sum >= target){
double ans = (double)((target - sumLeft)/(n - i));
if(ans-sum<=0.5)
return ans;
return ans + 1;
}
sumLeft += arr[i];
}
return 0;
}
};
14. 最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串
""
。输入: ["flower","flow","flight"] 输出: "fl"
暴力
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
string res;
if(!strs.size())
return res;
else if(strs.size()==1)
return strs[0];
int n=strs.size();
int i=0;
while(1){
char temp=strs[0][i];
for (int j = 1; j < n; ++j) {
if(i>=strs[i].size())
return res;
if(strs[j][i]!=temp)
return res;
}
res+=temp;
i++;
}
}
};
暴力优化
排序完就比较头和尾就好了
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if(strs.empty())
return string();
sort(strs.begin(),strs.end());
string begin=strs.front(),end=strs.back();
int i,num=min(begin.size(),end.size());
for (i = 0; i <num && begin[i]==end[i] ; ++i) ;
return string(begin,0,i);
}
};
水平扫描
先将第一个元素作为最长前缀,然后不断地将这个最长前缀和后面地元素匹配(缩短它),最后将结果输出
若最长前缀再某一次为空,则直接返回空就好了
class Solution {
public:
string longestCommonPrefixInTwo(string s1,string s2){
int i;
for (i = 0; i < min(s1.size(), s2.size()) && s1[i] == s2[i]; ++i);
return s1.size()>s2.size()?string(s2,0,i):string(s1,0,i);
}
string longestCommonPrefix(vector<string>& strs) {
if(strs.empty())
return string();
string res=strs[0];
for (int i = 1; i < strs.size(); ++i) {
res=longestCommonPrefixInTwo(res,strs[i]);
if(res.size()==0)
return res;
}
return res;
}
};
分治法
class Solution {
public:
string longestCommonPrefixInTwo(string s1,string s2){
int i;
for (i = 0; i < min(s1.size(), s2.size()) && s1[i] == s2[i]; ++i);
return s1.size()>s2.size()?string(s2,0,i):string(s1,0,i);
}
string divide(vector<string>& strs,int start,int end){
if(start==end)
return strs[start];
int mid=(start+end)/2;
string left=divide(strs,start,mid);
string right=divide(strs,mid+1,end);
return longestCommonPrefixInTwo(left,right);
}
string longestCommonPrefix(vector<string>& strs) {
if(strs.empty())
return string();
else
return divide(strs,0,strs.size()-1);
}
};
二分法
思路:先找到元素集合地最短长度,因为这个长度就是前缀最理想状况下的长度
因为最长前缀是公共的 所以就直接拿第一个元素来用
不断地看第一个元素的左区间是不是符合要求,是的话就去右区间找找,
不符合要求的话就是抛弃右区间
class Solution {
public:
bool isCommonPrefix(vector<string>& strs,int length){
string target=strs[0].substr(0,length);
for (int i = 1; i < strs.size(); ++i) {
string cur=strs[i];
for (int j = 0; j < length; ++j) {
if(target[j]!=cur[j])
return false;
}
}
return true;
}
string longestCommonPrefix(vector<string>& strs) {
if(strs.empty())
return string();
int left=0;
int right=INT_MAX;
for (int i = 0; i < strs.size(); ++i) {
if(strs[i].size()<right)
right=strs[i].size();
}
while(left<right){
int mid=(right-left+1)/2+left;
if(isCommonPrefix(strs,mid)) //mid之前是前缀,去mid后面找
left=mid;
else //mid之前不是前缀,在mid区间里面找
right=mid-1;
}
return strs[0].substr(0,left);
}
};
125. 验证回文串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
**说明:**本题中,我们将空字符串定义为有效的回文串。
输入: "A man, a plan, a canal: Panama" 输出: true
class Solution {
public:
bool isPalindrome(string s) {
for (int i = 0; i < s.size(); ++i) {
s[i]=tolower(s[i]);
}
int i=0;
int j=s.size()-1;
while(i<j){
while(s[i]==' ' || s[i]<'a' || s[i]>'z')
i++;
while(s[j]==' ' || s[j]<'a' || s[j]>'z')
j--;
if(s[i]!=s[j])
return false;
i++;
j--;
}
return true;
}
};
帅气双指针
class Solution {
public:
bool isPalindrome(string s) {
string str;
for (int i = 0; i < s.size(); ++i) {
if(isalpha(s[i]))
str.push_back(tolower(s[i]));
}
int left=0;
int right=str.size()-1;
while(left<right){
if(str[left]!=str[right])
return false;
left++;
right--;
}
return true;
}
};
帅气+1
class Solution {
public:
bool isPalindrome(string s) {
string str;
for (int i = 0; i < s.size(); ++i) {
if(isalpha(s[i]))
str.push_back(tolower(s[i]));
}
string str_re(str.rbegin(),str.rend());
return str_re==str;
}
};
309. 最佳买卖股票时机含冷冻期
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。输入: [1,2,3,0,2] 输出: 3 解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
动态规划
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.empty())
return 0;
int n=prices.size();
int dp[n][3];
dp[0][0]=0;//不持股
dp[0][1]=-prices[0];//持股
dp[0][2]=0;//冰冻期
for (int i = 1; i < n; ++i) {
dp[i][0]=max(dp[i-1][0],dp[i-1][2]); //第i天不持股可以从两种状态转移过来,第i-1天不持股或者是冰冻期(如果是持股的话应该是冰冻期而不是不持股地状态)
dp[i][1]=max(dp[i-1][0]-prices[i],dp[i-1][1]);//第i天持股可以从两种状态转移过来,第i-1天不持股,但是今天买入;第i-1天处于冰冻期,但是今天买入;
dp[i][2]=dp[i-1][1]+prices[i];//第i天冰冻期,只有一种情况就是i-1天持股,第i天卖出
}
return max(dp[n-1][0],dp[n-1][2]);//倒数第二天不持股或者处于冰冻期才能取最大值
}
};
空间优化
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.empty())
return 0;
int n=prices.size();
int a=0,b=-prices[0],c=0;
int aa,bb,cc;
for (int i = 1; i <n ; ++i) {
//a是不持股票
//b是持有股票
//c是冰冻期
aa=max(a,c);
bb=max(a-prices[i],b);
cc=b+prices[i];
a=aa;
b=bb;
c=cc;
}
return max(a,c);
}
};
350. 两个数组的交集 II
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
哈希表
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
vector<int> res;
if(nums1.empty() || nums2.empty())
return res;
unordered_map<int,int> map;
for(int i=0;i<nums1.size();i++){
if(map[nums1[i]]){
map[nums1[i]]++;
}else{
map[nums1[i]]=1;
}
}
for(int i=0;i<nums2.size();i++){
if(map[nums2[i]] || map[nums2[i]]>0){
map[nums2[i]]--;
res.push_back(nums2[i]);
}
}
return res;
}
};
排序
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
vector<int> res;
if(nums1.empty() || nums2.empty())
return res;
sort(nums1.begin(),nums1.end());
sort(nums2.begin(),nums2.end());
int i=0,j=0;
while(i<nums1.size() && j<nums2.size()){
if(nums1[i]==nums2[j]){
res.push_back(nums1[i]);
i++;
j++;
}else if(nums1[i]<nums2[j]){
i++;
}else{
j++;
}
}
return res;
}
};
120. 三角形最小路径和
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。
例如,给定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
动态规划
/*
* dp[i][j]表示到(i,j)的最小路径和
* */
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 j = 1; j <i ; ++j) {
dp[i][j]=min(dp[i-1][j-1],dp[i-1][j])+triangle[i][j];
}
dp[i][i]=dp[i - 1][i-1] + triangle[i][i];
}
return *min_element(dp[n-1].begin(),dp[n-1].end());
}
};
动态规划 降秩
思路上和大大背包9讲的降秩一样
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int n=triangle.size();
vector<int> dp(n);
dp[0]=triangle[0][0];
for (int i = 1; i <n ; ++i) {
dp[i]=dp[i - 1] + triangle[i][i];
for (int j = i-1; j >0 ; --j) {
dp[j]=min(dp[j-1],dp[j])+triangle[i][j];
}
dp[0]+= triangle[i][0];
}
return *min_element(dp.begin(),dp.end());
}
};
785. 判断二分图
给定一个无向图graph,当这个图为二分图时返回true。
如果我们能将一个图的节点集合分割成两个独立的子集A和B,并使图中的每一条边的两个节点一个来自A集合,一个来自B集合,我们就将这个图称为二分图。
graph将会以邻接表方式给出,graph[i]表示图中与节点i相连的所有节点。每个节点都是一个在0到graph.length-1之间的整数。这图中没有自环和平行边: graph[i] 中不存在i,并且graph[i]中没有重复的值。
示例 1:
输入: [[1,3], [0,2], [1,3], [0,2]]
输出: true
解释:
无向图如下:
0----1
| |
| |
3----2
我们可以将节点分成两组: {0, 2} 和 {1, 3}。示例 2:
输入: [[1,2,3], [0,2], [0,1,3], [0,2]]
输出: false
解释:
无向图如下:
0----1
| \ |
| \ |
3----2
我们不能将节点分割成两个独立的子集
记忆化DFS(染色法)
class Solution {
public:
bool dfs(vector<vector<int>>& graph,vector<int>& tag,int index,int color){ //判断第index个节点能否被染为color色(1 0)
if(tag[index]!=-1)
return tag[index]==color; //当前节点已染色 判断染色是否正常
tag[index]=color;
for (auto node:graph[index]) { //为相邻节点染相反色
if(!dfs(graph,tag,node,!color))
return false;
}
return true;
}
bool isBipartite(vector<vector<int>>& graph) {
int n=graph.size();
vector<int> tag(n,-1); //-1表示未染色 染成0 1
for (int i = 0; i < n; ++i) {
if(tag[i]==-1 && !dfs(graph,tag,0,0))
return false;
}
return true;
}
};
BFS
class Solution {
public:
bool isBipartite(vector<vector<int>>& graph) {
int n=graph.size();
vector<int> tag(n,-1); //-1表示未染色 染成0 1
queue<int> q;
for (int i = 0; i < n; ++i) {
if(tag[i]==-1){
q.push(i);
tag[i]=0;
while(!q.empty()){
int node= q.front();
int color=!tag[node];
q.pop();
for(auto x:graph[node]){
if(tag[x]==-1){
q.push(x);
tag[x]=color;
}
else if(tag[x]!=color)
return false;
}
}
}
}
return true;
}
};
35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5
输出: 2
示例 2:输入: [1,3,5,6], 2
输出: 1
示例 3:输入: [1,3,5,6], 7
输出: 4
示例 4:输入: [1,3,5,6], 0
输出: 0
二分查找
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int n=nums.size();
if(n==0)
return 0;
if(nums[n-1]<target)
return n;
int i=0,j=n-1;
while(i<j){
int mid=i+(j-i)/2;
if(nums[mid]==target)
return mid;
else if(nums[mid]<target)
i=mid+1;
else
j=mid;
}
return i;
}
};
64. 最小路径和
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小
帅气动态规划
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m=grid.size();
int n=grid[0].size();
int dp[m][n];
dp[0][0]=grid[0][0];
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(i==0 && j==0)
continue;
else if(i==0)
dp[i][j]=dp[i][j-1]+grid[i][j];
else if(j==0)
dp[i][j]=dp[i-1][j]+grid[i][j];
else
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j];
}
}
return dp[m-1][n-1];
}
};
帅气降秩
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m=grid.size();
int n=grid[0].size();
int dp[n];
dp[0]=grid[0][0];
for(int i=1;i<n;i++){
dp[i]=dp[i-1]+grid[0][i];
}
for(int i=1;i<m;i++){
dp[0]=dp[0]+grid[i][0];
for (int j = 1; j < n; ++j) {
dp[j]=min(dp[j-1],dp[j])+grid[i][j];
}
}
return dp[n-1];
}
};