LeetCode学习计划——高效制胜

Day1 求和问题

1、两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        
        /*int i,j;
        for(i=0;i<nums.size()-1;i++)
        {
            for(j=0;j<nums.size();j++)
            {
                if(nums[i]+nums[j]==target && i!=j)               
                return {i,j};
            }
        }
        return {i,j};*/

        //哈希表
        unordered_map<int, int> map; //哈希表的键是数组中的值,哈希表的值是数组中的索引下标

        for(int i = 0; i < nums.size(); i++){           
            auto iter = map.find(target - nums[i]);//迭代器访问用first second
            if(iter != map.end()){
                return {iter->second, i};
            }  
            map[nums[i]] = i; //先找后存 避免nums[i]和自己匹配        
        }

        return {};
    }
};

2、两数之和 II - 输入有序数组
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        int n = numbers.size() - 1;
        int left = 0, right = n;//左指针指向第一个,右指针指向最后一个

        while(left < right){
            if((numbers[left] + numbers[right]) == target){
                return {left + 1, right + 1};
            }
            else if((numbers[left] + numbers[right]) > target){
                right--;
            }
            else {
                left++;
            }
        }

        return {-1, -1};
    }    
};

Day2 求和问题

1、三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。

class Solution {
public:
    //双指针
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;        
        sort(nums.begin(), nums.end());//从小到大排序
        
        for(int i = 0; i < nums.size(); i++){
            if(nums[i] > 0) return res;//

            int left = i + 1;
            int right = nums.size() - 1;

            if(i > 0 && nums[i] == nums[i - 1]){
                continue;
            }

            while(left < right){
                if(nums[i] + nums[left] + nums[right] > 0){
                    right--;
                }
                else if(nums[i] + nums[left] + nums[right] < 0){
                    left++;
                }
                else{
                    res.push_back(vector<int> {nums[i], nums[left], nums[right]});

                    //去重
                    while(left < right && nums[left] == nums[left + 1]) left++;
                    while(left < right && nums[right] == nums[right - 1]) right--;

                    left++;
                    right--;
                }
            }
        }

        return res;

    }
};

2、四数之和
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());

        for(int i = 0; i < nums.size(); i++){
            //去重
            if(i > 0 && nums[i] == nums[i - 1]){
                continue;//
            }

            for(int j = i + 1; j < nums.size(); j++){
                //去重
                if(j > i + 1 && nums[j] == nums[j - 1]){
                    continue;//
                }

                int left = j + 1;
                int right = nums.size() - 1;

                while(left < right){
                    // nums[k] + nums[i] + nums[left] + nums[right] > target 会溢出
                    if(nums[i] + nums[j] > target - nums[left] - nums[right]){
                        right--;
                    }
                    else if(nums[i] + nums[j] < target - nums[left] - nums[right]){
                        left++;
                    }
                    else{
                        res.push_back(vector<int> {nums[i], nums[j], nums[left], nums[right]});

                        while(left < right && nums[left] == nums[left + 1]) left++;
                        while(left < right && nums[right] == nums[right - 1]) right--;

                        left++;
                        right--; 
                    }
                }
            }
        }
        return res;

    }
};

Day3 斐波那契数列

1、斐波那契数
斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n ,请计算 F(n) 。

class Solution {
public:
    int fib(int n) {
        /*if(n < 2)
        {
            return n;
        }

        int p = 0,q = 0,r = 1;
        for(int i = 2;i <= n;++i)
        {
            p = q;
            q = r;
            r = p + q;
         
        }
        return r;*/

        //递归
        /*if(n < 2) return n;
        return fib(n - 1) + fib(n - 2);*/

        //动态规划
        if(n < 2) return n;
        
        vector<int> dp(n + 1);
        dp[0] = 0;
        dp[1] = 1;

        for(int i = 2; i <= n; i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }

        return dp[n];

    }
};

2、爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

class Solution {
public:
    int climbStairs(int n) { 
        /*int p=0,q=0,r=1;
        for(int i=1;i<=n;i++)
        {
            p=q;
            q=r;
            r=p+q; //f(x)=f(x-1)+f(x-2)
        }
        return r;
        */

        //动态规划
        if(n <= 1) return n;

        vector<int> dp(n + 1);
        dp[0] = 1;
        dp[1] = 1;

        for(int i = 2; i <= n; i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }

        return dp[n];
    }
};

Day4 动态规划法

1、最大子数组和
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        /*int sum = nums[0];
        int n = nums[0];

        //判断前几项之和是否大于0,大于则加,小于舍弃重新开始记录,每次记录最大值,最后返回这个最大值
        for(int i=1;i<nums.size();i++)
        {
            if(n>0)
            {
                n += nums[i];
            }
            else
            {
                n = nums[i];
            }

            if(sum<n)
            {
                sum = n;
            }
        }
        return sum;*/

        //动态规划
        int n = nums.size();
        vector<int> dp(n);
        dp[0] = nums[0];
        int res = dp[0];

        for(int i = 1; i < n; i++){
            if(dp[i - 1] > 0){
                dp[i] = dp[i - 1] + nums[i];
            }
            else{
                dp[i] = nums[i];
            }

            res = max(res, dp[i]);            
        }

        return res;
    }
};

