首页 > 编程语言 >代码随想录算法训练营第七天 | 454.四数相加II 383. 赎金信 15. 三数之和 18. 四数之和

代码随想录算法训练营第七天 | 454.四数相加II 383. 赎金信 15. 三数之和 18. 四数之和

时间:2024-03-13 16:58:24浏览次数:25  
标签:vector 四数 15 nums int 随想录 ++ right left

day7 记录代码随想录

第一题 力扣454.四数相加II 

给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。

为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -2^28 到 2^28 - 1 之间,最终结果不会超过 2^31 - 1 。

例如:

输入:

  • A = [ 1, 2]
  • B = [-2,-1]
  • C = [-1, 2]
  • D = [ 0, 2]

输出:

2

解释:

两个元组如下:

  1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
  2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0

题目链接:力扣题目链接

解题思路:

这道题,我首先想到的是将四个数组分解为两个数组,这样就简化称为了两数相加的题。

因为只需要返回元祖数量,那么将值存入就可以了,所以我考虑使用multiset来做。

首先将nums1[i] + nums2[j] 遍历存入 sum1中

然后遍历查找sum1中符合 0 - nums[3] - num[4] 的元素数量,就得到了元祖数量。

代码如下:

//一个错误做法
class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        multiset<int> sum1;
        int count = 0;
        int n = nums1.size();
        for(int i = 0 ; i < n ; i++ ) {
            for( int j = 0 ; j < n ; j++ ) {
                sum1.insert(nums1[i]+nums2[j]);
            }
        }
        for(int i = 0 ; i < n ; i++ ) {
            for( int j = 0 ; j < n ; j++ ) {
                if( sum1.find(-nums3[i]-nums4[j]) != sum1.end() )
                    count++;
            }
        }
        return count;
    }
};

很显然,结果出错了。原因在哪里呢?

在于find这一步,由于sum1.find()只能找到一个符合的变量,所以就算num1中有多个相同的符合条件的值,count也只会加一次。

如何修改呢?

第一种方法是保留使用multiset,在find这里采用for循环遍历sum1赋值对比。

//超时了o(╥﹏╥)o
class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        multiset<int> sum1;
        int count = 0;
        int n = nums1.size();
        for(int i = 0 ; i < n ; i++ ) {
            for( int j = 0 ; j < n ; j++ ) {
                sum1.insert(nums1[i]+nums2[j]);
            }
        }
        for(int i = 0 ; i < n ; i++ ) {
            for( int j = 0 ; j < n ; j++ ) {
                for(auto& sum:sum1) {
                    if( sum == ( -nums3[i] - nums4[j] ) )
                        count++;
                }

            }
        }
        return count;
    }
};

很遗憾,三重循环超时了,不得不改用其他方法。

第二种方法是放弃set采用map映射。

对于本题来说,只需要给出最后的数量,那么map映射的key和value分别可以设置成“数值”和“数量”,这样一来,只需要查找符合条件的“数值”的map.value就可以了。

代码如下:

//使用map映射
class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int,int> mymap;
        int n = nums1.size();
        int count = 0;
        for(int i = 0 ; i < n ; i++ ) {
            for ( int j = 0 ; j < n ; j++) {
                mymap[nums1[i] + nums2[j]]++;   //新语法:元素对应的值加一
            }
        }
        for(int a:nums3) {
            for(int b:nums4) {
                if ( mymap.find(-a-b) != mymap.end() )
//                    count += mymap.find(-a-b)->second;
                   count += mymap[-a-b];
            }
        }
        return count;
    }
};

这里有一个for循环的新用法:for(int a:nums3),是将num3中的值依次赋值给a做循环操作。

时间复杂度为O(n^2)

第二题 力扣383. 赎金信

给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。

(题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思。杂志字符串中的每个字符只能在赎金信字符串中使用一次。)

注意:

你可以假设两个字符串均只含有小写字母。

canConstruct("a", "b") -> false
canConstruct("aa", "ab") -> false
canConstruct("aa", "aab") -> true

题目链接:力扣题目链接

解题思路:

本题关键词有:对比,重复,使用哈希表来做很简单

选择数组来实现哈希表,可以完整地将magazine存入,并且由于只有小写字母所以数组大小只需要26即可。数据跨度小,且可重复很符合数组的使用条件。

代码如下:

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int mag[26] = {0};
        for(int i = 0 ; i < magazine.size() ; i++ ) {
            mag[magazine[i] - 'a']++;
        }
        for ( int i = 0 ; i < ransomNote.size() ; i++) {
            mag[ransomNote[i] - 'a']--;
            if( mag[ransomNote[i] - 'a'] < 0 ) {
                return 0;
            }
        }
        return true;
    }
};

第三题 力扣 15.三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意: 答案中不可以包含重复的三元组。

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]

题目链接:力扣题目链接

解题思路:

