Leetcode刷题总结(二)

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) {
        if(root==NULL)    
            return "[]";
        string data="[";
        queue<TreeNode*> q;
        q.push(root);
        while(q.size()!=0)
        {
            TreeNode* node = q.front();
            q.pop();
            if(node!=NULL)
            {
                data+=to_string(node->val);
                data+=",";
                q.push(node->left);
                q.push(node->right);
            }else{
                data+="null,";
            }  
        } 
        // data.erase(data.length()-1,1);    本来想去掉末尾的“,”,但是有后面的函数,不去也可以
        return data+"]";
    }

    vector<string> split(string& s, string delim)    // 去掉“,”
    {
        vector<string> ret;
        int p = s.find(delim);
        while (p != -1)
        {
            ret.push_back(s.substr(0,p));
            s.erase(0, p+1);
            p = s.find(delim);
        }
        ret.push_back(s);
        return ret;
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        data.erase(0, 1);   // 去掉【
        data.erase(data.length()-1, 1);  // 去掉 】
        if(data.length()==0)    // "[]"的情况
            return NULL;
        vector<string> vec = split(data, ",");   // 此时节点都保存在vec中
        int i=0;
        int value = atoi(vec[i].c_str());
        i++;
        TreeNode* root = new TreeNode(value);
        queue<TreeNode*> q;
        q.push(root);
        while(q.size()!=0)
        {
            TreeNode* node = q.front();
            q.pop();
            string tmp_left = vec[i];
            if(tmp_left=="null")
            {
                node->left=NULL;
            }else{
                int value = atoi(tmp_left.c_str());
                TreeNode* le = new TreeNode(value);
                node->left = le;
                q.push(node->left);
            }
            i++;
            
            string tmp_right = vec[i];
            if(tmp_right=="null")
            {
                node->right=NULL;
            }else{
                int value = atoi(tmp_right.c_str());
                TreeNode* ri = new TreeNode(value);
                node->right = ri;
                q.push(node->right);
            }
            i++;
            
        }
        return root;
    }
};

// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));

2、字符串的排列

在这里插入图片描述
思路:这题考的是全排列,如果直接暴力肯定超时。其实就是设置一个visited数组,用来标志当前字符是否被访问,如果被访问过了那就跳过下一个,逐渐罗列出所有情况。

class Solution {
public:
    void dfs(string s, string tmp, vector<int> visited, set<string>& ans)
    {
        if(tmp.length()==s.length())
        {
            ans.insert(tmp);
            return;
        }
        for(int i=0;i<s.length();i++)
        {
            if(visited[i]==1)
                continue;
            visited[i]=1;
            dfs(s, tmp+s[i], visited, ans);
            visited[i]=0;
        }
    }
    vector<string> permutation(string s) {
        set<string> ans;    // 这里用set是由于有些情况会出现重复,用set去重
        vector<int> visited(s.length(), 0);
        dfs(s, "", visited, ans);
        vector<string> final;
        set<string>::iterator iter;
        iter = ans.begin();
        while(iter!=ans.end())
        {
            final.push_back(*iter);
            iter++;
        }
        return final;
    }
};

还有一种回溯写法,可以不借助set,因为太慢了。

class Solution {
public:
    vector<string> rec;
    vector<int> vis;

    void backtrack(const string& s, int i, int n, string& perm) {
        if (i == n) {
            rec.push_back(perm);
            return;
        }
        for (int j = 0; j < n; j++) {
            if (vis[j] || (j > 0 && !vis[j - 1] && s[j - 1] == s[j])) {     // 这里判断的意思是:1)如果被访问过,那就不要;2)如果这个字符不是第一个重复字符,就不要,意思是每次只拿重复字符中第一个,这样就避免重复
                continue;
            }
            vis[j] = true;
            perm.push_back(s[j]);
            backtrack(s, i + 1, n, perm);
            perm.pop_back();
            vis[j] = false;
        }
    }