2、分割等和子集
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

class Solution {
public:
    //要求集合里能否出现总和为 sum / 2 的子集
    bool canPartition(vector<int>& nums) {
        //每个数组中的元素不会超过 100,数组的大小不会超过 200
        // 总和不会大于20000,背包最大只需要其中一半
        vector<int> dp(10001, 0);//dp[j]表示 背包总容量是j,最大可以凑成j的子集总和为dp[j]。
        int sum = 0;
        for(int m = 0; m < nums.size(); m++){
            sum += nums[m];
        }
        if(sum % 2 == 1) return false; //sum为奇数,不可能分成两个子集
        int target = sum / 2;

        //遍历
        for(int i = 0; i < nums.size(); i++){
            for(int j = target; j >= nums[i]; j--){
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]); //dp[j]表示不放进去, dp[j - nums[i]] + nums[i]表示放进去
            }
        }

        if(dp[target] == target) return true;//集合中的子集总和正好可以凑成总和target
        return false;

    }
};

3、零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。

class Solution {
public:
//动态规划
//dp[i]表示兑换面额为i所需要的的最少硬币的个数
//dp[i] = min(dp[i], dp[i - coin] + 1)  上一个状态再加1个硬币coin就可以得到下个状态
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount + 1, 1e9);
        dp[0] = 0;

        for(int i = 1; i <= amount; i++){
            for(int j = 0; j < coins.size(); j++){//枚举上一个状态
                if(i - coins[j] >= 0) dp[i] = min(dp[i], dp[i - coins[j]] + 1);
            }
        }

        return dp[amount] == 1e9 ? -1 : dp[amount];

    }
};

Day5 堆栈

1、有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

class Solution {
public:
    bool isValid(string s) {
       /* //如果字符串s中的个数为奇数 返回false
        int n = s.length();
        if(n%2 == 1)
        return false;

        //定义字典 前面的为key 后面的为value
        unordered_map<char , char> pairs={
            {')','('},
            {']','['},
            {'}','{'}
        };

        stack<char> stk;
        //遍历字符串s
        for(char ch:s){
            if(pairs.count(ch)) //如果为pairs中的')' ']' '}'
            {
                if(stk.empty() || stk.top() != pairs[ch])   //pairs[ch]指的是map中的第二列字符
                {
                    return false;
                }
                stk.pop();
            }
            else  //如果为pairs中的'(' '[' '{'
            {
                stk.push(ch);
            }
        }
        return stk.empty();  //判断堆栈是否为空
*/

        stack<int> st;

        for(int i = 0; i < s.size(); i++){
            if(s[i] == '(') st.push(')');
            else if(s[i] == '{') st.push('}');
            else if(s[i] == '[') st.push(']');

            else if(st.empty() || st.top() != s[i]) return false;
            else st.pop();
        }

        return st.empty();
    }
};

2、下一个更大元素 I
nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。
给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。
对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1 。
返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素 。

class Solution {
public:
//栈头到栈顶 递增的单调栈   //递减栈就是求右边第一个比自己小的元素了
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
        vector<int> vec(nums1.size(), -1);
        stack<int> st;
        if(nums1.size() == 0) return vec;

        unordered_map<int, int> map; //key为值,value为索引
        for(int i = 0; i < nums1.size(); i++){
            map[nums1[i]] = i;
        }

        st.push(0);      
        for(int j = 1; j < nums2.size(); j++){
            /*if(nums2[j] < nums2[st.top()]){
                st.push(j);
            }
            else if(nums2[j] == nums2[st.top()]){
                st.push(j);
            }
            else{*/
                while(!st.empty() && nums2[j] > nums2[st.top()]){
                    if(map.count(nums2[st.top()])){ //map中是否存在此时的栈顶元素
                        int index = map[nums2[st.top()]]; //存在,拿到索引值
                        vec[index] = nums2[j];
                    }
                    st.pop();
                }
                st.push(j);              
            //}
        }

        return vec;

    }
};

3、132 模式
给你一个整数数组 nums ,数组中共有 n 个整数。132 模式的子序列 由三个整数 nums[i]、nums[j] 和 nums[k] 组成,并同时满足:i < j < k 和 nums[i] < nums[k] < nums[j] 。
如果 nums 中存在 132 模式的子序列 ,返回 true ;否则,返回 false 。

