LeetCode冲刺:Day5

发布于 2022-11-12  42 次阅读


今天试了下上次的周赛,感觉只能写出两题。

1. 组合

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

说好的用回溯慢呢,为什么只有我这么慢orz。

研究了下,主要问题在暂存的数组上。我每次都建一个新数组暂存结果,浪费了很多性能。而答案只使用一个数组暂存结果,在每次处理后用pop撤销刚才的处理,所以更节省性能。

另外主函数里没必要手动调用第一轮。

最后,我写的时候意识到了有些调用是不必要的,因为剩下的元素个数不足以凑一个elem了,但我的处理不太合适,随想录给出了正确的“剪枝”方法。即 i<=n-(k-size)+1 。优化这一步后,用时超过了98%。

代码

#include <vector>
using std::vector;

vector<vector<int>> res;

class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        res.clear();
        for (int i=1;i<=n-k+1;++i) {
            vector<int> v;
            backtracking(v,i,k,0,n);
        }
        return res;
    }
    void backtracking(vector<int> v,int next,int k,int deep,int n) {
        v.push_back(next);
        ++deep;
        if (deep==k) {
            res.push_back(v);
            return;
        }
        for (int i=next+1;i<=n;++i) {
            backtracking(v,i,k,deep,n);
        }
    }
};

优化后

#include <vector>
using std::vector;
class Solution {
private:
    vector<vector<int>> res;
    vector<int> v;
    void backtracking(int k,int start,int n) {
        if (v.size()==k) {
            res.push_back(v);
            return;
        }
        for (int i=start;i<=n;++i) {
            v.push_back(i);
            backtracking(k,i+1,n);
            v.pop_back();
        }
    }
public:
    vector<vector<int>> combine(int n, int k) {
        res.clear();
        v.clear();
        backtracking(k,1,n);
        return res;
    }
};

2. 组合总和 III

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

时间超过100%,空间超过60%,真是意外惊喜。

总体思路跟组合是一样的,只不过固定了可用的数字范围,并且在将elem加入res集之前加个判断,看看sum是否等于目标n。

剪枝操作还可以优化下,当sum大于目标值的时候就可以return了,再地跪下去没有意义。

代码

#include <vector>
using std::vector;
class Solution {
private:
    vector<vector<int>> res;
    vector<int> v;
    int sum=0;
    void backtracking(int start,int k,int n) {
        if (v.size()==k) {
            if (sum==n) res.push_back(v);
            return;
        }
        for (int i=start;i<=9-(k-v.size())+1;++i) {
            v.push_back(i);
            sum+=i;
            backtracking(i+1,k,n);
            v.pop_back();
            sum-=i;
        }
    }
public:
    vector<vector<int>> combinationSum3(int k, int n) {
        res.clear();
        v.clear();
        backtracking(1,k,n);
        return res;
    }
};

3. 电话号码的字母组合

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

时间超过100%,空间超过53.77%。

终止条件是字符串path长度已经等于数字串长度,那就直接返回。

否则继续到下一层去遍历。

每一层都是从0开始的,才能求出所有组合。

我用一个二维数组存储数字和字符的对应关系。这里要注意转换,字符比数字要大48。比如字符的2,转成int是50,所以要减个48才能用。

回溯函数参数里的start代表的是每个数字所对应字符的顺序,递归深度本身是给定数字串的长度。这点要分清楚,所以每次进循环是从i=0开始,而不是start+1。

随想录用一个index去记录遍历的深度,但其实可以省掉这个量。因为如果当前path长度小于数字串长度,那么当前path长度就是当前要遍历的数字串中数字的下标!比如“23”这个串,我们刚遍历到某个'2'对应的字母,当前path长度1,所以下一步要遍历“23”中下标1的数组所对应的字母!当然,这样写的话代码稍微难懂一点。

代码

#include <vector>
#include <string>
using std::vector;
using std::string;
class Solution {
private:
    const vector<vector<int>> m={{},{},{97,98,99},{100,101,102},{103,104,105},{106,107,108},{109,110,111},{112,113,114,115},{116,117,118},{119,120,121,122}};
    vector<string> res;
    string path;
    void backtracking(const string& digits) {
        if (path.size()==digits.size()) {
            res.push_back(path);
            return;
        }
        for (int i=0;i<m[int(digits[path.size()])-48].size();++i) {
            path.push_back(char(m[int(digits[path.size()])-48][i]));
            backtracking(digits);
            path.pop_back();
        }
    }
public:
    vector<string> letterCombinations(string digits) {
        res.clear();
        path="";
        if (digits=="") return res;
        backtracking(digits);
        return res;
    }
};

4. 最大子数组和

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

之前贪心法做过一次,这次要求用dp做。

核心还是状态转移方程的确定。每次增加一个元素,总是做一个选择:累加还是重置。在累加和重置之间选最大的即可。

代码

#include <vector>
#include <algorithm>

using std::vector;
using std::max;
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        // 贪心算法
        // int res=INT_MIN,count=0;
        // for (int i=0;i<nums.size();++i) {
        //     count+=nums[i];
        //     if (count >res) res=count;
        //     if (count <=0) count =0;
        // }
        // return res;

        // dp
        int res=nums[0];
        vector<int> dp(nums.size(),INT_MIN);
        dp[0]=nums[0];
        for (int i=1;i<nums.size();++i) {
            dp[i]=max(dp[i-1]+nums[i],nums[i]);
            res=max(res,dp[i]);
        }
        return res;

    }
};

5. 判断子序列

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

