454.四数相加II , 383. 赎金信 ,15. 三数之和 , 18. 四数之和

简介: 1. **454. 四数相加II**:通过哈希表优化暴力解法,将问题分为两部分处理,时间复杂度从O(n^4)降至O(n^2)。 2. **383. 赎金信**:利用数组统计字符频率,判断子串是否能由母串构成,时间复杂度为O(n)。 3. **15. 三数之和**:借助排序与双指针技巧,将三重循环优化至双重循环,时间复杂度为O(n^2)。 4. **18. 四数之和**:在三数之和的基础上扩展,增加一层循环并结合剪枝操作,避免重复解,时间复杂度仍为O(n^3)。 这些题目展示了哈希表、双指针和排序等常用算法技巧的应用,适合巩固基础与提升解题能力。

题目:454.四数相加II  

454. 四数相加 II

给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

0 <= i, j, k, l < n

nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

示例 1:

输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]

输出:2

解释:

两个元组如下:

1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0

2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

示例 2:

输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]

输出:1

 提示:

n == nums1.length

n == nums2.length

n == nums3.length

n == nums4.length

1 <= n <= 200

-228 <= nums1[i], nums2[i], nums3[i], nums4[i] <= 228

通过次数204,710提交次数319,002

思考历程与知识点:  

如果day06的那几个哈希题目看了的话,

今天day07应该不会再用O(n^4),也就是四个for去做这道题了吧哈哈哈。

题目要求找A[i] + B[j] + C[k] + D[l] = 0,我们同样可以先求部分和,再通过结果为0来反推查找。

把四个数组分为两队,A和B分为一队,C和D分为一队。

第一步:

      先求出A[ ]和B[ ]数组的每一位成员的和,相当于任意两两配对,复杂度O(n^2), 并且同时记录这个和出现的次数。用一个k数组就可以搞定。

k[ A[ i ]+B[ j ] ]++;//下标也就是为两数的和,里面存的是这两数和出现的次数)

  第二步:

       同上一步一样,再求出C[ ]和D[ ]数组的和。

       因为题目要求(A[ ] + B[ ])+ (C[ ] + D[ ]) = 0

       所以容易推出(A[ ] + B[ ])= 0 -   (C[ ] + D[ ])

       而(A[ ] + B[ ])我们第一步已经存了,就是k数组的下标,只要判断k是否有值就可以确定有没有这个和。

所以我们直接查找就可以了,有的话,加上出现的次数就可以了。

if (umap.find(0 - (c + d)) != umap.end()) {

                   count += umap[0 - (c + d)];

题解:

class Solution {
public:
    int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
        unordered_map<int, int> umap; //key:a+b的数值,value:a+b数值出现的次数
        // 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中
        for (int a : A) {
            for (int b : B) {
                umap[a + b]++;
            }
        }
        int count = 0; // 统计a+b+c+d = 0 出现的次数
        // 在遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就把map中key对应的value也就是出现次数统计出来。
        for (int c : C) {
            for (int d : D) {
                if (umap.find(0 - (c + d)) != umap.end()) {
                    count += umap[0 - (c + d)];
                }
            }
        }
        return count;
    }
};

题目: 383. 赎金信  

383. 赎金信

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:

输入:ransomNote = "a", magazine = "b"

输出:false

示例 2:

输入:ransomNote = "aa", magazine = "ab"

输出:false

示例 3:

输入:ransomNote = "aa", magazine = "aab"

输出:true

提示:

1 <= ransomNote.length, magazine.length <= 105

ransomNote 和 magazine 由小写英文字母组成

思考历程与知识点:  

这题很像day06的242. 有效的字母异位词

这题同样,开26长度的数组存次数。

输入的第一个是字符子串,第二个是母串。

先存下来母串的26个字母出现次数,然后遍历子串,每个子串字母,对应母串数量减1。

如果遍历后,母串字符数量出现负数,则证明字母不够了,没法拼出来。否则就可行。

题解:

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int record[26] = {0};
        //add
        if (ransomNote.size() > magazine.size()) {
            return false;
        }
        for (int i = 0; i < magazine.length(); i++) {
            // 通过recode数据记录 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;
    }
};

题目: 15. 三数之和  

15. 三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

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

示例 1:

输入:nums = [-1,0,1,2,-1,-4]

输出:[[-1,-1,2],[-1,0,1]]

解释:

nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。

nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。

nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。

不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。

注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]

输出:[]

解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = [0,0,0]

输出:[[0,0,0]]

解释:唯一可能的三元组和为 0 。