class Solution {
public:
//单调栈
    bool find132pattern(vector<int>& nums) {
        stack<int> st;//递增栈(栈头到栈底)  
        int mid = -INT_MAX; //初始化中间的值为最小

        for(int i = nums.size() - 1; i >= 0; i--){
            if(nums[i] < mid) return true; //存在nums[i]比次大值小

            while(!st.empty() && st.top() < nums[i]){  //栈顶元素小于nums[i]  栈顶元素更新为中间值  将该值弹出 
                mid = st.top();//mid是nums[i]后面的最大值 即mid为包括nums[i]在内的后面的次大值
                st.pop();
            }

            st.push(nums[i]); //压进去的是包括nums[i]在内的后面的最大值
        }

        return false;

    }
};

Day6 数字

1、杨辉三角 II
给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。

class Solution {
public:
//从倒数第二个元素开始往前更新 它等于原来这个位置的数 + 前一个位置的数
//行[i] = 行[i] + 行[i-1]
    vector<int> getRow(int rowIndex) {
        /*vector<int> Row(rowIndex + 1);//第K行的vector大小为 rowIndex+1   

        for(int i=0;i<=rowIndex;i++)
        {
            Row[i] = 1;//行末尾为1

            for(int j = i;j>1;j--)//每一行的更新过程
            {
                Row[j-1] += Row[j-2]; 
            }
        }
        return Row;*/

        vector<vector<int>> res(rowIndex + 1);

        for(int i = 0; i <= rowIndex; i++){
            res[i].resize(i + 1);
            res[i][0] = res[i][i] = 1; //首尾为1

            for(int j = 1; j < i; j++){
                res[i][j] = res[i - 1][j - 1] + res[i - 1][j]; 
            }
        }

        return res[rowIndex];
    }
};

2、完全平方数
给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。

class Solution {
public:
//动态规划
//dp[11]的前一个状态可以为:dp[11 - 1*1]=dp[10],dp[11 - 2*2]=dp[7],dp[11 - 3*3]=dp[2],它们再数量上加1即可得到dp[11] (因为只减了1个j*j)
//dp[i] = min(dp[i], dp[i - j * j] + 1)  dp[i]表示返回和为 i 的完全平方数的最少数量 
    int numSquares(int n) {
        vector<int> dp(n + 1, 0);

        for(int i = 0; i <= n; i++){
            dp[i] = i;//初始化为最大值  最坏的情况下是全部由1相加得到 即初始化为1的数量

            for(int j = 0; i - j * j >= 0; j++){ //枚举上一个状态
                dp[i] = min(dp[i], dp[i - j * j] + 1); //
            }
        } 

        return dp[n];

    }
};

3、最小好进制
以字符串的形式给出 n , 以字符串的形式返回 n 的最小 好进制 。
如果 n 的 k(k>=2) 进制数的所有数位全为1,则称 k(k>=2) 是 n 的一个 好进制 。

class Solution {
public:
    string smallestGoodBase(string n) {
        long nVal = stol(n);
        int mMax = floor(log(nVal) / log(2));

        for(int m = mMax; m > 1; m--){
            int k = pow(nVal, 1.0 / m);
            long mul = 1;
            long sum = 1;

            for(int i = 0; i < m; i++){
                mul *= k;
                sum += mul;
            }
            if(sum == nVal) return to_string(k);
        }

        return to_string(nVal - 1);

    }
};

Day7 树

1、路径总和
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
叶子节点 是指没有子节点的节点。

/**
 * 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 hasPathSum(TreeNode* root, int targetSum) {
        if(root == nullptr) return false;
        if(root->left == nullptr && root->right == nullptr) return root->val == targetSum;

        return hasPathSum(root->left, targetSum - root->val) || hasPathSum(root->right, targetSum - root->val);

    }
};

2、二叉搜索树中第K小的元素
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 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:
//中序遍历可得从小到大排列的数组
    void inOrder(TreeNode* root, vector<int> &res){
        if(root == nullptr) return;

        inOrder(root->left, res);
        res.push_back(root->val);
        inOrder(root->right, res);
    }

    int kthSmallest(TreeNode* root, int k) {
        if(root == nullptr) return 0;

        vector<int> res;
        inOrder(root, res);
        //return res; //res为从小到大的排序数组
        /*for(int i = 0; i<res.size();i++){
            cout<<res[i]<<endl;
        }*/
        return res[k - 1];
    }
};

3、监控二叉树
给定一个二叉树,我们在树的节点上安装摄像头。
节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。
计算监控树的所有节点所需的最小摄像头数量。

