LeetCode每日一题系列 随缘更新

##每日一题##

根据前序遍历和中序遍历构造二叉树

在这里插入图片描述

思路:从性质出发,前序数组的开头就是根节点,然后对应到中序数组,得到左子树和右子树,分别再构造左子树和右子树

下标对应:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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;
        }
    }
};

上一题的前导问题的多种解法(合并两个有序数组)

给你两个有序整数数组 nums1nums2,请你将 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;
    }
};

并查集

思路:

  1. 初始化的时候先把数组里每个元素初始化为他的下一个数;
  2. 并的时候找他能到达的最远的数字就可以了。
  3. 太强了
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];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值