看到三数之和,很容易想到昨天的两数之和,两数之和采用了map映射来做,将数组的下标与数值存在map中,然后遍历数组去map中找值。

那么对于此题,是不是也可以考虑使用map呢?

构思一下,固定一个数,然后用两重for循环遍历数组去找map中的值。

看起来是可以的,但是有个问题时题目中要求不能包含重复的三元组,即[-1,-1,2]只能用一次,但是在上面的构思中,去重是个十分棘手的操作。

放弃map,尝试使用别的办法。

此题中并不考虑a,b,c的顺序,那么是否可以提前给nums排序一下。

对于有序数组,固定一个值查找另外一个值,很容易想到双指针法。

使用for循环,遍历固定 i ,再定义两个指针 left 和 right ,在 i<left<right 的范围内移动left和right查找对应的值,就可以实现。

移动查找的循环条件为 left <right

当nums[i] + nums[left] + nums[right] == 0 时,将此时的三个值存入result中,考虑到需要去重,判断一下left与其右边是否相等,相等则跳过,同样的,判断right与其左边是否相同,相等则跳过,然后left右移,right左移。

当nums[i] + nums[left] + nums[right] < 0 时,则说明三个数的值太小,此时将较小的数 left 增加一点(右移)

同样的,当nums[i] + nums[left] + nums[right] > 0 时,则说明三个数的值太大,此时将较大的数 right 减小一点(左移)

对于 i 来说,也需要去重,判断 i > 0 && nums[i] == nums[i - 1],符合直接跳过此次循环

这里需要注意的是不能使用nums[i] == nums[i + 1] 来判断,因为这样的话会将left挤出去,导致缺失值。

代码如下:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        int left;
        int right;
        for (int i = 0; i < nums.size(); i++) {
            left = i + 1;
            right = nums.size() - 1;
            if (i > 0 && nums[i] == nums[i - 1])
                continue;
            while (left < right) {
                if (nums[i] + nums[left] + nums[right] == 0) {
                    result.push_back({nums[i], nums[left], nums[right]});
                    while (left < right && nums[right] == nums[right - 1])
                        right--;
                    while (left < right && nums[left] == nums[left + 1])
                        left++;                    
                    left++;
                    right--;
                } 
                else if (nums[i] + nums[left] + nums[right] < 0)
                    left++;
                else
                    right--;
            }
        }
        return result;
    }
};

时间复杂度为O(n^2)

双指针法将原本时间复杂度为O(n^3)的解法优化为了O(n^2)的解法。

第四题 18. 四数之和

题意:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:

答案中不可以包含重复的四元组。

示例: 给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。 满足要求的四元组集合为: [ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]

提示:

  • 1 <= nums.length <= 200
  • -10^9 <= nums[i] <= 10^9
  • -10^9 <= target <= 10^9

题目链接:力扣题目链接

解题思路:

四数之和与三数之和的解题思路基本一致,也是采用双指针法。

不过这里需要用双重for循环来固定两个数 i , j ,然后采用对撞指针来找值。

这里需要注意的有两个点:

1)题目给的值大小为-10^9 <= nums[i] <= 10^9,而int的大小为2^31-1,是负的21亿多到正的21亿多,如果三个足够大的数相加就会导致数值越界,此时需要使用  (long) 来进行强制类型转换

2)在循环过程中,有些条件是肯定不符合题意的,比如nums[i] > target && nums[i] > 0,对于这部分,直接跳过就可以了,这个操作叫做“剪枝”,能够增加运行效率。

代码如下:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        int left;
        int right;
        for (int i = 0; i < nums.size(); i++) {
            if(nums[i] > target && nums[i] > 0) //剪枝
                break;
            if(i > 0 && nums[i] == nums[i-1])
                continue;
            for (int j = i + 1; j < nums.size(); j++) {
                if( nums[i] + nums[j] > target && nums[i] + nums[j] > 0)    //二重剪枝
                    break;
                if(j > i + 1 && nums[j] == nums[j-1])
                    continue;
                left = j + 1;
                right = nums.size() - 1;
                while( left < right ) {
                    if ((long) nums[i] + nums[j] + nums[left] + nums[right] == target) {
                        result.push_back({nums[i], nums[j], nums[left], nums[right]});
                        while (left < right && nums[right] == nums[right-1])
                            right--;
                        while (left < right && nums[left] == nums[left+1])
                            left++;
                        left++;
                        right--;
                    }
                    else if((long) nums[i] + nums[j] + nums[left] + nums[right] < target)
                        left++;
                    else
                        right--;

                }
            }
        }
        return result;
    }
};

时间复杂度为O(n^3)

双指针法将原本时间复杂度为O(n^4)的解法优化为了O(n^3)的解法。

