算法学习 | day6/60 四数相加II/赎金信/三数之和/四数之和

        今天依然是打卡哈希表的题目,在上一天的基础上加了一些难度,我发现自己之前每次投入的时间有点过多,考虑到后面时间有限,如果一个题目没有思路或者很久没有做出来,那我就直接看答案,然后按照答案的思路求解,顺带在题目上进行标记,方便自己以后进行回顾。

一、题目打卡

        1.1 四数相加ii(借助答案思路)

        题目链接:. - 力扣(LeetCode)

        这个题目一开始想到了暴力的解法,但是这样嵌套四层循环肯定会时间超时,考虑到这次是哈希表专题,但是没有想到合适利用哈希表的方法,然后看了答案做出的解法如下:

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int,int> umap;
        for(auto &i : nums1){
            for(auto &j : nums2){
                umap[i + j]++;
            }
        }
        int res = 0;
        for(auto &i : nums3){
            for(auto &j : nums4){
                if(umap.find(0-(i+j)) != umap.end()){
                    // res++;
                    res += umap[-i-j];
                }
            }
        }
        return res;
    }
};

        想法:

        题目本质上利用的是加法的结合律,然后分组进行计算,这样的话就把暴力法 O(n^4) 的复杂度降低到了O(n^2),是一种很不一样的思路,有一个小的错误是后面在统计返回值的时候,由于之前统计的 key-val 的 val 是在前两组中和等于特定 key 的数目总和,所以这里不能是简单的 res++ 。

        1.2 赎金信

        leetcode 题目链接:. - 力扣(LeetCode)

        和前面的同构词的思路类似:        

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        unordered_map<char, int> umap;
        for(auto &it : magazine){
            umap[it]++;
        }
        for(auto &it : ransomNote){
            if(umap.find(it) == umap.end() || umap[it] <= 0) return false;
            if(umap.find(it) != umap.end()) umap[it]--;
        }
        return true;
    }
};

        想法:

        题目本身没有太大难度,注意的是在第二次循环判断的时候,有一个题目的条件是 magazine 中的字符在 randomNote 中只能出现一次,所以如果进行遍历的时候,出现过的字符在 umap 中统计次数需要减 1。

        进一步地,如果想要继续优化,那么就可以使用数组的思路进行映射,这样省去了哈希表 umap 所构造哈希函数使用的额外开销,参考答案的解法:

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int record[26] = {0};
        // 因为只能使用一次,所以如果 ransomNode 的空间更大,肯定是不成立的
        if (ransomNote.size() > magazine.size()) {
            return false;
        }
        for (int i = 0; i < magazine.length(); i++) {
            // 通过record数据记录 magazine里各个字符出现次数
            record[magazine[i]-'a'] ++;
        }
        for (int j = 0; j < ransomNote.length(); j++) {
            // 遍历ransomNote,在record里对应的字符个数做--操作
            record[ransomNote[j]-'a']--;
            // 如果小于零说明ransomNote里出现的字符,magazine没有
            if(record[ransomNote[j]-'a'] < 0) {
                return false;
            }
        }
        return true;
    }
};

        1.3 三数之和(借助答案思路)

        题目链接:. - 力扣(LeetCode)