提示:

3 <= nums.length <= 3000

-105 <= nums[i] <= 105

思考历程与知识点:  

用三个for自然是可以的,时间复杂度是O(N^3)。

这题如果想降时间复杂度是有点困难的,但是 是可以降到O(N^2)的。

题解:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        // 找出a + b + c = 0
        // a = nums[i], b = nums[left], c = nums[right]
        for (int i = 0; i < nums.size(); i++) {
            // 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
            if (nums[i] > 0) {
                return result;
            }
            // 错误去重a方法,将会漏掉-1,-1,2 这种情况
            /*
            if (nums[i] == nums[i + 1]) {
                continue;
            }
            */
            // 正确去重a方法
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i + 1;
            int right = nums.size() - 1;
            while (right > left) {
                // 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组
                /*
                while (right > left && nums[right] == nums[right - 1]) right--;
                while (right > left && nums[left] == nums[left + 1]) left++;
                */
                if (nums[i] + nums[left] + nums[right] > 0) right--;
                else if (nums[i] + nums[left] + nums[right] < 0) left++;
                else {
                    result.push_back(vector<int>{nums[i], nums[left], nums[right]});
                    // 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;
 
                    // 找到答案时,双指针同时收缩
                    right--;
                    left++;
                }
            }
 
        }
        return result;
    }
};

题目: 18. 四数之和  

18. 四数之和

给你一个由 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

你可以按 任意顺序 返回答案 。

示例 1:

输入:nums = [1,0,-1,0,-2,2], target = 0

输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

示例 2:

输入:nums = [2,2,2,2,2], target = 8

输出:[[2,2,2,2]]

提示:

1 <= nums.length <= 200

-109 <= nums[i] <= 109

-109 <= target <= 109

题解:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        for (int k = 0; k < nums.size(); k++) {
            // 剪枝处理
            if (nums[k] > target && nums[k] >= 0) {
              break; // 这里使用break,统一通过最后的return返回
            }
            // 对nums[k]去重
            if (k > 0 && nums[k] == nums[k - 1]) {
                continue;
            }
            for (int i = k + 1; i < nums.size(); i++) {
                // 2级剪枝处理
                if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
                    break;
                }
 
                // 对nums[i]去重
                if (i > k + 1 && nums[i] == nums[i - 1]) {
                    continue;
                }
                int left = i + 1;
                int right = nums.size() - 1;
                while (right > left) {
                    // nums[k] + nums[i] + nums[left] + nums[right] > target 会溢出
                    if ((long) nums[k] + nums[i] + nums[left] + nums[right] > target) {
                        right--;
                    // nums[k] + nums[i] + nums[left] + nums[right] < target 会溢出
                    } else if ((long) nums[k] + nums[i] + nums[left] + nums[right]  < target) {
                        left++;
                    } else {
                        result.push_back(vector<int>{nums[k], nums[i], nums[left], nums[right]});
                        // 对nums[left]和nums[right]去重
                        while (right > left && nums[right] == nums[right - 1]) right--;
                        while (right > left && nums[left] == nums[left + 1]) left++;
 
                        // 找到答案时,双指针同时收缩
                        right--;
                        left++;
                    }
                }
 
            }
        }
        return result;
    }
};

\

