【LeetCode高频100题-4】冲冲冲(持续更新23.3.12))

101. 对称二叉树

题意

  • 给定一棵二叉树,判断它是否轴对称
  • 轴对称:左孩子 = 右孩子
  • 左孩子 = 右孩子:
    • 左孩子的左孩子 = 右孩子的右孩子;
    • 左孩子的右孩子 = 右孩子的左孩子。

解法1 递归

给定一棵二叉树:

  • root==NULL,则轴对称;否则比较 root->leftroot->right
  • 检查 root->left 是否等于 root->right
    • left==right==NULL,则轴对称;
    • left==NULL || right==NULL,则非轴对称;(注意,由于先判断的第一条,所以这里只要有一个为NULL,则说明非轴对称;且此后的三条的前提都是left和right都不是NULL)
    • 比较 left->leftright->right
    • 比较 left->rightright->left
    • 比较 left->valright->val
/**
 * 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:
    bool check(TreeNode* left,TreeNode* right)
    {
        //左子树?=右子树
        //左子树的左孩子?=右子树的右孩子
        //左子树的右孩子?=右子树的左孩子
        if(left==NULL&&right==NULL) return true;
        if(left==NULL||right==NULL) return false;
        return check(left->left,right->right)&&check(left->right,right->left)&&left->val==right->val;
    }
    bool isSymmetric(TreeNode* root) {
        if(root==NULL) return true;
        return check(root->left,root->right);
    }
};

ATTENTION

  • 树的很多判定都是递归实现的

解法2 迭代(队列)(待实现)


102. 二叉树的层序遍历

题意

  • 获取二叉树的层次遍历
  • 但是要根据层次分别存储每层的节点值

解法1 BFS广度优先搜索(维护相邻两层节点数)

要获取二叉树的层次遍历,最典型的就是基于队列:

  • root 加入到队列中
  • 只要队列非空,每次从队列中获取一个节点(front & pop),然后将其左孩子 left 和右孩子 right 都加入到队列中(如果孩子存在的话)。

为了按层获取节点,额外维护 pre_level_cntnext_level_cnt 两个变量,分别记录上一层的节点数和下一层的节点数。

  • 每一个节点从队列中 pop 出时,上一层的节点数 pre_level_cnt 就减 1 ;
  • 每当一个孩子被 进队列时,下一层的节点数 cur_level_cnt 就加 1。
/**
 * 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<vector<int>> levelOrder(TreeNode* root) {
       int pre_level_cnt = 0;
       vector<vector<int> > ans;
       queue<TreeNode*> q;
       
       vector<int> cur_level;
       if(root)
       {
            q.push(root);
            pre_level_cnt=1;
       }
       int next_level_cnt = 0;
       while(!q.empty())
       {
           if(pre_level_cnt==0)
           {
               pre_level_cnt=next_level_cnt;
               next_level_cnt=0;
               ans.push_back(cur_level);
               cur_level.clear();
           }
           TreeNode* cur = q.front();
           q.pop();
           cur_level.push_back(cur->val);
           pre_level_cnt--;
           if(cur->left)
           {
               next_level_cnt+=1;
               q.push(cur->left);
           } 
           if(cur->right)
           {
               next_level_cnt+=1;
               q.push(cur->right);
           } 
       }
       if(cur_level.size()!=0)
            ans.push_back(cur_level);
       return ans;
    }
};

解法2 BFS 广度优先搜索

修改后的广度优先搜索:

  • root 入队
  • 当队列不为空时:
    • 求当前队列的长度si
    • 依次从队列中取出si个元素进行拓展,然后进行下一次迭代

注意代码中的 for 循环,虽然还是 pop 一个父节点,push 两个孩子,但是,当一轮循环结束时,上一层的节点已经全部被 pop 出了,而下一层的节点此时全在队列中,因此,队列的长度就是当前层的节点个数。

//作者:LeetCode-Solution
//链接:https://leetcode.cn/problems/binary-tree-level-order-traversal/solution/er-cha-shu-de-ceng-xu-bian-li-by-leetcode-solution/
//来源:力扣(LeetCode)
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector <vector <int>> ret;
        if (!root) {
            return ret;
        }

        queue <TreeNode*> q;
        q.push(root);
        while (!q.empty()) {
            int currentLevelSize = q.size();
            ret.push_back(vector <int> ());
            for (int i = 1; i <= currentLevelSize; ++i) {
                auto node = q.front(); q.pop();
                ret.back().push_back(node->val);
                if (node->left) q.push(node->left);
                if (node->right) q.push(node->right);
            }
        }
        
        return ret;
    }
};

// 每层单独建立一个vector变量存储
/**
 * 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<vector<int>> levelOrder(TreeNode* root) {
       vector< vector<int>> ret;
       if(!root) return ret;
       queue<TreeNode*> q;
       q.push(root);
       while(!q.empty())
       {
           int cur_level_cnt=q.size();
           vector<int> cur_level;
           for(int i=1;i<=cur_level_cnt;i++)
           {
               TreeNode* cur=q.front();
               q.pop();
               cur_level.push_back(cur->val);
               if(cur->left) q.push(cur->left);
               if(cur->right) q.push(cur->right);
           }
           ret.push_back(cur_level);
       }
       return ret;
    }
};

104. 二叉树的最大深度

题意

  • 计算二叉树的最大深度

解法1 DFS深度优先搜索(递归实现)

root 节点的最大深度 = max(左孩子的最大深度,右孩子的最大深度) + 1

/**
 * 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:
    int maxDepth(TreeNode* root) {
        if(!root) return 0;
        return max(maxDepth(root->left),maxDepth(root->right))+1;
    }
};

解法2 BFS广度优先搜索(队列实现)

二叉树的最大深度 即 二叉树的层数。基于层次遍历,获取层数。

/**
 * 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:
    int maxDepth(TreeNode* root) {
        queue<TreeNode*> q;
        if(!root) return 0;
        q.push(root);
        int ans=0;
        while(!q.empty())
        {
            int cur_level_cnt=q.size();
            for(int i=1;i<=cur_level_cnt;i++)
            {
                TreeNode* cur=q.front();
                q.pop();
                if(cur->left) q.push(cur->left);
                if(cur->right) q.push(cur->right);
            }
            ans++;
        }
        return ans;
    }
};

105. 从前序与中序遍历序列构造二叉树

题意

  • 根据前序遍历和中序遍历构造二叉树
  • 前序遍历:根左右,[ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]
  • 中序遍历:左根右,[ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]

解法1 自己写的(逻辑不够清晰)

主要想法没有问题:

  • 维护以每个节点为根的 preorderinorder
  • 首先得到 root->valpreorder 的第一个值)
  • 然后在 inorder 中寻找 root->val,根据 root_idx 可以判断该节点有没有左孩子和右孩子
  • 如果有左孩子/右孩子,分别维护孩子的 preorderinorder,继续递归。
/**
 * 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* subTree(vector<int>& preorder,vector<int>& inorder,int p_left,int p_right,int i_left,int i_right)
    {
        int root_val=preorder[p_left];
        TreeNode* root=new TreeNode(root_val,NULL,NULL);
        //找 inorder 中的根 root_idx
        int root_idx=-1;
        for(int i=i_left;i<=i_right;i++)
            if(inorder[i]==root_val)
            {
                root_idx=i;
                break;
            }
        int left_num=root_idx-i_left;
        // 要判断有没有左右子树
        if(root_idx==i_left)    // 没有左子树
            root->left=NULL;
        else
            root->left=subTree(preorder,inorder,p_left+1,p_left+left_num,i_left,root_idx-1);
        if(root_idx==i_right) 	// 没有右子树
            root->right=NULL;
        else
            root->right=subTree(preorder,inorder,p_left+left_num+1,p_right,root_idx+1,i_right);
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        return subTree(preorder,inorder,0,preorder.size()-1,0,inorder.size()-1);
    }
};

解法2 官方解答

对于每一棵子树,首先判断子树是否存在,如果存在,再进行构造,否则直接返回 NULL

//作者:LeetCode-Solution
//链接:https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/solution/cong-qian-xu-yu-zhong-xu-bian-li-xu-lie-gou-zao-9/
//来源:力扣(LeetCode)
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
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) {
            return nullptr;
        }
        
        // 前序遍历中的第一个节点就是根节点
        int preorder_root = preorder_left;
        // 在中序遍历中定位根节点
        int inorder_root = index[preorder[preorder_root]];
        
        // 先把根节点建立出来
        TreeNode* root = new TreeNode(preorder[preorder_root]);
        // 得到左子树中的节点数目
        int size_left_subtree = inorder_root - inorder_left;
        // 递归地构造左子树,并连接到根节点
        // 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
        root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
        // 递归地构造右子树,并连接到根节点
        // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
        root->right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 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);
    }
};
/**
 * 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* subTree(vector<int>& preorder,vector<int>& inorder,int p_left,int p_right,int i_left,int i_right)
    {
        //判断该子树有没有节点
        if(p_left>p_right) return NULL;
        int root_val=preorder[p_left];
        TreeNode* root=new TreeNode(root_val,NULL,NULL);
        
        //找 inorder 中的根 root_idx
        int root_idx=-1;
        for(int i=i_left;i<=i_right;i++)
            if(inorder[i]==root_val)
            {
                root_idx=i;
                break;
            }
        int left_num=root_idx-i_left;
        root->left=subTree(preorder,inorder,p_left+1,p_left+left_num,i_left,root_idx-1);
        root->right=subTree(preorder,inorder,p_left+left_num+1,p_right,root_idx+1,i_right);
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        return subTree(preorder,inorder,0,preorder.size()-1,0,inorder.size()-1);
    }
};

114. 二叉树展开为链表

题解

解法1 根据前序遍历存储各节点,然后修改各节点指向

首先通过一次前序遍历 preOrder(root,ans) 将各节点存储在vector<TreeNode*> ans中,然后ans修改每个节点的左右孩子。
ATTENTION

  • 若树非空,则 ans[0]root指向同意内存空间。
  • 必须在指针 p 指向 root 后,再赋值q=p才能使指针q指向树。如果一开始赋值q=p=NULL,再另p指向rootq并不会随着p的指向修改而修改,所以q仍旧指向NULL
/**
 * 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 preOrder(TreeNode* root,vector<TreeNode*>& ans)
    {
        if(!root)
        {
            return;
        }
        ans.push_back(root);
        preOrder(root->left,ans);
        preOrder(root->right,ans);
    }
    void flatten(TreeNode* root) {
        if(!root) return ;

        vector<TreeNode*> ans;
        preOrder(root,ans);
        // cout<<root<<endl;
        // cout<<ans[0]<<endl; 
        TreeNode* p=root; // root 和 ans[0] 指向的是同一个内存单元 (cout 后证实)
        TreeNode* q=p;
        for(int i=1;i<ans.size();i++)
        {

            p->left=NULL;
            p->right=ans[i];
            p=p->right;
        }
    }
};

解法2 (另外建树,修改root,但是输出发现 root 不能被修改)

同样地,首先根据前序遍历 preOrder(root,ans) 将各节点存储在vector<TreeNode*> ans中,然后根据 ans 重新建树。但是 debug 发现新树建立正确,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 preOrder(TreeNode* root,vector<TreeNode*>& ans)
    {
        if(!root)
        {
            return;
        }
        ans.push_back(root);
        preOrder(root->left,ans);
        preOrder(root->right,ans);
    }
    // void preOrderInput(TreeNode* root)
    // {
    //     if(!root)
    //     {
    //         cout<<"NULL,";
    //         return;
    //     }
    //     cout<<root->val<<",";
    //     preOrderInput(root->left);
    //     preOrderInput(root->right);
    // }
    void flatten(TreeNode* root) {
        if(!root) return ;

        vector<TreeNode*> ans;
        preOrder(root,ans);
        TreeNode* p=new TreeNode(root->val,NULL,NULL);
        TreeNode* q=p;
        for(int i=1;i<ans.size();i++)
        {
            p->right=new TreeNode(ans[i]->val,NULL,NULL);
            // cout<<p->val<<" "<<p->right->val<<endl;
            p=p->right;

        }
        // cout<<root<<endl;
        root=q;
        // cout<<root<<endl;
        // preOrderInput(q);
    }
};
// input
[1,2,5,3,4,null,6]
// expected output
[1,null,2,null,3,null,4,null,5,null,6]
// output
[1,2,5,3,4,null,6]
// stdout
1 2
2 3
3 4
4 5
5 6
0x603000000040
0x603000000190
1,NULL,2,NULL,3,NULL,4,NULL,5,NULL,6,NULL,NULL,

121. 买卖股票的最佳时机

题意

  • 选择一天买入,并选择在未来的某一天卖出
  • 求最大利润
  • 如果不能获取利润,则返回 0

解法1 动态规划

维护 dp[i],表示在前 i 天买入的最低价格,所以有dp[i]=min(dp[i-1],prices[i])。初始状态:dp[0]=0(表示在第 0 天以前买入的最低价为 0 )。

假设你想在第 i 天卖出这只股票,那么只有在前 i-1 天的最低价时买入这只股票,利润才是最大的,所以有ans=max(ans,prices[i]-dp[i-1])

首先判断临界条件,即,当prices.size()==1时,此时定不能获取利润,所以直接return 0

然后从第 1 天开始,更新维护ansdp[i]

最后直接retrurn ans即可。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n=prices.size();
        if(n==1) return 0;
        vector<int> dp(n);
        dp[0]=prices[0];
        int ans=0;
        for(int i=1;i<n;i++)
        {
            ans=max(ans,prices[i]-dp[i-1]);
            dp[i]=min(dp[i-1],prices[i]);
        }
        ans=max(ans,prices[n-1]-dp[n-2]);
        return ans;
    }
};

124. 二叉树中的最大路径和

题意

  • 在树中寻找一条权重最大的路径。
  • 路径可以从树的任意一个节点开始,即,路径不一定从根节点开始。
  • 路径至少包含一个节点。
  • 一个节点不可被重复经过。

解法1 递归

一个路径上的节点 root ,要么是起点,要么是中间点:

  • 如果是起点,那么这条路径应该通向以 root 为根的子树;
  • 如果是中间点,那么应该是 root->left , root , root->right

考虑函数maxGin(TreeNode* root),计算每个节点的两种情况:

  • root 是起点,那么这条路径上的下一个点应该是 root 的左孩子和右孩子中 maxGain 更大的那个点,所以有 return root->val+max(left_gain,right_gain)
  • 若 root 是中间点,那么维护 ans=max(ans,left_gain+root->val_right_gain)

为了能够在计算每个节点的 gain 时直接获得路径的最大权重,不用再次遍历树,维护一个全局变量 ans ,初始值为 INT_MIN,并且在遍历到每个节点的时候,直接更新。

/**
 * 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 {
private:
    int ans=-1e9;
public:
    int maxGain(TreeNode* root)
    {
        if(!root) return 0;
        int l_gain=max(0,maxGain(root->left));
        int r_gain=max(0,maxGain(root->right));
        ans=max(ans,l_gain+root->val+r_gain);
        return root->val+max(l_gain,r_gain);
    }
    int maxPathSum(TreeNode* root) {
        maxGain(root);
        return ans;
    }
};

128. 最长连续序列

题意

  • 给一个未排序的整数数组,返回一个数字连续的最长序列的长度
  • 子序列在原数组中可以不连续
  • 可以给数组排序!
  • 要去重!因为在原数组中可以不连续!

解法1 sort + 队列

先给 nums 数组排序,然后去重!最后利用一个queue,获得连续的最长序列的长度。

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        int n=nums.size();
        int ans=1;
        if(n==0) return 0;
        if(n==1) return ans;
        sort(nums.begin(),nums.end());
        // 要去重!
        nums.erase(unique(nums.begin(),nums.end()),nums.end());
        queue<int>q;
        q.push(nums[0]);
        for(int i=1;i<n;i++)
        {
            int tmp=q.back();
            if(nums[i]==tmp+1)
            {
                q.push(nums[i]);
            }
            else
            {
                ans=ans<=q.size()?q.size():ans;
                while(!q.empty()) q.pop();
                q.push(nums[i]);
            }
        }
        ans=ans<=q.size()?q.size():ans;
        return ans;
    }
};

ATTENTION

  • queue 常用函数
  • q.front():返回队列的第一个元素
  • q.back():返回队列的最后一个元素
  • q.empty():判断队列是否为空,不会清空队列
  • 清空队列: while(!q.empty()) q.pop();
  • vector 数组去重
  • nums.erase(unique(nums.begin(),nums.end()),nums.end());
  • vec.unique(start, end):从头到尾,判断当前元素是否等于上一个元素,将不重复的元素移到前面来(赋值操作),而不是将重复的元素移动到后面去。

136. 只出现一次的数字

题意

  • 给定一个数组,除了一个数只出现一次,其他的数都会出现两次,找出那个只出现了一次的数。

解法 异或

异或 ^ :(妙哉)

  • a ^ a = 0
  • 0 ^ a = a
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ans=0;
        for(auto num:nums)
        {
            ans^=num;
        }
        return ans;
    }
};

139. 单词拆分

题意

  • dp

解法1

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        int n=s.size();
        vector<bool> dp(n+1,false);
        dp[0]=true;
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<i;j++)
            {
                string tmp=s.substr(j,i-j);
                if(dp[j]&&find(wordDict.begin(),wordDict.end(),tmp)!=wordDict.end())
                {
                    dp[i]=true;
                }
            }
        }
        return dp[n];
    }
};

141. 环形链表

题意

  • 判断链表是否有环

解法1 哈希表

/**
 * 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) {
        map<ListNode*,bool> mp;
        
        while(head!=NULL)
        {
            if(mp[head]==false)
            {
                mp[head]=true;
                head=head->next;
            }
            else
            {
                return true;
            }
        }
        return false;
    }
};

解法2 快慢指针

如果无环,fast 指针一定比 slow 先到达 NULL,又由于 fast 一次走两步,所以只需要判断fast==NULL||fast->next==NULL

/**
 * 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;
        // slow 、 fast 一定有非NULL值
        ListNode* slow=head,*fast=head->next;
        while(slow!=fast)
        {
        	if(fast==NULL||fast->next==NULL) return false;
            //if(slow==NULL||fast==NULL||fast->next==NULL) return false;
            slow=slow->next;
            fast=fast->next->next;
        }
        return true;
    }
};

142. 环形链表 II

题意

  • 无环返回 NULL ,有环返回入环口节点。

解法1 哈希表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        map<ListNode*,int> mp;
        while(head)
        {
            if(mp[head]==1)
            {
                return head;
            }
            else
            {
                mp[head]=1;
                head=head->next;
            }
        }
        return NULL;
    }
};

解法2 快慢指针

slowfast 相遇后,另外创建一个指针 cur ,使其从链表头开始走,与 slow 同速,curslow 会在入环口相遇。

在这里插入图片描述

和上一题不一样的是,slowfast 起点相同

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head==NULL||head->next==NULL) return NULL;
        ListNode* slow=head,*fast=head;
        bool flag=false;
        while(slow!=fast||!flag)
        {
            flag=true;
            if(fast==NULL||fast->next==NULL) return NULL;
            slow=slow->next;
            fast=fast->next->next;
        }
        ListNode* cur=head;
        while(slow!=cur)
        {
            slow=slow->next;
            cur=cur->next;
        }
        return cur;
        // return NULL;
    }
};

239. 滑动窗口最大值

题意

  • 返回滑动窗口的最大值

解法1 优先队列

利用优先队列求得滑动窗口的最大值,注意在得到最大值时,需要 判断是否在滑动窗口内,若不在滑动窗口内,需要 pop()

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n = nums.size();
        priority_queue<pair<int, int> > q;
        vector<int> ans;

        for(int i = 0; i < n; i++)
        {
            q.emplace(nums[i], i); 	// 每次将值 push 到优先队列中,利用优先队列获取最大值
            if(i >= k - 1) 	// 该最大值是否在滑动窗口范围内,不在则 pop(),直到找到在滑动窗口范围内的最大值
            {
                while(!q.empty() && q.top().second < i - k + 1)
                {
                    q.pop();
                }
                ans.emplace_back(nums[q.top().second]); 	// 保存每个滑动窗口的最大值
            }
        }
        return ans;
    }
};

复杂度

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn),最坏的情况是 nums[] 本身就是一个递增序列,每 push 一个值的时间复杂度为 O ( l o g n ) O(logn) O(logn)
空间复杂度: O ( n ) O(n) O(n),最坏的情况是 nums[] 本身就是一个递增序列,那么优先队列永远不会有值被 pop()

解法2 单调队列

对于当前滑动窗口中的两个数 nums[i]nums[j] 来说,假设 nums[i] < nums[j] && i < j,那么,在滑动窗口 右移 时,最大值绝不可能是 nums[i],因此,nums[i] 可以不保存。也就是说,当遇到一个比当前值大的值的时候,当前值可以弃置。

因此,引入单调递减栈,在滑动窗口右移时,如果当前值 nums[i] >= q.top(),那么 q.pop(),直到nums[i] < q.top()

但是,由于滑动窗口的左界也在更新,单调递减栈不能更新左界,所以引入单调递减队列,每次更新左界,若超出左界范围,则 q.pop_front()

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n = nums.size();
        deque<int> q;
        vector<int> ans;

        for(int i = 0; i < n; i++)
        {
            while(!q.empty() && nums[q.back()] < nums[i]) 	// 当遇到一个比当前值大的值的时候,当前值可以弃置
            {
                q.pop_back();
            }
            q.push_back(i);
            
            if(i >= k - 1)
            {
                while(!q.empty() && q.front() < i - k + 1) 	// 判断左界
                {
                    q.pop_front();
                }
                ans.emplace_back(nums[q.front()]);
            }
        }
        return ans;
    }
};

297. 二叉树的序列化与反序列化

题意

解法1 层次遍历

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Codec {
public:

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        string res = "";
        queue<TreeNode*> q;

        if(root == nullptr) return res;

        res += to_string(root->val) + ",";
        q.push(root);

        while(!q.empty())
        {
            TreeNode* cur = q.front();
            q.pop();

            if(cur->left != nullptr)
            {
                q.push(cur->left);
                res += to_string(cur->left->val) + ",";
            }
            else
            {
                res += "null,";
            }
            if(cur->right != nullptr)
            {
                q.push(cur->right);
                res += to_string(cur->right->val) + ",";
            }
            else
            {
                res += "null,";
            }
            
        }
        res.pop_back();
        return res;
    }
    vector<string> split(string s)
    {
        vector<string> res;     // 此时 res.size() = 0
        s += ",";
        int idx = 0, pre = 0;

        while(idx < s.size())
        {
            if(s[idx] == ',')
            {
                string tmp = s.substr(pre, idx - pre);
                res.emplace_back(tmp);
                pre = idx + 1;
            }
            idx++;
        }
        // for(auto s : res)
        //     cout<<s;
        // cout<<endl;
        return res;
    }
    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        vector<string> val = split(data);
        for(auto s : val)
            cout<<s<<"*";
        // cout<<endl;
        if(val.size() == 0 || val[0] == "") return nullptr;
        cout<<val[0].c_str()<<"&"<<endl;
        TreeNode* root = new TreeNode(atoi(val[0].c_str()));
        TreeNode* cur = root;

        int curLayer = 0, preLayer = 1;
        queue<TreeNode*> q;
        q.push(cur);

        int idx = 1;
        while(idx < val.size())
        {
            for(int i = 0; i < preLayer; i++)
            {
                TreeNode* tmp = q.front();
                q.pop();
                if(val[idx] != "null")
                {
                    tmp->left = new TreeNode(atoi(val[idx].c_str()));
                    q.push(tmp->left);
                    curLayer++;
                }
                if(val[idx + 1] != "null")
                {
                    tmp->right = new TreeNode(atoi(val[idx + 1].c_str()));
                    q.push(tmp->right);
                    curLayer++;
                }
                idx+=2;
            }
            preLayer = curLayer;
            curLayer = 0;
        }
        return root;
    }
};

// Your Codec object will be instantiated and called as such:
// Codec ser, deser;
// TreeNode* ans = deser.deserialize(ser.serialize(root));

309. 买卖股票的最佳时机含冷冻期

题意

  • 股票可以多次买入和卖出
  • 计算可以获得的最大价值
  • 股票在卖出后有一天冷冻期

解法 动态规划

dp[i] 表示第 i 天操作(买入/卖出/不操作)后,可以获得的最大利益。一共有三种状态,其中:

  • dp[i][0]:在结束第 i 天的操作后(买入/卖出/不操作),空仓,且非冷冻期
    • i - 1 天就是空仓,且非冷冻期,第 i 天不操作
    • i - 1 天是冷冻期,第 i 天不操作
    • 因此,dp[i][0] = max(dp[i - 1][0], dp[i - 1][2])
  • dp[i][1]:在结束第 i 天的操作(买入/卖出/不操作)后,持股
    • i - 1 天是空仓,且非冷冻期,第 i 天买入
    • i - 1 天就持股,第 i 天不操作
    • 因此,dp[i][1] = max(dp[i - 1][0] - prices[i], dp[i - 1][1])
  • dp[i][2]:在结束第 i 天的操作(买入/卖出/不操作)后,空仓,且冷冻期
    • i - 1 天持股,第 i 天卖出
    • 因此,dp[i][2] = dp[i - 1][1] + prices[i]

初始状态:

  • dp[0][0] = 0
  • dp[0][1] = -prices[0],第 0 天操作后要持股,只能进行买入操作,因此此时最大收益为 -prices[0]
  • dp[0][2] = 0
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        vector<vector<int> > dp(n, vector<int>(3, 0));

        // dp[i]:表示第 i 天结束后可得的最大收益,0 <= i < n
        // dp[i][0]:操作后(买入/卖出/不操作),空仓,非冷冻期
        // dp[i][1]:操作后(买入/卖出/不操作),持股
        // dp[i][2]:操作后(买入/卖出/不操作),空仓,冷冻期

        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]);
            dp[i][1] = max(dp[i - 1][0] - prices[i], dp[i - 1][1]);
            dp[i][2] = dp[i - 1][1] + prices[i];
        }

        return max(dp[n - 1][0], max(dp[n - 1][1], dp[n - 1][2]));
    }
};

复杂度

时间复杂度: O ( n ) O(n) O(n),遍历一遍。
空间复杂度: O ( n ) O(n) O(n)dp[][] 数组的大小。


复杂度

时间复杂度: O ( n ) O(n) O(n)出队入队的时间复杂度都是 O ( 1 ) O(1) O(1)
空间复杂度: O ( k ) O(k) O(k),单调队列永远只有 k k k 大小。


312. 戳气球

题意

  • 戳破第 i i i 个气球,获得 n u m s [ i − 1 ] ∗ n u m s [ i ] ∗ n u m s [ i + 1 ] nums[i - 1] * nums[i] * nums[i + 1] nums[i1]nums[i]nums[i+1] 个硬币;
  • 求可以获取的硬币的最大数量

解法 动态规划(参考 labuladong 解答)

要求最值,肯定得穷举所有情况,而要穷举所有情况,要么 i). 回溯;ii). 动态规划。

若选择回溯。要穷举所有获取硬币的情况,实际上就相当于穷举所有戳破气球的方案,即,全排列问题,因此:

int res = Integer.MIN_VALUE;
/* 输入一组气球,返回戳破它们获得的最大分数 */
int maxCoins(int[] nums) {
    backtrack(nums, 0); 
    return res;
}
/* 回溯算法的伪码解法 */
void backtrack(int[] nums, int socre) {
    if (nums 为空) {
        res = max(res, score);
        return;
    }
    for (int i = 0; i < nums.length; i++) {
        int point = nums[i-1] * nums[i] * nums[i+1];
        int temp = nums[i];
        // 做选择
        在 nums 中删除元素 nums[i]
        // 递归回溯
        backtrack(nums, score + point);
        // 撤销选择
        将 temp 还原到 nums[i]
    }
}