    vector<string> permutation(string s) {
        int n = s.size();
        vis.resize(n);
        sort(s.begin(), s.end());    // 先把字符串排序,让重复的字符放在一起
        string perm;
        backtrack(s, 0, n, perm);
        return rec;
    }
};


3、格雷码(不会

在这里插入图片描述
思路:这里就记住格雷码的公式
在这里插入图片描述

class Solution {
public:
    vector<int> grayCode(int n) {
        vector<int> ret(1 << n);   // 初始化2^n长度的vector
        for (int i = 0; i < ret.size(); i++) {
            ret[i] = (i >> 1) ^ i;
        }
        return ret;
    }
};

4、丑数(超时

在这里插入图片描述
思路:根据题目意思,其实可以想到,在已有n个丑数的情况下,要找第n+1个丑数的话:
在这里插入图片描述
但不能直接用循环遍历做,会超时。为了保证每个丑数都有乘2,乘3, 乘5,且保持有序,就要记录这个丑数是否乘过。设置3个索引a, b, c,分别记录前几个数已经被乘2, 乘3, 乘5了,比如a表示前(a-1)个数都已经乘过一次2了,下次应该乘2的是第a个数。
对于某个状态下的丑数序列,我们知道此时第a个数还没有乘2(有没有乘3或者乘5不知道), 第b个数还没有乘3(有没有乘2或者乘5不知道),第c个数还没有乘5(有没有乘2或者乘3不知道), 下一个丑数一定是从第a丑数乘2, 第b个数乘3, 第c个数乘5中获得,他们三者最小的那个就是下个丑数。

class Solution {
public:
    int nthUglyNumber(int n) {
        if(n < 7) return n;
        vector<int> res(n);
        //初始化
        res[0] = 1;
        int a = 0,b = 0,c = 0;    // 用a,b,c分别作为2、3、5因子倍数的索引
        for(int i = 1;i < n;i++)
        {
            // 取dp[a]*2,dp[b]*3,dp[c]*5的最小值
            res[i]=min({res[a]*2,res[b]*3,res[c]*5});
            if(res[i] == res[a] * 2) a++;    // 如果是2的倍数,那么2对应的索引++
            if(res[i] == res[b] * 3) b++;
            if(res[i] == res[c] * 5) c++;
        }
        return res[n - 1];
    }
};

5、 n个骰子的点数(不会

在这里插入图片描述
思路:暴力法肯定超时。对于n个骰子,最小值为n,最大是6n,总共可能出现的和的数量是5n+1种。
假设n-1个骰子,所有和出现概率的情况记为f(n-1),现在要求f(n)。f(n-1,x)表示n-1个骰子投出和为x的概率。
这相当于在n-1个骰子已经求完和后,再扔第n个骰子,此时第n个骰子的值只能是1-6,且概率都是1/6,独立于前n-1个骰子。
现在假设要求f(n,x),那么n个骰子和为x的概率是:
f ( n , x ) = ∑ 1 < = i < = 6 f ( n − 1 , x − i ) ∗ 1 6 f(n, x) = {\sum_{1<=i<=6}}f(n-1,x-i)*{\frac{1}{6}} f(n,x)=1<=i<=6f(n1,xi)61
但在遍历1-6的过程中,x可能小于i,导致出现负数,需要增加判断条件。也可以改为正向思路:
f ( n , x + i ) + = f ( n − 1 , x ) ∗ 1 6 f(n, x+i) += f(n-1,x)*{\frac{1}{6}} f(n,x+i)+=f(n1,x)61
当n个骰子和为x+i时,是在n-1个骰子和为x的基础上再乘1/6。

class Solution {
public:
    vector<double> dicesProbability(int n) {
        vector<double> dp(6, 1.0 / 6.0);
        for (int i = 2; i <= n; i++) {
            vector<double> tmp(5 * i + 1, 0);      // 当i个骰子的时候,总共可能出现的情况数量
            for (int j = 0; j < dp.size(); j++) {
                for (int k = 0; k < 6; k++) {
                    tmp[j + k] += dp[j] / 6.0;
                }
            }
            dp = tmp;
        }
        return dp;
    }
};


6、正则表达式匹配(不会

在这里插入图片描述
思路:
在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size() + 1, n = p.size() + 1;
        vector<vector<bool>> dp(m, vector<bool>(n, false));
        dp[0][0] = true;
        // 初始化首行
        for(int j = 2; j < n; j += 2)
            dp[0][j] = dp[0][j - 2] && p[j - 1] == '*';
        // 状态转移
        for(int i = 1; i < m; i++) {
            for(int j = 1; j < n; j++) {
                if(p[j - 1] == '*') {
                    if(dp[i][j - 2]) dp[i][j] = true;                              // 1.
                    else if(dp[i - 1][j] && s[i - 1] == p[j - 2]) dp[i][j] = true; // 2.
                    else if(dp[i - 1][j] && p[j - 2] == '.') dp[i][j] = true;      // 3.
                } else {     
                    if(dp[i - 1][j - 1] && s[i - 1] == p[j - 1]) dp[i][j] = true;  // 1. 在前i-1和j-1匹配的基础上,若新添加s[i-1]和p[j-1]两者相等,那么dp[i][j]也匹配
                    else if(dp[i - 1][j - 1] && p[j - 1] == '.') dp[i][j] = true;  // 2.   取‘.’代表任意字母,可以匹配
                }
            }
        }
        return dp[m - 1][n - 1];
    }
};

7、数组中的逆序对(不会

在这里插入图片描述
思路:这里是借助了归并排序,在合并的过程中,需要比较前后两个数字大小来判断先放谁,后放谁,这就包含了计算逆序对的过程。

class Solution {
public:
    int reversePairs(vector<int>& nums) {
        vector<int> tmp(nums.size());
        return mergeSort(0, nums.size() - 1, nums, tmp);
    }
private:
    int mergeSort(int l, int r, vector<int>& nums, vector<int>& tmp) {
        // 终止条件
        if (l >= r) return 0;
        // 递归划分
        int m = (l + r) / 2;
        int res = mergeSort(l, m, nums, tmp) + mergeSort(m + 1, r, nums, tmp);
        // 合并阶段
        int i = l, j = m + 1;
        for (int k = l; k <= r; k++)
            tmp[k] = nums[k];
        for (int k = l; k <= r; k++) {
            if (i == m + 1)
                nums[k] = tmp[j++];
            else if (j == r + 1 || tmp[i] <= tmp[j])
                nums[k] = tmp[i++];
            else {
                nums[k] = tmp[j++];
                res += m - i + 1; // 统计逆序对
            }
        }
        return res;
    }
};

8、1-n整数中1 出现的次数(不会

在这里插入图片描述
思路:设n是x位数,可表示为 n x n x − 1 . . . n 2 n 1 n_xn_{x-1}...n_2n_1 nxnx1...n

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\]和引用\[2\]的内容,推荐的LeetCode顺序是按照目类型,优先选择树、链表、二分查找、DFS、BFS、动态规划等常见类型的目。可以先做2~4道简单,然后再做中等难度的目。在选择目时,可以优先选择目序号小、点赞多、提交成功率高的目,这样可以从简单入手,节省时间。同时,LeetCode每道目都有“模拟面试”功能,可以给自己设定时间限制,如果做不出来可以看答案,然后记住思路后再自己尝试一遍。每种类型的目做完10+道后,可以总结规律。 根据引用\[3\]的内容,目可以按照不同的分类进行,比如数组与贪心算法、子数组与贪心算法、子序列与贪心算法、数字与贪心、单调栈法、双指针法等。可以根据自己的兴趣和需求选择相应的目进行。 综上所述,LeetCode顺序可以按照目类型或者目分类进行选择。 #### 引用[.reference_title] - *1* [LeetCode 顺序,按标签分类,科学!](https://blog.csdn.net/fengyuyeguirenenen/article/details/125099023)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [leetcode 指南 & 顺序](https://blog.csdn.net/qijingpei/article/details/125561071)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [leetcode-顺序推荐](https://blog.csdn.net/weixin_38087674/article/details/114107841)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值