/**
 * 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:
//自下往上  后序遍历
//尽量让叶子节点的父节点安装摄像头,这样摄像头的数量才是最少的
//0表示该节点无覆盖;
//1表示该节点有摄像头;
//2表示该节点有覆盖
    int res = 0;
    //该函数返回值是 当前根节点的状态
    int postOrderPro(TreeNode* root){
        if(root == nullptr) return 2;//空节点也要 有覆盖

        int left = postOrderPro(root->left);//左边
        int right = postOrderPro(root->right);//右边

        //中间的做处理
        //总情况为 3 * 3 + 1 = 9 + 1 = 10

        //情况一:左右节点 都有覆盖  父节点必无覆盖   1
        if(left == 2 && right == 2) return 0;

        //情况二:左右节点 至少有一个无覆盖  要加一个摄像头--父节点   5
        if(left == 0 || right == 0){
            res++;
            return 1;
        }

        //情况三:左右节点 至少有一个有摄像头  父节点必有覆盖   3
        if(left == 1 || right == 1){
            return 2;
        }

        return -1;
    }

    int minCameraCover(TreeNode* root) {
        //情况四:根节点无覆盖  要加一个摄像头--父节点   1
        if(postOrderPro(root) == 0) res++; 

        return res;
    }
};

Day8 字符串

1、词典中最长的单词
给出一个字符串数组 words 组成的一本英语词典。返回 words 中最长的一个单词,该单词是由 words 词典中其他单词逐步添加一个字母组成。
若其中有多个可行的答案,则返回答案中字典序最小的单词。若无答案,则返回空字符串。

class Solution {
public:
    string longestWord(vector<string>& words) {
        unordered_set<string> S(words.begin(), words.end());
        string res;

        for(auto w : words){ //遍历字符串数组中的每个字符串
            bool flag = true;

            for(int i = 0; i < w.size() - 1; i++){
                string preword = w.substr(0, i + 1);//拿到w的前i个字符
                if(!S.count(preword)){
                    flag = false;//如果前i个字符在哈希集合里不存在
                    break; //进行下一个w的判断
                }
            }

            //w的每一个前i个字符在哈希集合中都存在
            if(flag){
                if(res.empty() || w.size() > res.size() || w.size() == res.size()
                 && w < res){ //w的字典序小于当前存储的单词字典序
                        res = w; //更新res
                }
            }
        }

        return res;
    }
};

2、无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_set<char> Sc;
        int n = s.size();
        int right = -1, res = 0; //right为右指针 res为结果
        for(int i = 0; i < n; i++){ //i为左指针
            //左指针移动一位 从哈希集合中删除一个元素
            if(i != 0){
                Sc.erase(s[i - 1]);
            }

            //右指针一直移动 只要没移动到字符串尾 只要哈希集合中没有该元素  
            while(right + 1 < n && !Sc.count(s[right + 1])){
                Sc.insert(s[right + 1]);//向哈希集合中插入右指针所指向的字符
                right++;
            }

            res = max(res, right - i + 1);
        }

        return res;
    }
};

3、交错字符串
给定三个字符串 s1、s2、s3,请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。
两个字符串 s 和 t 交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串:
s = s1 + s2 + … + sn
t = t1 + t2 + … + tm
|n - m| <= 1
交错 是 s1 + t1 + s2 + t2 + s3 + t3 + … 或者 t1 + s1 + t2 + s2 + t3 + s3 + …
注意:a + b 意味着字符串 a 和 b 连接。

class Solution {
public:
//DP
    bool isInterleave(string s1, string s2, string s3) {
        int lengthS1 = s1.size(), lengthS2 = s2.size(), lengthS3 = s3.size();

        if(lengthS1 + lengthS2 != lengthS3) return false;//长度不相等 返回false

        //定义 dp[i][j] 表示 s1的前 i 个元素和 s2的前 j 个元素是否能交错组成 s3的前 i+j 个元素
        vector<vector<int>> dp(lengthS1 + 1, vector<int>(lengthS2 + 1, false));
        dp[0][0] = true;

        for(int i = 1; i <= lengthS1; i++){
            dp[i][0] = dp[i - 1][0] && s1[i - 1] == s3[i - 1];//第一列
        }
        for(int j = 1; j <= lengthS2; j++){
            dp[0][j] = dp[0][j - 1] && s2[j - 1] == s3[j - 1];//第一行
        }

        for(int i = 1; i <= lengthS1; i++){
            for(int j = 1; j <= lengthS2; j++){
                dp[i][j] = (dp[i - 1][j] && s1[i - 1] == s3[i + j - 1]) || (dp[i][j - 1] && s2[j - 1] == s3[i +j - 1]);
            }
        }

        return dp[lengthS1][lengthS2];
    }
};

Day9 字符串搜索

1、实现 strStr()
实现 strStr() 函数。
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。

class Solution {
public:
    //获取next数组
    int* getNextArray(string needle){
        int* next = new int[needle.size()];

        if(needle.size() == 1){
            next[0] = -1;//人为规定
            return next;
        }

        next[0] = -1;
        next[1] = 0;

        int cn = 0;//哪一个位置和next[i - 1]进行比较 进而得出next[i]的值 ;(cn所处的索引值等于next[i - 1]的值)
        for(int i = 2; i < needle.size();){
            if(needle[i - 1] == needle[cn]){
                next[i] = cn + 1;
                i++; cn++;
                //next[i++] = ++cn;
            }
            else if(cn > 0){//needle[i - 1] != needle[cn] 且cn > 0 则cn跳回到next[cn]所对应的位置 继续参与比较
                cn = next[cn];
            }
            else{//cn不能再往前跳 已到字符串头部 则最长相等前后缀长度为0
                next[i] = 0;
                i++;
            }
        }

        return next;
    }

    int strStr(string haystack, string needle) {
        /*int n = haystack.size();
        int m = needle.size();

        for(int i = 0; i + m <= n; i++){
            bool flag = true;
            for(int j = 0; j < m; j++){
                if(haystack[i + j] != needle[j]){
                    flag = false;
                    break;
                }
            }

            if(flag) return i;
        }

        return -1;*/

        if(needle.size() == NULL) return 0;
        /*if(haystack.size() == NULL || needle.size() == NULL || needle.size() < 1 || haystack.size() < needle.size()){
            return -1;
        }*/
        if(haystack.size() < needle.size()){
            return -1;
        }

        int *nextArray = getNextArray(needle);
        int i = 0, j = 0;
        while(i < haystack.size() && j < needle.size()){
            if(haystack[i] == needle[j]){
                i++;
                j++;//每匹配成功一次 j移动一次  直到越界则说明匹配成功
            }
            else if(j == 0){//或者 else if(nextArray[j] == -1)   //j不能再往回跳
                i++;//移动haystack的指针
            }
            else{ 
                j = nextArray[j];//跳回去 进行下一次比较
            }
        }

        return j == needle.size() ? i - j : -1; //匹配成功返回 两个指针的差
    }
};