作者:labuladong
链接:https://leetcode.cn/problems/burst-balloons/solutions/247603/dong-tai-gui-hua-tao-lu-jie-jue-chuo-qi-qiu-wen-ti/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

但是显然这道题并不能用这种方法,因为这样的复杂度为 n ! n! n!,即便 1 < = n < = 300 1 <= n <= 300 1<=n<=300,也会超时。

因此这道题选择动态规划做。

将问题转换为:在一排气球中,戳破 0 0 0 n + 1 n+1 n+1 之间所有的气球,计算能够获取的硬币的最大数量。

dp[i][j] 表示戳破 ij 之间所有的气球,可以获取的硬币的最大数量,则要求取的就是 dp[0][n+1]

状态转移方程为:
最后一个戳破的气球k,那么:
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + myNums[i] * myNums[k] * myNums[j]);

为什么是 myNums[i] * myNums[k] * myNums[j] 呢,因为此时 dp[i][k]dp[k][j] 都被戳破了,所以最后只剩下气球 i 、气球 k 和气球 j,因此自然是 myNums[i] * myNums[k] * myNums[j]

在这里插入图片描述

而由于在计算 dp[i][j] 之前需要计算 dp[i][k]dp[k][j],所以要自下而上,自左而右遍历:
在这里插入图片描述

