当前位置: 首页 > news >正文

【2/3/4-sum问题】算法设计剖析

【2/3/4-sum问题】算法设计剖析

刷Leetcode的第一题的时候,就是4-sum问题,然后脑子里开始回想算法课中接触过的2-sum和3-sum问题,发现只有零星的片段,没有系统的优化设计过程,所以在这里进行整理。

有任何问题欢迎评论区或私信交流~


Two-sum问题

  1. 问题描述

对于给定的一个整型数组,求解其中两个元素和为零的整数对以及整数对的数目。(不允许包含重复的整数对,整数对是无序的)

  1. 问题解决

(1)暴力算法O(n2)

用两层循环来维持两个元素的指针,依次判断元素的和是否为0。

注意:为了维持数对的不重复性,维护的两个指针也应该有顺序区分。

void TwoSum(vector<int> arr){
    int res = 0;
    vector<pair<int,int> > ans;
    memset(ans,0,sizeof(ans));
    for(int i = 0;i<arr.size();i++){
        for(int j = i+1;j<arr.size();j++){
            if(arr[i]+arr[j] == 0){
                res++;
                ans.push_back(make_pair(arr[i],arr[j]);
            }
    //ans中存放的就是结果数对,res中存放的就是符号要求的数对数量
}

2)归并排序+二分查找

【核心思路】
先将元素进行排序,对于每一个元素a[i],当且仅当-a[i]也存在于数组中且a[i]≠0时,a[i]存在于满足要求的数对中。

【复杂度分析】
对于每一个-a[i]进行查询的时候,使用二分查找,这要求数组是有序的,故先要用归并排序对数组进行排序。

整个算法的复杂度为O(NlogN)

①折半查找的实现

【递归实现】

//二分查找返回符合要求的数组下标,若查询不到符合要求的下标,则返回-1
int binary_search(int a[],int low,int high,int key){
     if(low >high}
         return -1;
     int mid = (low + high) / 2;
     if(a[mid] == key)
         return mid;
     if(a[mid] > key)
         return binary_search(a,low,mid-1,key);
     else
         return binary_search(a,mid+1,high,key);
     }

【非递归实现】

//二分查找返回符合要求的数组下标,若查询不到符合要求的下标,则返回-1
int binary_search(int a[],int low,int high,int key){
     while(low<=high){
         int mid = (low + high) / 2;
         if(a[mid] == key)
             return mid;
         if(a[mid] > key)
             high = mid - 1;
         else
             low = mid + 1;
     }
     return -1;
 }

②归并排序的实现

归并排序是分治算法常见的应用之一,关于其实现、优化以及自顶向下和自底向上的两种不同思想,可以参考这篇博文《归并排序算法的分析和实现》

③线性对数级别的TwoSum问题的算法伪码

void TwoSumFast(vector<int> arr){
    int res = 0;
    for(int i = 0;i<arr.size();i++){
        if(binary_search(arr,0,arr.size()-1,-arr[i])>i
            res++;
        }
    //res中存放的就是符号要求的数对数量
}

算法使用二分查找,对于数组中的每一个元素a[i],针对键值-a[i]进行查找。如果结果为j且j>i(维持这个条件主要是为了防止重复),我们就将计数器加1.
这个简单的条件猜测是覆盖了三种情况:

  • 如果二分查找不成功则会返回01,那么我们不会增加计数器的值
  • 如果二分查找返回的j>i,我们就有a[i]+a[j] = 0,增加计数器的值。
  • 如果二分查找返回的j在0和i之间,我们也有a[i] + a[j] = 0,但不能增加计数器的值,以避免重复计数。

Three-sum问题

  1. 问题描述

对于给定的一个整型数组,求解其中三个元素和为零的整数对以及整数对的数目。(不允许包含重复的整数对,整数对是无序的)

  1. 问题解决

(1)暴力算法O(n2)

用三层循环来维持三个元素的指针,依次判断元素的和是否为0。

注意:为了维持数对的不重复性,维护的三个指针也应该有顺序区分。

代码和Two-sum问题的暴力解法很相似,这里略过。

2)归并排序+二分查找

【核心思路】
先将元素进行排序(不论是两数和还是三数和问题,均假设所有整数各不相同),对于元素a[i]和a[j],当且仅当-(a[i]+a[j])也存在于数组中时,整数对(a[i],a[j])为某个和为0的三元组的一部分。

【复杂度分析】
对于每一个整数对的和的相反数-(a[i]+a[j])进行查询的时候,使用二分查找,这要求数组是有序的,故先要用归并排序对数组进行排序。

整个算法的复杂度为O(N2logN)

在这种情况下,排序的成本反而是次要因素。

void TwoSumFast(vector<int> arr){
    int res = 0;
    for(int i = 0;i<arr.size();i++){
        for(int j = =i+1;j<arr.size();j++){
            if(binary_search(arr,0,arr.size()-1,-(arr[i]+arr[j]))>j)
            res++;
        }
    //res中存放的就是符号要求的数对数量
}

Four-sum问题

  1. 问题描述

对于给定的一个整型数组,求解其中四个元素和为给定目标值target的整数对以及整数对的数目。(不允许包含重复的整数对,整数对是无序的)

  1. 问题求解

①当然也可以考虑之前都用过的暴力求解方法,四层循环,依次验证。但是时间开销会很大,对于数据量过大的用例是无法通过的。

②双指针思想

题解来源于《双指针解法-参照三数之和》

【核心思路】
维护了四个指针a,b,c,d(a<b<c<d),起初的时候固定最小的a和b在数组的最左侧,c = b+1,d = size-1,c和两个指针实行包夹求解。保存使得nums[a]+nums[b]+nums[c]+nums[d]==target的解。

偏大时d左移,偏小时c右移。c和d相遇时,表示以当前的a和b为最小值的解已经全部求得。b++,进入下一轮循环b循环,当b循环结束后。 a++,进入下一轮a循环。 即(a在最外层循环,里面嵌套b循环,再嵌套双指针c,d包夹求解)。

【代码示例】

class Solution{
	public: 
	vector<vector<int>> fourSum(vector<int>& nums, int target) {
        sort(nums.begin(),nums.end());
        vector<vector<int> > res;
        if(nums.size()<4)
        return res;
        int a,b,c,d,_size=nums.size();
        for(a=0;a<=_size-4;a++){
        	if(a>0&&nums[a]==nums[a-1]) continue;      //确保nums[a] 改变了
        	for(b=a+1;b<=_size-3;b++){
        		if(b>a+1&&nums[b]==nums[b-1])continue;   //确保nums[b] 改变了
        		c=b+1,d=_size-1;
        		while(c<d){
        			if(nums[a]+nums[b]+nums[c]+nums[d]<target)
        			    c++;
        			else if(nums[a]+nums[b]+nums[c]+nums[d]>target)
        			    d--;
        			else{
        				res.push_back({nums[a],nums[b],nums[c],nums[d]});
        				while(c<d&&nums[c+1]==nums[c])      //确保nums[c] 改变了
        				    c++;
        				while(c<d&&nums[d-1]==nums[d])      //确保nums[d] 改变了
        				    d--;
        				c++;
        				d--;
					}
				}
			}
		}
		return res;
    }
};


如果把四数求和的问题中的双指针思想弄明白了,那么对于二数和、三数和问题同样可以借鉴这个思想。

比如二数和的时候,只需要两个指针,分别从左和右进行包夹即可。

三数和的时候,需要维持一层的循环,循环内就是两个指针的包夹。

在双指针思想中,循环控制的是基准,相当于为包夹定下范围和边界;包夹是进行数值选择,确定最终的解。

相关文章:

  • 【矩阵论】线性空间与线性变换(6)
  • 【矩阵论】内积空间与等距变换(1)
  • TOP K问题的算法分析与实现
  • 【矩阵论】内积空间与等距变换(2)
  • 【矩阵论】矩阵的相似标准型(1)
  • 【矩阵论】矩阵的相似标准型(2)
  • 【MIT算法导论】线性时间排序
  • 【矩阵论】矩阵的相似标准型(3)
  • 【Leetcode】二叉树的遍历
  • 【矩阵论】矩阵的相似标准型(4)(5)
  • 【PyTorch】深度学习基础:神经网络
  • 【矩阵论】矩阵的相似标准型(5)
  • 【台大李宏毅|ML】Gradient Descent
  • 【矩阵论】Hermite二次型(1)
  • 【矩阵论】Hermite二次型(2)
  • 【347天】每日项目总结系列085(2018.01.18)
  • Android系统模拟器绘制实现概述
  • ECMAScript入门(七)--Module语法
  • hadoop集群管理系统搭建规划说明
  • Java比较器对数组,集合排序
  • JS基础之数据类型、对象、原型、原型链、继承
  • Node 版本管理
  • node和express搭建代理服务器(源码)
  • Python_网络编程
  • Python利用正则抓取网页内容保存到本地
  • Python十分钟制作属于你自己的个性logo
  • 高性能JavaScript阅读简记(三)
  • 机器人定位导航技术 激光SLAM与视觉SLAM谁更胜一筹?
  • 基于遗传算法的优化问题求解
  • 力扣(LeetCode)21
  • 批量截取pdf文件
  • 前端工程化(Gulp、Webpack)-webpack
  • 如何进阶一名有竞争力的程序员?
  • 使用Maven插件构建SpringBoot项目,生成Docker镜像push到DockerHub上
  • 为视图添加丝滑的水波纹
  • 一天一个设计模式之JS实现——适配器模式
  • elasticsearch-head插件安装
  • 回归生活:清理微信公众号
  • ​RecSys 2022 | 面向人岗匹配的双向选择偏好建模
  • $$$$GB2312-80区位编码表$$$$
  • $(function(){})与(function($){....})(jQuery)的区别
  • %@ page import=%的用法
  • (1)bark-ml
  • (C语言)输入一个序列,判断是否为奇偶交叉数
  • (Python) SOAP Web Service (HTTP POST)
  • (阿里云万网)-域名注册购买实名流程
  • (附源码)ssm高校升本考试管理系统 毕业设计 201631
  • (轉貼)《OOD启思录》:61条面向对象设计的经验原则 (OO)
  • .net core 客户端缓存、服务器端响应缓存、服务器内存缓存
  • .Net FrameWork总结
  • .Net 应用中使用dot trace进行性能诊断
  • .net6+aspose.words导出word并转pdf
  • .NET面试题解析(11)-SQL语言基础及数据库基本原理
  • //解决validator验证插件多个name相同只验证第一的问题
  • @SentinelResource详解