2、通过删除字母匹配到字典里最长单词
给你一个字符串 s 和一个字符串数组 dictionary ,找出并返回 dictionary 中最长的字符串,该字符串可以通过删除 s 中的某些字符得到。
如果答案不止一个,返回长度最长且字母序最小的字符串。如果答案不存在,则返回空字符串。

class Solution {
public:
    string findLongestWord(string s, vector<string>& dictionary) {
        //双指针 + 排序
        string res = "";

        /*sort(dictionary.begin(), dictionary.end(), [](string &x, string &y){
            //if(x.size() != y.size()) return x.size() < y.size();
            //else return x > y;
            return x.size() == y.size() ? x > y : x.size() < y.size();
        });*/
        
        for(string str : dictionary){
            int i = 0, j = 0;
            while(i < s.size() && j < str.size()){
                if(s[i] == str[j]) j++;
                i++;
            }

            /*if(j == str.size()) res = str;*/
            
            if(j == str.size()){
                //运算符优先级 先与后或
                if(str.size() > res.size() || (str.size() == res.size() && str < res)) res = str;
            }
            
        }

        return res;

    }    
};

3、最长快乐前缀
「快乐前缀」是在原字符串中既是 非空 前缀也是后缀(不包括原字符串自身)的字符串。
给你一个字符串 s,请你返回它的 最长快乐前缀。
如果不存在满足题意的前缀,则返回一个空字符串。

class Solution {
public:
//kmp
    int* nextArray(string s){
        int* next = new int[s.size()];
        if(s.size() == 1){
            next[0] = -1;
            return next;
        }

        next[0] = -1;
        next[1] = 0;
        int cn = 0;
        for(int i = 2; i < s.size();){
            if(s[i - 1] == s[cn]){
                next[i] = cn +1;
                i++; cn++;
            }
            else if(cn > 0){
                cn = next[cn];
            }
            else{
                next[i] = 0;
                i++;
            }
        }

        return next;

    }

    string longestPrefix(string s) {
        s +=' ';
        int *next = nextArray(s);

        //substr 起始位置和数目
        return s.substr(0, next[s.size() - 1]); //应该返回的是 字符串中最后一位子串的最长相等前后缀长度即补了空格之后的next[s.size()-1]

    }
};

Day10 图

1、不邻接植花
有 n 个花园,按从 1 到 n 标记。另有数组 paths ,其中 paths[i] = [xi, yi] 描述了花园 xi 到花园 yi 的双向路径。在每个花园中,你打算种下四种花之一。
另外,所有花园 最多 有 3 条路径可以进入或离开.
你需要为每个花园选择一种花,使得通过路径相连的任何两个花园中的花的种类互不相同。
以数组形式返回 任一 可行的方案作为答案 answer,其中 answer[i] 为在第 (i+1) 个花园中种植的花的种类。花的种类用 1、2、3、4 表示。保证存在答案。

