LeetCode 刷题 -- Day 3

今天题目

题目难度备注
242. 有效的字母异位词 - 力扣(LeetCode)简单♥♥
349. 两个数组的交集 - 力扣(LeetCode)简单
202. 快乐数 - 力扣(LeetCode)简单此题考查分析能力
454. 四数相加 II - 力扣(LeetCode)中等此题考查分析能力
383. 赎金信 - 力扣(LeetCode)简单掌握这个思想
15. 三数之和 - 力扣(LeetCode)中等动态规划题
18. 四数之和 - 力扣(LeetCode)中等动态规划题

题目:242. 有效的字母异位词

242. 有效的字母异位词 - 力扣(LeetCode)

一、源代码

bool isAnagram(string s, string t) {
        if (s.size() != t.size()) {
            return false;
        } 
        vector<int> hasNum(30,0);
        for (int i = 0; i < s.size(); ++i) {   //存入s 用++
            hasNum[s[i] - 'a']++;
        }
        for (int i = 0; i < t.size(); ++i) {   //判断与 t 是否相等,则--
            hasNum[t[i] - 'a']--;
            if (hasNum[t[i] - 'a'] < 0) {   //不相等必会 < 0
                return false;
            }
        }
        return true;
    }

二、代码思路

一开始想着字符要和出现次数绑定,想着用 map<char,int>,但这样得先用一个for循环存入,在存入中还得不断用find()方法判断map中是否已经存在该字符,然后还得判等…太麻烦

其实要实现字符和出现次数的绑定,最好用 int 数组,map更适合用在字符串和数字的绑定。因为单个字符也可以看作数字嘛,两者可以转换的:如字符 ’5’ - ‘0‘ = 数字 5,字符 ‘b’ - ‘a’ = 数字 1

那要实现两个字符串判断各字符出现次数是否相等就简单了,先定义一个int 数组hasNum,初始化各值为 0。再遍历字符串还是,直接用 hasNum[s[i] - ‘a’]++ 就可以得到字符和出现次数的绑定了,hasNum的下标可指代 字符,其值为出现次数。 存入 s 用 +,那判断 s 与 t 每个字符出现的次数是否相同当然用 -,若完全相同遍历 t 后 hasNum数组的值为全0

三、优化思路

其实若 s 和 t 中每个字符出现的次数都相同,那 s 与 t 的长度必相等,用sort排序后,sort(s) 必等于 sort(t),利用性质解题更分别。

四、优化代码

bool isAnagram(string s, string t) {
        if (s.size() != t.size()) {
            return false;
        }
        sort(s.begin(), s.end());
        sort(t.begin(), t.end());
        return s == t;
    }

题目:349. 两个数组的交集

349. 两个数组的交集 - 力扣(LeetCode)

一、源代码

vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        vector<int> ans;
        bool hasVis[1000] = {0};   //用hasVis判断是否已经存在
        sort(nums1.begin(),nums1.end());
        sort(nums2.begin(),nums2.end());
        int i = 0, j = 0;
        while (i < nums1.size() && j < nums2.size()){  //双指针法
            if (nums1[i] > nums2[j])  ++j;
            else if (nums1[i] < nums2[j]) ++i;
            else {
                if (hasVis[nums1[i]] == 0) {   //hasVis[nums1[i]] == 1,表示已存在,无需存入
                    ans.push_back(nums1[i]);
                    hasVis[nums1[i]] = 1;
                }
                ++i; ++j;
            }
        }
        return ans;
    }

二、优化思路

因为使用 sort() 后,nums1 和nums2 中元素都从小到大排列,所以用双指针遍历时 push_back到 ans 的数也是按从小到大的顺序,即下一个要 push_back 到 ans 的数,要么比ans中所有的数都大,要么等于ans.back() 。那么就无需定义 hasVis 数组来判断 nums1[i]] 是否已经存在。只要判断 nums1[i] 是否等于 ans.back() 就行了,等于就不存入。

if (hasVis[nums1[i]] == 0)  ==>  if (nums[i] != ans.back())

题目:202. 快乐数

202. 快乐数 - 力扣(LeetCode)

没有思路

一、答案思路

任何一个数,循环把它各个位置平方相加,可能出现的情况无非就三种:

① 最终会得到 1;

② 最终会进入循环;

③ 值越来越大,最终接近无穷