目录
打赏
0
55
55
0
59
分享
相关文章
专家博主最新专享福利上线!发文即得积分好礼!
最新专享福利上线!赢取海量积分兑换心仪礼品
668 0
977.有序数组的平方、209.长度最小的子数组、 59.螺旋矩阵II
1. **997. 有序数组的平方**:给定非递减顺序的整数数组,返回每个数字平方后的新数组,要求结果仍为非递减顺序。提供了两种解法——使用`sort()`函数和双指针法,后者利用原数组有序特性优化时间复杂度。 2. **209. 长度最小的子数组**:寻找和大于等于目标值的最短连续子数组长度。采用滑动窗口(毛毛虫比喻)方法,在O(n)时间内完成任务,通过动态调整窗口大小实现高效求解。 3. **59. 螺旋矩阵 II**:生成一个按顺时针螺旋排列的n×n矩阵。通过模拟填充过程,依次向右、下、左、上四个方向扩展边界,直至填满整个矩阵。
704.二分查找、27.移除元素
### 704. 二分查找 题目要求在有序数组中查找目标值,若存在则返回下标,否则返回 -1。通过二分查找实现,时间复杂度为 O(log n)。关键点在于正确计算中间索引 `mid`,并避免溢出。提供了 C++、Java 和 Python 的实现代码。 ### 27. 移除元素 题目要求原地移除数组中所有等于指定值的元素,并返回新数组长度。使用快慢指针法,将不等于目标值的元素移动到数组前部,从而实现 O(1) 空间复杂度的要求。同样提供了 C++、Java 和 Python 的实现代码。 两题均注重算法效率与空间优化,适合初学者练习基础算法思想。
24. 两两交换链表中的节点, 19.删除链表的倒数第N个节点 ,面试题 02.07. 链表相交
1. **两两交换链表中的节点**:通过引入虚拟头结点,使所有节点都能采用统一的交换逻辑,避免对头结点单独处理。 2. **删除链表的倒数第N个节点**:利用双指针技巧,让快慢指针保持N个节点的距离,当快指针到达末尾时,慢指针正好指向待删除节点的前一个节点。 3. **链表相交**:先计算两链表长度并调整起点,确保从相同距离末尾的位置开始遍历,从而高效找到相交节点或确定无交点。 以上方法均在时间复杂度和空间复杂度上进行了优化,适合用于理解和掌握链表的基本操作及常见算法设计思路。
Agent 工程师绕不开的必修课:API 网关 vs API 管理
本文探讨了“API管理”与“API网关”的起源、发展及差异,二者分别服务于API生命周期的不同阶段。API网关从流量网关演进至AI网关,承担运行时请求控制;API管理则从接口文档化发展到商业化平台,关注全生命周期治理。两者在实际应用中协同工作,通过分层架构和策略联动实现高效运营。未来,随着大模型应用的兴起,AI网关和MCP Server管理将成为新趋势,推动API技术迈入智能化和服务化的新阶段。
Agent 工程师绕不开的必修课:API 网关 vs API 管理
Python内置函数ord()详解
`ord()` 是 Python 中用于将单个字符转换为对应 Unicode 码点的核心函数,支持 ASCII、多语言字符及特殊符号。其返回值为整数(范围 0-1114111),适用于字符编码验证、数据清洗、自定义排序、基础加解密等场景。使用时需注意参数长度必须为 1,否则会触发 `TypeError`。结合 `chr()` 函数可实现双向转换,进阶技巧包括多字节字符处理、编码范围检测及字符分类验证等。
软考软件评测师——软件工程之开发模型与方法
本内容主要介绍了软件开发过程中的核心概念及主流模型,包括瀑布模型、原型模型、增量模型、螺旋模型和敏捷开发等。每种模型各有优劣,适用于不同场景:瀑布模型适合需求明确的大型项目;螺旋模型适用于高风险复杂系统;增量模型支持模块化开发;原型模型适合需求模糊的小型项目;敏捷方法则强调灵活响应与持续交付。此外,还通过历年真题解析,深入探讨了各模型的应用场景及其特点,为实际开发提供了理论指导与实践经验。选择合适的开发模型需综合考虑需求明确度、项目规模、团队经验等因素。
哈夫曼树完全解析:从原理到应用
哈夫曼树是一种带权路径长度最短的二叉树,广泛应用于数据压缩领域。它通过为高频元素分配短编码、低频元素分配长编码,显著减少数据量。构建时根据权重动态合并节点,最终生成无歧义前缀编码。其核心特性包括最优压缩效率、贪心策略有效性和高空间利用率。在现代应用中,哈夫曼编码被用于ZIP压缩、PNG图像、HTTP/2头部压缩及多媒体处理等领域。例如,对字符串“ABRACADABRA”进行压缩,可将88bit数据降至26bit,压缩率达70.5%。
Linux wget 常用命令详解
wget 是一款强大的命令行下载工具,支持 HTTP/HTTPS/FTP 协议。本文详细介绍其基础用法、高效下载参数、高级应用场景及参数速查。内容涵盖断点续传(-c)、后台下载(-b)、限速下载(--limit-rate)、递归下载(-r)、整站镜像(-mk)等实用功能,同时提供文件管理、网络优化与安全下载配置方法,助您高效完成各类下载任务。
性能测试怎么做?方法、流程与核心要点解析
本文系统阐述了性能测试的核心方法论、实施流程、问题定位优化及报告编写规范。涵盖五大测试类型(负载验证、极限压力、基准比对、持续稳定性、弹性扩展)与七项关键指标,详解各阶段任务如需求分析、场景设计和环境搭建,并提供常见瓶颈识别与优化实战案例。最后规范测试报告内容框架与数据可视化建议,为企业级实践提出建立基线库、自动化回归和全链路压测体系等建议,助力高效开展性能测试工作。
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问