class Solution {
public:
/*1、根据paths建立邻接表;
2、默认所有的花园先不染色,即染0;
3、从第一个花园开始走,把与它邻接的花园的颜色从color{1,2,3,4}这个颜色集中删除;
4、删完了所有与它相邻的颜色,就可以把集合中剩下的颜色随机选一个给它了,为了简单,将集合中的第一个颜色赋给当前花园;
5、循环3和4到最后一个花园。*/

//vector<int> v[n]:表示n个vector v ,二维数组
//相当于 vector<vector<int > > v(n);二维数组
    vector<int> gardenNoAdj(int n, vector<vector<int>>& paths) {
        //建立邻接表
        vector<vector<int>> graph(n);
        for(int i = 0; i < paths.size(); i++){         
            graph[paths[i][0] - 1].push_back(paths[i][1] - 1);         
            graph[paths[i][1] - 1].push_back(paths[i][0] - 1);
        }
        //cout<<graph[0][0]<<endl; cout<<graph[0][1]<<endl;  // "0" : "1", "2"  //1
        //cout<<graph[1][0]<<endl; cout<<graph[1][1]<<endl;  // "1" : "0", "2"  //2
        //cout<<graph[2][0]<<endl; cout<<graph[2][1]<<endl;  // "2" : "1", "0"  //3
        //cout<<graph.size()<<endl;  //行数 3  
    
        vector<int> res(n, 0); //初始化全为0 都没染色
        for(int i = 0; i < n; i++){
            set<int> color{1, 2, 3, 4};//
            for(int j = 0; j < graph[i].size(); j++){
                color.erase(res[graph[i][j]]);//删除相邻的 已染过的颜色
            }
            res[i] = *(color.begin()); //染色  每次取color集合中的第一个元素
        }

        return res;
    }
};

2、K 站中转内最便宜的航班
有 n 个城市通过一些航班连接。给你一个数组 flights ,其中 flights[i] = [fromi, toi, pricei] ,表示该航班都从城市 fromi 开始,以价格 pricei 抵达 toi。
现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到出一条最多经过 k 站中转的路线,使得从 src 到 dst 的 价格最便宜 ,并返回该价格。 如果不存在这样的路线,则输出 -1。

class Solution {
public:
//auto&&或函数参数类型的自动推导的T&& 是一个未定的引用类型,它可能是左值引用,也可能是右值引用,取决于初始化的值类型
//T&& 这是一个左值,只不过她的类型是右值引用,只能绑定右值

//用 f[t][i] 表示通过恰好 t 次航班,从出发城市 src 到达城市 i 需要的最小花费
//DP方程: f[t][i] = min(f[t][i], f[t - 1][j] + cost(j, i))
//最多只能中转 k 次,也就是最多搭乘 k+1 次航班,最终的答案即为 f[1][dst],f[2][dst],⋯,f[k+1][dst] 中的最小值。

    static constexpr int inf = 101 * 10000 + 1; //constexpr仅发生在编译期
    int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
        vector<vector<int>> f(k + 2, vector<int>(n, inf));

        f[0][src] = 0;
        for(int t = 1; t <= k + 1; t++){
            for(auto& flight : flights){ //for(auto&& flight : flights){
                int j = flight[0], i = flight[1], cost = flight[2];
                f[t][i] = min(f[t][i], f[t - 1][j] + cost);
            }
        }
        
        int res = inf;
        for(int i = 0; i <= k + 1; i++){
            res = min(res, f[i][dst]);
        }

        return res == inf ? -1 : res;
    }
};

Day11 图

1、单词搜索
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

class Solution {
public:
    int dx[4] = {1, 0, -1, 0}; int dy[4] = {0, 1, 0, -1};
    
    bool dfs(vector<vector<char>>& board, string word, int x, int y, int k){
        if(board[x][y] != word[k]) return false;
        if(k == word.size() - 1) return true;

        char tmp = board[x][y];
        board[x][y] = '.';//已经搜索过

        for(int i = 0; i < 4; i++){
            int mx = x + dx[i]; int my = y + dy[i];

            if(mx < 0 || mx >= board.size() || my < 0 || my >= board[0].size() || board[mx][my] == '.'){
                continue;
            }
            if (dfs(board, word, mx, my, k + 1)) return true;    
        }
        board[x][y] = tmp;

        return false;
    }

    bool exist(vector<vector<char>>& board, string word) {
        int m = board.size();
        int n = board[0].size();

        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(dfs(board, word, i, j, 0)) return true;
            }
        }

        return false;
    }
};

2、矩阵中的最长递增路径
给定一个 m x n 整数矩阵 matrix ,找出其中 最长递增路径 的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你 不能 在 对角线 方向上移动或移动到 边界外(即不允许环绕)。

class Solution {
public:
//DFS + DP
    int dx[4] = {1, 0, -1, 0}; int dy[4] = {0, 1, 0, -1};
    int dfs(vector<vector<int>>& matrix, int x, int y, vector<vector<int>>& visited){
        if(visited[x][y] != 0) return visited[x][y];//

        visited[x][y]++;  //满足条件matrix[mx][my] > matrix[x][y] 后执行     

        for(int i = 0; i < 4; i++){
            int mx = x + dx[i]; int my = y + dy[i];

            //if(mx < 0 || mx >= matrix.size() || my < 0 || my >= matrix[0].size() || matrix[mx][my] <= matrix[x][y]) continue;
            if(mx >= 0 && mx < matrix.size() && my >= 0 && my < matrix[0].size() && matrix[mx][my] > matrix[x][y]){
                visited[x][y] = max(visited[x][y], dfs(matrix, mx, my, visited) + 1);
            }          
        }

        return visited[x][y];
    }