分析 ③,什么情况值会越来越大?假如n为三位数,则n <= 999,999的各个位置平方和 == 243 < 999。所以三位数的 n 其平方和再大也不会大于 999 (三位数中999的平方和最大,所以三位数中最大的平方和仍然为三位数,仍然小于999,再计算平方和肯定任小于999)

所以值不会越来越大的,那就只有 ①、② 两种情况,那这题就简单了,循环计算各个位置平方和,若得到的值出现过,则说明进入循环了,break;若得1 则为快乐数

二、答案代码

private:
    int getNext (int n) {
        int sum = 0;
        while (n > 0) {
            int d = n % 10;
            n = n / 10;
            sum += d * d;
        }
        return sum;
    }
public:
    bool isHappy(int n) {
        unordered_set<int> nums;
        while (n != 1 && !nums.count(n)) {
            nums.insert(n);
            n = getNext(n);
        }
       return n == 1;
    }

三、优化思路

既然要么 ① 最终会得到 1,要么② 最终会进入循环;那题目不就变成了类似判断链表是否有环了吗?那当然是 fast 和 slow 双指针法,和前几天的题目很像,所以本体出现的情况可以变成以下:

如果 n 是一个快乐数,即没有循环,那么快跑者最终会比慢跑者先到达数字 1。

如果 n 不是一个快乐的数字,那么最终快跑者和慢跑者将在同一个数字上相遇。

四、优化代码

 private:
    int getNext (int n) {
        int sum = 0;
        while (n > 0) {
            int d = n % 10;
            n = n / 10;
            sum += d * d;
        }
        return sum;
    }
public:
    bool isHappy(int n) {
        int slowRunner = n;
        int fastRunner = getNext(n);
        while (fastRunner != 1 && slowRunner != fastRunner) {
            slowRunner = getNext(slowRunner);
            fastRunner = getNext(getNext(fastRunner));
        }
        return fastRunner == 1;
    }

题目:454. 四数相加 II

454. 四数相加 II - 力扣(LeetCode)

一、源代码

private:
    int cnt = 0;
    void DFS (vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, 
              vector<int>& nums4, int i, int j, int k, int h) {   //dfs实现递归探索
        int n = nums1.size();
        if (i>=n || j>=n || k>=n || h>=n) return;
        if (nums1[i] + nums2[j] + nums3[k] + nums4[h] == 0) {  //找到符合条件的则++cnt
            ++cnt;
            return;
        }
        DFS(nums1, nums2, nums3, nums4, i+1, j, k, h);
        DFS(nums1, nums2, nums3, nums4, i, j+1, k, h);
        DFS(nums1, nums2, nums3, nums4, i, j, k+1, h);
        DFS(nums1, nums2, nums3, nums4, i, j, k, h+1);
    }
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        DFS(nums1, nums2, nums3, nums4, 0, 0, 0, 0);
        return cnt;
}

二、代码思路

用DFS实现递归探索,一次改变一个量,这样能遍历所有情况了,遇到符合情况的则++cnt

三、错误原因

① 实际输出与答案不符

因为遍历过程中,会有一个元组多次出现,并且每次都能++cnt 的情况,如对以下输入

image-20240328110413389

遍历过程中会出现 元组为0100 -> 1100 时 cnt++,元组2为1000 ->1100时,cnt还++,这样cnt数就会大于答案了

也就是说会出现一个元组占两个坑计数两次的情况,那遇到这种情况怎么处理呢?当然是用 set,在set中一个元素只能有一个坑,所以全部导入set中,再返回 set 的 size 试试

四、修改代码(答案错误)

private:
    int p = 200;   //n最多为200,为了把元组<i,j,k,h> 转换为数,所以定义p为200
    set<long long> ans;
    void DFS (vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4, int i, int j, int k, int h){
        int n = nums1.size();
        if (i>=n || j>=n || k>=n || h>=n) return;
        if (nums1[i] + nums2[j] + nums3[k] + nums4[h] == 0) {
            long long sum = h + k*p + j*p*p + i*p*p*p;  //为了把元组<i,j,k,h> 转换为数
            ans.insert(sum);  //存入set,避免一个元组占多个坑的情况
            return;
        }
        DFS(nums1, nums2, nums3, nums4, i+1, j, k, h);
        DFS(nums1, nums2, nums3, nums4, i, j+1, k, h);
        DFS(nums1, nums2, nums3, nums4, i, j, k+1, h);
        DFS(nums1, nums2, nums3, nums4, i, j, k, h+1);
    }
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        DFS(nums1, nums2, nums3, nums4, 0, 0, 0, 0);
        return ans.size();
    }