class Solution {
private:
    bool checkRepeat(vector<vector<int>> &res, vector<int> &tmp){
        for(auto &i : res){
            unordered_map<int,int> _tmp;
            unordered_map<int,int> __tmp;
            for(auto& j : i){
                _tmp[j]++;
            }
            for(auto& it : tmp){
                __tmp[it]++;
            }
            if(_tmp == __tmp) return false;
        }
        return true;
    }
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        unordered_map<int,int> umap;
        for(auto& it: nums){
            umap[it]++;
        }
        vector<int> tmp;
        vector<vector<int>> res;
        // for(auto& it : umap){
        //     cout << it.first << " " << it.second << endl;
        // }
        for(int i = 0 ; i < nums.size() ; i++){
            unordered_map<int,int> _umap(umap);
            for(int j = i + 1 ; j < nums.size() ; j++){
                // for(auto& it : umap){
                //     cout << it.first << " " << "-" << " " << it.second << endl;
                // }
                tmp.push_back(nums[i]);
                tmp.push_back(nums[j]);
                // 这里的 i 是临时减的
                umap[nums[i]]--;
                umap[nums[j]]--;
                // for(auto& it : umap){
                //     cout << it.first << " " << "--" << " " << it.second << endl;
                // }
                if(umap.find(-nums[i]-nums[j]) != umap.end() && umap[-nums[i]-nums[j]] >= 1){
                    tmp.push_back(-nums[i]-nums[j]);
                    // res.push_back(tmp);
                    if(checkRepeat(res,tmp)) res.push_back(tmp);
                }
                umap[nums[i]]++;
                // for(auto& it : umap){
                //     cout << it.first << " " << "---" << " " << it.second << endl;
                // }
                tmp.clear();
            }
            // 这里必须要拷贝一份,不然在下一轮j的时候,会导致map对应的数字没有复原
            umap = _umap;
            umap[nums[i]]--;
            // for(auto& it : umap){
            //     cout << it.first << " ---- " << it.second << endl;
            // }
        }
        return res;
    }
};

        想法:

        这个题目原本想借助哈希表将 O(n^3) 的复杂度降低到 O(n^2),但是在做的过程中发现这个题目没有那么简单,首先是在我的方法下,对映射的 umap 中的键值会频繁改变,这样就需要设计这个键值改变的逻辑,而且还会涉及到拷贝,导致了空间和时间的浪费,最关键的是最终的结果不能包含重复的元组,基于这一点,我设计了一个检查重复的函数,但是这样在数据量很大的时候会超时。

        然后看了参考答案的思路,重新使用双指针对题目进行了设计:

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;
            if(i > 0 && nums[i] == nums[i-1]) continue; // 对 i 进行去重
            int left = i + 1;
            int right = nums.size() - 1;
            while(left < right){
                // while(nums[right] == nums[right - 1] && left < right) right--; // 去重c
                // while(nums[left] == nums[left + 1] && left < right) left++; // 去重b
                if(left < right && nums[i] + nums[left] + nums[right] > 0) right--;
                else if(left < right && nums[i] + nums[left] + nums[right] < 0 ) left++;
                else{
                    while(nums[right] == nums[right - 1] && left < right) right--; // 去重c
                    while(nums[left] == nums[left + 1] && left < right) left++; // 去重b
                    res.push_back({nums[i],nums[left],nums[right]});
                    left++;
                    right--;
                }
            }
        }
        return res;
    }
};

        这里答案的思路其实就是在排序了以后,重复的元素会在一起,这样每轮进行取值的时候,想办法只进行一次取值,这样的话有一个容易犯的小错误就是对于第二个数字 b ,和第三个数值 c 去重时候,应该先添加进了结果了以后再进行去重,否则类似 [0,1,1] 这种,left 和 right 在一开始去重了以后,都会指向索引为 1 的位置,并且最后还会加入到 res 中(在 else 的条件中,对于left >= right 的条件没有进行限制,只是默认了 a + b + c == 0 会进入这个 else 执行语句)。

        1.4 四数之和

        这个题目和三数之和的思路差不多,但是有更多的坑,自己基本通过写出了大部分的案例,最后出现的一些问题以及解决方法:

        (1)一开始忘记处理了第一个数字和第二个数字的重复,并且我最后才发现我处理的方式和答案的有一些不一样,我是后续处理的,那个在一开始也出现了很多问题,是处理的思路不够恰当导致了越界。

        (2)这个案例里面存在越界的情况,处理的方法参考的答案,这样也算是学会了一种强制类型转化的方法,也就是在一串计算式的最前面加上 (long) 就可以处理。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        if(nums.size() < 4) return {};
        target = double(target);
        vector<vector<int>> res;
        sort(nums.begin(),nums.end());
        for(int i = 0 ; i < nums.size() ; i++){
            // cout << "after i = " << i << endl;
            if(target > 0 && nums[i] > target) break;
            for(int j = i + 1; j < nums.size() ;j++){
                // cout << "i=" << i << endl;
                if(nums[i] + nums[j] > target && nums[i] > 0) break;
                int l = j + 1;
                int r = nums.size() - 1;
                while(l < r){
                    // cout << i << j << l << r<<endl;
                    if(l < r && (long) nums[i] + nums[j] + nums[l] + nums[r] > target) r--;
                    else if(l < r && (long) nums[i] + nums[j] + nums[l] + nums[r] < target) l++;
                    else{
                        while(l < r && nums[r] == nums[r - 1]) r--;
                        while(l < r && nums[l] == nums[l + 1]) l++;
                        // cout << "l=" << l << " r=" << r << endl;
                        res.push_back({nums[i],nums[j],nums[l],nums[r]});
                        l++;
                        r--;
                    }
                }
                // cout <<"i=" << i << "j=" << j << endl;
                // while(i < nums.size() && nums[j] == nums[i]){
                //     // cout <<"i=" << i << "j=" << j << endl;
                //     j=i;
                //     i++;
                // }
                // cout <<"i=" << i << "j=" << j << endl;
                while(i + 1<nums.size() && nums[i+1] == nums[i]) i++;
                while(j + 1<nums.size() && nums[j+1] == nums[j]) j++;
            }
        }
        return res;
    }
};

  • 24
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是 C 语言实现输入四位数年份和该年元旦星期几,打印全年年历的代码: ```c #include <stdio.h> int main() { int year, day; printf("请输入四位数的年份:"); scanf("%d", &year); printf("请输入该年元旦是星期几(1-7分别代表周一到周日):"); scanf("%d", &day); printf("\n"); // 计算该年2月份的天数 int days_of_feb = 28; if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { days_of_feb = 29; } // 输出年历 printf(" %d年\n", year); printf(" 一月 二月 三月\n"); printf(" 周一 周二 周三 周四 周五 周六 周日 周一 周二 周三 周四 周五 周六 周日 周一 周二 周三 周四 周五 周六 周日\n"); // 输出1月份 int i = 1; while (i <= 31) { if (i == 1) { for (int j = 1; j < day; j++) { printf(" "); } } printf("%4d", i); if ((i + day - 1) % 7 == 0) { printf("\n"); } i++; } printf(" \n"); // 输出2月份 i = 1; while (i <= days_of_feb) { if (i == 1) { for (int j = 1; j <= 7 - (day + 31 - 1) % 7; j++) { printf(" "); } } printf("%4d", i); if ((i + day + 31 - 1) % 7 == 0) { printf("\n"); } i++; } printf(" \n"); // 输出3月份 i = 1; while (i <= 31) { if (i == 1) { for (int j = 1; j <= 7 - (day + 31 + days_of_feb - 1) % 7; j++) { printf(" "); } } printf("%4d", i); if ((i + day + 31 + days_of_feb - 1) % 7 == 0) { printf("\n"); } i++; } printf("\n"); // 输出4~12月份 int days_of_month[12] = {31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; for (int month = 4; month <= 12; month++) { printf(" "); for (int i = 1; i <= 7; i++) { printf(" "); } printf(" "); for (int i = 1; i <= 7; i++) { printf(" "); } printf(" "); for (int i = 1; i <= 7; i++) { printf(" "); } printf("\n"); printf(" "); for (int i = 1; i <= days_of_month[month - 1]; i++) { printf("%4d", i); if ((i + day + days_of_feb + 31 * (month - 1) - 1) % 7 == 0) { printf("\n"); } } printf("\n"); } return 0; } ``` 这个代码可以实现输入指定年份和该年元旦是星期几,然后输出该年的全年年历。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值