    int longestIncreasingPath(vector<vector<int>>& matrix) {
        if(matrix.size() == 0 || matrix[0].size() == 0) return 0;
        int m = matrix.size();
        int n = matrix[0].size();

        vector<vector<int>> visited(m, vector<int>(n, 0)); //矩阵中(i, j)表示 在matrix上 格子(i, j)能走的 最长的递增路径(包括自身)
        int res = 0;
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                res = max(res, dfs(matrix, i, j, visited));
            }
        }

        return res;
    }
};

Day12 生活趣题

1、买卖股票的最佳时机
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        /*int inf = 1e9;
        int minprice = inf, maxprofit = 0;

        for(auto price : prices){
            maxprofit = max(maxprofit, price - minprice);
            minprice = min(minprice, price);
        }

        return maxprofit;*/

        
        //DP
        //dp[i][0]表示第i天持有股票所得的现金
        //dp[i][1]表示第i天不持有股票所得的现金
        if(prices.size() == 0) return 0;

        vector<vector<int>> dp(prices.size(), vector<int>(2));
        dp[0][0] = -prices[0];
        dp[0][1] = 0;

        for(int i = 1; i < prices.size(); i++){
            dp[i][0] = max(dp[i - 1][0], -prices[i]); //第i天持有股票所得的现金:要么是昨天持有;要么是今天买入
            dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);//第i天不持有股票所得的现金:要么是昨天也没有;要么是今天卖出
        }

        return dp[prices.size() - 1][1];

    }
};

2、买卖股票的最佳时机 II
给定一个数组 prices ,其中 prices[i] 表示股票第 i 天的价格。
在每一天,你可能会决定购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以购买它,然后在 同一天 出售。
返回 你能获得的 最大 利润 。

class Solution {
public:

    int maxProfit(vector<int>& prices) {
        /*//贪心算法 
        int maxprofit = 0;

        for(int i = 1; i < prices.size(); i++){
            maxprofit += max(prices[i] - prices[i - 1], 0); //只收集每天的正利润,最后稳稳的就是最大利润了
        }

        return maxprofit;*/

        //DP
        //dp[i][0]表示第i天持有股票所得的现金
        //dp[i][1]表示第i天不持有股票所得的现金
        if(prices.size() == 0) return 0;

        vector<vector<int>> dp(prices.size(), vector<int>(2));
        dp[0][0] = -prices[0];
        dp[0][1] = 0;

        for(int i = 1; i < prices.size(); i++){
            // 注意这里是和121. 买卖股票的最佳时机唯一不同的地方 : 
            //因为一只股票可以买卖多次,当第i天买入股票的时候,所持有的现金可能包含有之前买卖过的利润。
            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]-prices[i]); //第i天持有股票所得的现金:要么是昨天持有;要么是今天买入加上之前不持有股票时的利润
            dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);//第i天不持有股票所得的现金:要么是昨天也没有;要么是今天卖出
        }

        return dp[prices.size() - 1][1];
    }
};

Day13 生活趣题

1、天际线问题
城市的 天际线 是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓。给你所有建筑物的位置和高度,请返回 由这些建筑物形成的 天际线 。
每个建筑物的几何信息由数组 buildings 表示,其中三元组 buildings[i] = [lefti, righti, heighti] 表示:
lefti 是第 i 座建筑物左边缘的 x 坐标。
righti 是第 i 座建筑物右边缘的 x 坐标。
heighti 是第 i 座建筑物的高度。
你可以假设所有的建筑都是完美的长方形,在高度为 0 的绝对平坦的表面上。
天际线 应该表示为由 “关键点” 组成的列表,格式 [[x1,y1],[x2,y2],…] ,并按 x 坐标 进行 排序 。关键点是水平线段的左端点。列表中最后一个点是最右侧建筑物的终点,y 坐标始终为 0 ,仅用于标记天际线的终点。此外,任何两个相邻建筑物之间的地面都应被视为天际线轮廓的一部分。
注意:输出天际线中不得有连续的相同高度的水平线。例如 […[2 3], [4 5], [7 5], [11 5], [12 7]…] 是不正确的答案;三条高度为 5 的线应该在最终输出中合并为一个:[…[2 3], [4 5], [12 7], …]

//(此题暂时放弃)