本以为这个代码虽然很烂,时间复杂度很高。好像也能做出来题目,先求做出再求优化。

结果测试用例对了,一提交还是错的。看了题解,这个题目我是真不喜欢。

五、题解思路

image-20240328113249017

分成两组,一组存入一组拿来对比。这能理解,比较类的题目大都要用这个思路

注意这个分治思想,为什么得出要两两一组,这个分析过程很重要!要学会从时间复杂角度分析

六、题解代码

class Solution {
public:
    int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
        unordered_map<int, int> countAB;
        for (int u: A) {
            for (int v: B) {
                ++countAB[u + v];
            }
        }
        int ans = 0;
        for (int u: C) {
            for (int v: D) {
                if (countAB.count(-u - v)) {
                    ans += countAB[-u - v];
                }
            }
        }
        return ans;
    }
};

题目:383. 赎金信

383. 赎金信 - 力扣(LeetCode)

一、源代码

    bool canConstruct(string ransomNote, string magazine) {
        bool falg = true;
        for (int i = 0; i < ransomNote.size(); i++) {   //遍历ransomNote
            int n = magazine.find(ransomNote[i])  //在magazine中查找ransomNote的元素,find返回下标
            if (n!= magazine.npos) {  //如果找到则magazine中下标元素
                magazine.erase(n,1);
            }else{
                falg = false;
                break;
            }
        }
        return falg;
    }

二、优化思路

其实这也是两个string 相比较,也可以用一组存入,一组对比的思想来解题。时间复杂度会小

三、优化代码

    bool canConstruct(string ransomNote, string magazine) {
        if (ransomNote.size() > magazine.size()) {
            return false;
        }
        vector<int> cnt(26);
        for (auto & c : magazine) {
            cnt[c - 'a']++;
        }
        for (auto & c : ransomNote) {
            cnt[c - 'a']--;
            if (cnt[c - 'a'] < 0) {
                return false;
            }
        }
        return true;


题目:15. 三数之和

15. 三数之和 - 力扣(LeetCode)

一、源代码

    vector<vector<int>> threeSum(vector<int>& nums) {
        unordered_map<int,int> numIndex;
        vector<int> ans;
        vector<vector<int> > ansTotal;
        for(int i = 0; i < nums.size(); i++) {
            numIndex[nums[i]] = i;
        }
        for(int i = 1; i < nums.size(); i++) {
            for(int j = 1; j < nums.size(); j++) {
                unordered_map<int,int>::iterator it = numIndex.find(-nums[i]-nums[j]);
                if (it != numIndex.end() && it->second != i && it->second != j && i != j) {
                    ans.push_back(it->second); ans.push_back(i); ans.push_back(j);
                    ansTotal.push_back(ans);
                }
            }
        }
        return ansTotal;
    }

二、错误原因

本想继续用分两组存一组,另一组拿来比较的方法。用 map 存入数组值和下标,再在map中查找值。但是这会出现两个问题:

① map 不能出现两个相同的键,nums中却有重复的值,这就不能用map;

② 答案要求不重复,用map 得去重,很麻烦

所以这题其实不是用哈希表法,而是用动态规划的知识,动态规划以后再来探讨吧,先学基础的

三、题解代码

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n = nums.size();
        sort(nums.begin(), nums.end());
        vector<vector<int>> ans;
        // 枚举 a
        for (int first = 0; first < n; ++first) {
            // 需要和上一次枚举的数不相同
            if (first > 0 && nums[first] == nums[first - 1]) {
                continue;
            }
            // c 对应的指针初始指向数组的最右端
            int third = n - 1;
            int target = -nums[first];
            // 枚举 b
            for (int second = first + 1; second < n; ++second) {
                // 需要和上一次枚举的数不相同
                if (second > first + 1 && nums[second] == nums[second - 1]) {
                    continue;
                }
                // 需要保证 b 的指针在 c 的指针的左侧
                while (second < third && nums[second] + nums[third] > target) {
                    --third;
                }
                // 如果指针重合,随着 b 后续的增加
                // 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
                if (second == third) {
                    break;
                }
                if (nums[second] + nums[third] == target) {
                    ans.push_back({nums[first], nums[second], nums[third]});
                }
            }
        }
        return ans;
    }
};


题目:18. 四数之和

18. 四数之和 - 力扣(LeetCode)

同样动态规划的题,暂时不考虑,以后再来做

  • 40
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值