class Solution {
public:
    int maxCoins(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int> > dp(n+2, vector<int>(n+2, 0));
        vector<int> myNums(n+2);

        myNums[0] = myNums[n+1] = 1;
        for(int i = 1; i <= n; i++)
            myNums[i] = nums[i-1];

        for(int i = n + 1; i >= 0; i--)
        {
            for(int j = i + 1; j < n + 2; j++)
            {
                for(int k = i + 1; k < j; k++)
                {
                    dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + myNums[i] * myNums[k] * myNums[j]);
                }
            }
        }
        return dp[0][n+1];
    }
};

复杂度:

时间复杂度:O( n 3 n^3 n3)
空间复杂度:O( n 2 n^2 n2)


322. 零钱兑换

题意

  • 给定不同面额的硬币 coins[],以及一个整数 amount
  • 计算可以凑成 amount 的最少硬币数;
  • 硬币可以多次使用;
  • 本质上是一个 完全背包问题

解法 dp

dp[i] 表示凑成金额 i 需要的最少硬币数,那么状态转移公式为:

dp[i] = min(dp[i], dp[i - coin] + 1)

也就是说,每次从 coins[] 中选择一个硬币。

class Solution {
public:
    int maxn = 1e9;
    int coinChange(vector<int>& coins, int amount) {
        int n = coins.size();

        vector<int> dp(amount + 1, maxn);

        dp[0] = 0;

        for(auto coin : coins)
        {
            if(coin <= amount)
                dp[coin] = 1;
        }

        for(int i = 1; i <= amount; i++)
        {
            for(auto coin : coins)
            {
                if(i >= coin)
                {
                    dp[i] = min(dp[i], dp[i - coin] + 1);
                }
            }
        }
        return dp[amount] == maxn ? -1 : dp[amount];
    }
};