总结:数组、set、map是实现哈希表的三种方法,分别具有其适用条件和范围,也各有优缺点,同时还有multi 和 unordered 分别用来处理重复和无序不重复的元素,需要结合不同的功能来选择不同的方法实现。双指针算法是一种强大的算法,对于有序的数组来说十分适用,并且相关操作(如去重)的代码也很简洁,且效率很高,双指针法将原本时间复杂度为O(n^3)的解法优化为了O(n^2)的解法。

部分图文出自代码随想录。

标签:vector,四数,15,nums,int,随想录,++,right,left
From: https://blog.csdn.net/zhang1282882326/article/details/136657547

相关文章

  • 【PW2153A电源管理芯片】100V降压,稳定输出,短路保护,电子工程师必备
    在电子设备日新月异的今天,电源管理芯片作为电子设备的“心脏”,其性能的稳定性和高效性对于设备的整体运行至关重要。PW2153A作为一款宽电压范围降压型DC-DC电源管理芯片,凭借其出色的性能和丰富的功能,在电源管理领域大放异彩。首先,我们来深入了解一下PW2153A的描述。这款芯片内部......
  • 代码随想录算法训练营第四十五天| ● 70. 爬楼梯 (进阶) ● 322. 零钱兑换 ● 279.完全
    爬楼梯 (进阶)题目链接:57.爬楼梯(第八期模拟笔试)(kamacoder.com)思路:笑嘻了,直接给默写出来了。#include<bits/stdc++.h>usingnamespacestd;intmain(){intn,m;cin>>n>>m;vector<int>dp(n+1);dp[0]=1;for(inti=1;i<=n;i++){for(in......
  • 洛谷题单指南-二叉树-P4715 【深基16.例1】淘汰赛
    原题链接:https://www.luogu.com.cn/problem/P4715题意解读:计算亚军得主,注意能力值最高的肯定是冠军,但能力值第二高的不一定是亚军,因为有可能中途就遭遇冠军。解题思路:根据题意,两两比赛,一轮后再按顺序两两比赛,形如一棵二叉树,但解题其实用不到二叉树的数据结构可以看出,最后参与......
  • 第15届蓝桥杯青少组STEMA考试C++中高级真题试卷(2024年3月)编程题部分
    编程题第6题   问答题编程实现:寒假期间小明需要做完n张试卷,但他每天最多能做完m张,请计算出小明做完n张试卷最少需要多少天?输入描述一行输入两个整数n和m(1≤n≤100,1≤m≤10),分别表示要完成的试卷张数,及每天最多能做完的试卷张数,整数之间以一个空格隔开输出描述输出......
  • 力扣面试经典150 —— 16-20题
    力扣面试经典150题在VScode中安装LeetCode插件即可使用VScode刷题,安装DebugLeetCode插件可以免费debug本文使用python语言解题,文中“数组”通常指python列表;文中“指针”通常指python列表索引文章目录16.[困难]接雨水16.1解法1:按行计算16.2解......
  • 今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 3月13日,星期三
    每天一分钟,知晓天下事!2024年3月13日星期三农历二月初四1、四部门:中小学每天安排30分钟大课间体育活动,缓解视力疲劳。2、我国视障人士首次使用无障碍格式文件完成结婚登记。3、数据显示:城乡居民医保的参保人数从2019年开始逐渐下降。4、福建:节假日期间,鼓励......
  • 错误:在 /tmp/easy_install-rad8_t5b/PyQt5-5.14.0.tar.gz #15 中找不到安装脚本
    thePyQt55.14.0isbrokenbecausecan'tnotinstallonresppi3.youcantoinstallaversionofPyQt5thatworkingfineonresp.followthesteps:PyQt55.14.0已损坏,因为无法无法安装在resppi3上。您可以安装一个在resp上运行良好的PyQt5版本,请按照以下......
  • 代码随想录算法训练营day21 | leetcode 530. 二叉搜索树的最小绝对差、501. 二叉搜索
    目录题目链接:530.二叉搜索树的最小绝对差-简单题目链接:501.二叉搜索树中的众数-简单题目链接:236.二叉树的最近公共祖先-中等题目链接:530.二叉搜索树的最小绝对差-简单题目描述:给你一个二叉搜索树的根节点root,返回树中任意两不同节点值之间的最小差值。差值是一个正数,......
  • Day7代码随想录(1刷) 哈希表
    目录454.四数相加II 383.赎金信15.三数之和 18.四数之和 454.四数相加II给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i,j,k,l) 能满足:0<=i,j,k,l<nnums1[i]+nums2[j]+nums3[k]+nums4[l......
  • 代码随想录刷题笔记
    代码随想录刷题数组二分查找思路:有序数组,数组中无重复元素移除元素思路:数组在内存地址中是连续的,不能单独删除某个数组中的某个元素,只能覆盖快慢指针法,用于数组、链表、字符串等的操作双向指针法,更优,移动更少的元素注意:补充快慢指针法的代码交换时候......