• LeetCode第 30 场双周赛题解


    5177. 转变日期格式

    思路:按照空格划分出年月日,年已经是数字形式,不需要做处理。月是英文单词的缩写,需要对应到相应的数字。
    对于日,只需要把结尾的英文序数词去掉,只留下数字即可。 这里要注意月和日可能是个位数,这种情况下要在前面补一个0.

    class Solution {
    string months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    
    public:
        string reformatDate(string date) {
            string day, month, year, res;
            int split = 0;                        //用双指针划分出字符串
            while(isdigit(date[split])) {      
                day += date[split];
                ++split;
            }
            if(day.size() < 2) {                  //如果是个位数,在前面补一个0
                day = '0' + day;
            }
            while(date[split] != ' ') {           //指针继续移动,划分下一个字符串(月)
                ++split;
            }
            ++split;
            while(date[split] != ' ') {
                month += date[split];
                ++split;
            }
            for(int i = 0; i < 12; ++i) {         //把月份的单词对应到相应的数字上
                if(months[i] == month) {
                    month = to_string(i + 1);
                }
            }
            if(month.size() < 2) {                //如果是个位数,在前面补一个0
                month = '0' + month;
            }
            ++split;
            year = date.substr(split);            //剩下的部分就是年
            //printf("%s-%s-%s", year.c_str(), month.c_str(), day.c_str());
            res = year + '-' + month + '-' + day;
            return res;
        }
    };
    

    这题划分字符串也可以不用双指针,用stringstream可以直接以空格为分隔符划分三个字符串。
    代码如下:

    class Solution {
    string months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    
    public:
        string reformatDate(string date) {
            stringstream ss(date);                  //用一个stringstream划分date
            string day, month, year, temp, res;      //temp表示每一个划分出的字符串
            while(getline(ss, temp, ' ')) {         //使用空格作为分隔符
                if(isdigit(temp[0]) && !isdigit(temp.back())) {    //如果某个划分出的字符串temp第一个字符是数字而最后一个不是,则说明现在划分出的字符串是日
                    while(!isdigit(temp.back())) {              //删掉末尾的英文序数词
                        temp.pop_back();
                    }
                    day = temp;                      
                    if(day.size() < 2) {                       //如果是个位数,在前面补个0
                        day = '0' + day;
                    }
                } else if(!isdigit(temp[0]) && !isdigit(temp.back())) {     //如果某个字符串第一个字符和末尾都不是数字,说明现在划分出的字符串是月
                    for(int i = 0; i < 12; ++i) {             
                        if(months[i] == temp) {
                            month = to_string(i + 1);             //把英文单词转化成对应的数字
                            break;
                        }
                    }
                    if(month.size() < 2) {                        //如果是个位数,在前面补个0
                        month = '0' + month;
                    }
                } else if(isdigit(temp[0]) && isdigit(temp.back())) {      //首末都是数字,说明是年,不作处理
                    year = temp;
                }
            }
            res = year + '-' + month + '-' + day;
            return res;
        }
    };
    

    这题用python做会方便很多:

    class Solution:
        def reformatDate(self, date: str) -> str:
            day, month, year = date.split(' ')
            Months = {"Jan":"01", "Feb":"02", "Mar":"03", "Apr":"04", "May":"05", "Jun":"06", "Jul":"07", "Aug":"08", "Sep":"09", "Oct":"10", "Nov":"11", "Dec":"12"}
            month = Months[month]
            day = day[:2] if day[1].isdigit() else '0' + str(day[0])
            return '-'.join([year, month, day])
    

    5445. 子数组和排序后的区间和

    这题题意是给定一个数组,求出他的所有子数组的和的可能,再对子数组的和排序组成新数组,
    对于新数组中下标从left到right(left和right给定)的数组再进算和并返回。

    要计算所有子数组和,最简单的想法是枚举所有子数组的起点和终点,再计算这一段的元素和。
    同时枚举起点和终点,再在起点和终点里计算和需要O(n^3)的复杂度。超时。

    计算子数组和,常见的方法是用一个前缀和数组来优化时间。
    额外用一个数组preSum表示每个元素的前缀和,前缀和就是从数组第一个元素到当前元素的和。
    比如preSum[3]就是nums[0] + nums[1] + nums[2] + nums[3]。
    这样做的好处就是,我们枚举了起点i和终点j之后,要计算这一段子数组的和不需要逐个相加,
    可以直接用preSum[j] - preSum[i]得到子数组和,这样时间就压缩到了O(n^2).

    代码如下:

    class Solution {
    typedef __int64_t bignum;                        //这题数据很大,我们用__int64_t
    bignum mod = 1e9 + 7;                            //返回结果要对1e9 + 7取余
    public:
        int rangeSum(vector<int>& nums, int n, int left, int right) {
            int size = nums.size();
            vector<bignum> preSum(size);
            preSum[0] = nums[0];
            for(int i = 1; i < size; ++i) {
                preSum[i] = preSum[i - 1] + nums[i];      //对所有元素计算前缀和
            }
            vector<bignum> sums;                       //sums数组保存所有的子数组和
            for(int i = 0; i < size; ++i) {
                sums.push_back(preSum[i]);              //每一个前缀和都是一个子数组和
                for(int j = i + 1; j < size; ++j) {
                    sums.push_back(preSum[j] - preSum[i]);     //枚举所有起点和终点,计算子数组的和
                }
            }
            //for(auto x : sums) cout << x << ' ';
            sort(sums.begin(), sums.end());
            bignum res = 0;
            for(int i = left - 1; i <= right - 1; ++i) {
                res += sums[i];          
            }
            return res % mod;
        }
    };
    

    最后一个for循环也可以这么写:

    for(int i = left - 1; i <= right - 1; ++i) {
                res += sums[i];
                if(res >= mod) {
                    res -= mod;
                }
            }
            return res;
    

    5446. 三次操作后最大值与最小值的最小差

    这是一道智商题,如果能发现规律就很好做。

    要求出三次操作后最大值和最小值的差的最小值。
    我们就要让三次操作之后最大值和最小值尽可能接近,也就是尽可能让最大值小一点,最小值大一点。

    所以三次操作都是对数组的最大值(或者前两个或前三个最大值)或者对数组的最小值(或者前两个或前三个最小值)进行操作。
    这一点很好理解,如果对一个中间元素进行操作,则对于最大值和最小值的差并无影响。

    由于只有三次操作,且都是对于最小或最大的几个数进行操作,所以一共只有四种情况:

    1. 三次操作将最小的三个数改为第四小的数。 修改最小的三个数,修改后的值一定是改为第四小的数,
      因为这样才会让最大值和最小值(原来的第四小的值)的差最小。

    2. 两次操作将最小的两个数改为第三小的数,一次操作将最大的数改为第二小的数。 这也是一种缩小最大值和最小值的差的方法。

    3. 一次操作将最小的数改为第二小的数,两次操作将最大的两个数改为第三大的数。

    4. 三次操作将最大的三个数改为第四大的数。

    最终的结果,就是上述四种情况的最小值!

    注意,如果一共只有四个数或者更少,直接返回0即可,因为肯定可以让最大值和最小值的差相等。

    代码如下:

    class Solution {
    public:
        int minDifference(vector<int>& nums) {
            int size = nums.size();
            if(size <= 4) {
                return 0;
            }
            sort(nums.begin(), nums.end());
            int res = INT_MAX;
            for(int i = 0; i <= 3; ++i) {            //枚举四种情况
                res = min(res, nums[size - 4 + i] - nums[i]);    //更新修改后的最大值和最小值的差的最小值
            }
            return res;
        }
    };
    

    5447. 石子游戏 IV


    题意是给定石头数量,alice和bob每次都只能拿平方数个石头,如果某次操作某人拿走了所有的石头,则另一个玩家输。
    典型DP问题: 用一个数组dp表示石头数和alice是否稳赢的关系,比如dp[i]表示有i个石头时alice是否稳赢(是返回true,否返回false)。

    1. 显然,当i是0时,alice是输得,因为题目说了没有石头时,无法操作的玩家输。

    2. 当i是一个平方数时,aclie稳赢,因为只要alice把石头全部拿走,bob就输了。

    3. 对于其他情况,就需要判断从当前石头里拿走所有可以拿的平方数个石头的方案里,对方是否一定输。
      只要有一种拿平方数的方案里,对方一定输,就可以返回true,否则,遍历完所有可以拿的方案对方都不输,就返回false。

    代码如下:

    class Solution {
    public:
        bool winnerSquareGame(int n) {
            vector<bool> dp(n + 1);                  //dp[i]表示有i个石头时alice是否稳赢
            for(int i = 0; i <= n; ++i) {            //从小到大递推计算dp数组
                for(int j = 1; j * j + i <= n; ++j) {     //枚举可以拿的石头数,j * j是当前拿的石头数
                    if(dp[i] == false) {                //如果只有i个石头时必输
                        dp[j * j + i] = true;           //那么有j * j + i个石头的时候就必赢,因为alice可以拿走j * j个石头,这样bob就必输
                    }
                }
            }
            return dp[n];         //返回有n个石头时alice是否赢
        }
    };
    
  • 相关阅读:
    cuda实践2
    对旋转矩阵R做(行)初等变换会发生什么?
    关于最短路径问题:Dijkstra与Floyd算法
    深入理解JavaScript系列
    jquery的$.extend和$.fn.extend作用及区别
    知道WCF的地址用工厂通道方式快速调用WCF
    WCF大数据量传输解决方案
    系统上线后WCF服务最近经常死掉的原因分析总结
    Microsoft Web Application Stress Tool 使用
    标识符解析在闭包中理解
  • 原文地址:https://www.cnblogs.com/linrj/p/13291568.html
Copyright © 2020-2023  润新知