一开始想的是:
dp[i][j] 表示使用前 i 中面额的硬币,凑成 j 的最少硬币数,状态转移方程为:

dp[i][j] = min(dp[i][j], dp[i-1][j]); 	if j < coins[i]
dp[i][j] = min(dp[i][j], dp[i-1][j], dp[i-1][j-k*coins[i]] + k); 	other,其中 j-k*coins[i]>=0

但是这样的复杂度就是 O(amount * coins.size() * k),就,很容易超时,当然结果果然超时了,惨惨。

// TLE 
class Solution {
public:
    int maxn = 1e9;
    int coinChange(vector<int>& coins, int amount) {
        int n = coins.size();

        vector<vector<int> > dp(n + 1, vector<int>(amount + 1, maxn));

        for(int i = 0; i < n; i++)  dp[i][0] = 0;
        for(int i = 0; i < coins.size(); i++)
        {
            for(int j = 1; j * coins[i] <= amount; j++)
            {
                dp[i][j * coins[i]] = j;
            }
        }
        // 用前i种数额组合,得到j
        for(int i = 1; i < n; i++)
        {
            for(int j = 1; j <= amount; j++)
            {
                if(j < coins[i])
                {
                    dp[i][j] = min(dp[i][j], dp[i-1][j]);
                }
                else
                {
                    for(int k = 1; k * coins[i] <=j; k++) 	// TLE 可恶捏
                    {
                        dp[i][j] = min(dp[i][j], dp[i-1][j]);
                        dp[i][j] = min(dp[i][j], dp[i-1][j-k*coins[i]] + k);
                    }
                }
            }
        }
        return dp[n-1][amount] == maxn ? -1 : dp[n-1][amount];
    }
};

复杂度:

时间复杂度:O(amount * coins.size())
空间复杂度:O(amount)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值