2、保持城市天际线
给你一座由 n x n 个街区组成的城市,每个街区都包含一座立方体建筑。给你一个下标从 0 开始的 n x n 整数矩阵 grid ,其中 grid[r][c] 表示坐落于 r 行 c 列的建筑物的 高度 。
城市的 天际线 是从远处观察城市时,所有建筑物形成的外部轮廓。从东、南、西、北四个主要方向观测到的 天际线 可能不同。
我们被允许为 任意数量的建筑物 的高度增加 任意增量(不同建筑物的增量可能不同) 。 高度为 0 的建筑物的高度也可以增加。然而,增加的建筑物高度 不能影响 从任何主要方向观察城市得到的 天际线 。
在 不改变 从任何主要方向观测到的城市 天际线 的前提下,返回建筑物可以增加的 最大高度增量总和 。

class Solution {
public:
//先创建两个数组 记录每一行和每一列的最大值
//该建筑物增加后的最大高度是 min(rowMax[i],colMax[j])。由于该建筑物的原始高度是 grid[i][j],
//因此该建筑物高度可以增加的最大值是 min(rowMax[i],colMax[j])−grid[i][j]。

    int maxIncreaseKeepingSkyline(vector<vector<int>>& grid) {
        int n = grid.size();

        vector<int> rowsMax(n, 0);
        vector<int> colsMax(n, 0);
        for(int i = 0; i < n; i++){           
            for(int j = 0; j < n; j++){
                rowsMax[i] = max(rowsMax[i], grid[i][j]); //每一行的最大值
                colsMax[j] = max(colsMax[j], grid[i][j]); //每一列的最大值
            }
        }

        int res = 0;        
        for(int i = 0; i < n; i++){           
            for(int j = 0; j < n; j++){
                res += min(rowsMax[i], colsMax[j]) - grid[i][j];
            }
        }

        return res;
    }
};

Day14 生活趣题

1、盛最多水的容器
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。

class Solution {
public:
    int maxArea(vector<int>& height) {
        /*会超时 
        int res = 0;
        vector<int> dp(height.size());
        dp[0] = 0;
        for(int i = 1; i < height.size(); i++){
            for(int j = 0; j < i; j++){
                dp[i] = max(dp[i], min(height[i], height[j]) * (i - j));                
            }
        }

        sort(dp.begin(), dp.end());

        return dp[height.size() - 1];*/

		//双指针
        int res = 0;
        int left = 0, right = height.size() - 1;

        while(left < right){           
            res = max(res, min(height[left], height[right]) * (right - left));
            if(height[left] < height[right]) left++;
            else right--;
        }

        return res;
    }
};

2、接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

class Solution {
public:
//以列为单位求和   
    int trap(vector<int>& height) {
        /*//双指针  会超时
        int res = 0;
        for(int i = 0; i < height.size(); i++){        
            //第一个柱子和最后一个柱子不接雨水
            if(i == 0 || i == height.size() - 1) continue;

            int leftHeight = height[i]; //某一列左边柱子的最高值
            int rightHeight = height[i]; //某一列右边柱子的最高值

            for(int l = i - 1; l >= 0; l--){//for(int l = 0; l < i; l++){
                if(leftHeight < height[l]) leftHeight = height[l];
            }
            for(int r = i + 1; r < height.size(); r++){
                if(rightHeight < height[r]) rightHeight = height[r];
            }

            //计算该列的雨水高度 :该列 左边柱子的最高值 和 右边柱子的最高值 的较小值  减去  该列的柱子高度
            int h = min(leftHeight, rightHeight) - height[i];

            //对h求和
            if(h > 0) res += h;
        }

        return res;*/


        //优化:动态规划
        //把每一个位置的左边最高高度记录在一个数组上(maxLeft),右边最高高度记录在一个数组上(maxRight)
        //当前位置,左边的最高高度是前一个位置的左边最高高度和本高度的最大值。
        //即从左向右遍历:maxLeft[i] = max(height[i], maxLeft[i - 1]);
        //从右向左遍历:maxRight[i] = max(height[i], maxRight[i + 1]);
        if(height.size() <= 2) return 0;
        vector<int> leftHeight(height.size(), 0);
        vector<int> rightHeight(height.size(), 0);

        //记录每个柱子左边柱子最大高度(从左往右遍历)
        leftHeight[0] = height[0];
        for(int i = 1; i < height.size(); i++){
            leftHeight[i] = max(leftHeight[i - 1], height[i]);
        }
        //记录每个柱子右边柱子最大高度(从右往左遍历)
        rightHeight[rightHeight.size() - 1] = height[height.size() - 1];
        for(int j = rightHeight.size() - 2; j >= 0; j--){
            rightHeight[j] = max(rightHeight[j + 1], height[j]);
        }

        //求和
        int res = 0;
        for(int s = 0; s < height.size(); s++){
            //计算该列的雨水高度 :该列 左边柱子的最高值 和 右边柱子的最高值 的较小值  减去  该列的柱子高度
            int h = min(leftHeight[s], rightHeight[s]) - height[s];
            if(h > 0) res += h;
        }

        return res;
    }
};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wrdoct

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值