直接简单模拟,可以在O(n)时间内完成。但本题放在dp类里,可以用dp解。

代码

#include <vector>
#include <string>
using std::vector;
using std::string;
class Solution {
public:
    bool isSubsequence(string s, string t) {
        // 简单模拟
        if (s=="") return true;
        if (s!=""&&t=="") return false;
        int i=0;
        for (int j=0;j<t.size();++j) {
            if (t[j]==s[i]&&i==s.size()-1) return true;
            else if (t[j]==s[i]) ++i; 
        }
        return false;
    }
};

6. 对数组执行操作

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

就硬写。虽然vector的动态扩展能力非常差。

代码

#include <vector>
#include <list>
using std::list;
using std::vector;

class Solution {
public:
    vector<int> applyOperations(vector<int>& nums) {
        for (int i=0;i<nums.size()-1;++i) {
            if (nums[i]==nums[i+1]) {
                nums[i]*=2;
                nums[i+1]=0;
            }
        }
        int count=0;
        for (vector<int>::iterator iter=nums.begin();iter!=nums.end();) {
            if (*iter==0) {
                nums.erase(iter);
                ++count;
            }
            else ++iter;
        }
        for (int i=0;i<count;++i) nums.push_back(0);
        return nums;
    }
};

7. 长度为 K 子数组中的最大和

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

被折磨了……

老想着赶紧开始写代码,结果细节部分总出错。一开始以为是不连续子序列。

还是要先想清楚。

最后的代码性能也不太行。但周赛感觉能做出来就算赢,性能不考虑了。

代码

#include <vector>
#include <map>
#include <algorithm>
using std::vector;
using std::map;
class Solution {
public:
    long long maximumSubarraySum(vector<int>& nums, int k) {
        long long res=0;
        long long temp=0;
        int count=0;
        vector<int> last(100005,-k);
        for (int i=0;i<nums.size();++i) {
            if (last[nums[i]]!=i&&last[nums[i]]>i-k) {
                int newPos=last[nums[i]]+1;
                last[nums[i]]=i;
                i=newPos;
                temp=0;
                count=0;
            } else last[nums[i]]=i;
            temp+=nums[i];
            ++count;
            if (count==k) {
                res=temp>res?temp:res;
                temp-=nums[i-k+1];
                --count;
            }
        }
        return res;
    }
};

8. 不同的子序列

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

我自己的思路是用回溯法列出所有等长子序列,再计算相等的串数。显然时间复杂度比较感人,逻辑是没问题,但有用例超时。

答案用dp做,逻辑确实有点难懂。

代码

#include <vector>
#include <string>
using std::vector;
using std::string;
class Solution {
// private:
//     vector<string> permutation;
//     string path;
//     void backtracking(const string&s,int len,int start) {
//         if (path.size()==len) {
//             permutation.push_back(path);
//             return;
//         }
//         for (int i=start;i<=s.size()-(len-path.size())+1;++i) {
//             path.push_back(s[i]);
//             backtracking(s,len,i+1);
//             path.pop_back();
//         }
//     }
public:
    int numDistinct(string s, string t) {
        // permutation.clear();
        // path="";
        // backtracking(s,t.size(),0);
        // int res=0;
        // for (int i=0;i<permutation.size();++i) {
        //     if (permutation[i]==t) ++res;
        // }
        // return res;
        vector<vector<uint64_t>> dp(s.size()+1,vector<uint64_t>(t.size()+1,0));
        dp[0][0]=1;
        for (int i=0;i<=s.size();++i) dp[i][0]=1;
        for (int i=1;i<=s.size();++i) {
            for (int j=1;j<=t.size();++j) {
                if (s[i-1]==t[j-1]) dp[i][j]=dp[i-1][j]+dp[i-1][j-1];
                else dp[i][j]=dp[i-1][j];
            }
        }
        return dp[s.size()][t.size()];
    }
};

9. 两个字符串的删除操作

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

说实话,做这题没思考太多,大多是直觉和经验写出来的代码……

代码

#include <vector>
#include <string>
using std::vector;
using std::string;
using std::min;
class Solution {
public:
    int minDistance(string word1, string word2) {
        vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1,0));
        for (int i=1;i<=word1.size();++i) dp[i][0]=i;
        for (int j=1;j<=word2.size();++j) dp[0][j]=j;
        for (int i=1;i<=word1.size();++i) {
            for (int j=1;j<=word2.size();++j) {
                if (word1[i-1]==word2[j-1]) dp[i][j]=dp[i-1][j-1];
                else {
                    dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1);
                    dp[i][j]=min(dp[i-1][j-1]+2,dp[i][j]);
                }
            }
        }
        return dp[word1.size()][word2.size()];
    }
};

10. 编辑距离

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

感觉两个字符串操作的dp基本有公式了。

代码

#include <vector>
#include <string>
using std::vector;
using std::string;
using std::min;
class Solution {
public:
    int minDistance(string word1, string word2) {
        vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1,0));
        for (int i=1;i<=word1.size();++i) dp[i][0]=i;
        for (int j=1;j<=word2.size();++j) dp[0][j]=j;
        for (int i=1;i<=word1.size();++i) {
            for (int j=1;j<=word2.size();++j) {
                if (word1[i-1]==word2[j-1]) dp[i][j]=dp[i-1][j-1];
                else {
                    dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1);
                    dp[i][j]=min(dp[i][j],dp[i-1][j-1]+1);
                }
            }
        }
        return dp[word1.size()][word2.size()];
    }
};