1个小时就掌握了N数之和算法的秘诀

本文通过讲解如何解决LeetCode上的两数之和问题,逐步引导读者理解双指针法,并扩展到N数之和的解题思路。通过实例和对话形式,解释了如何利用排序和双指针寻找数组中和为目标值的元素,避免重复,并逐步过渡到解决三数之和、四数之和的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言:前端时间有一学弟问我LeetCode上这两题如何解,以及思路是怎样来的

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

🤷‍♂️:学长,这有两道关于数字之和的题目,你能给我讲解一下吗?

🐱‍💻:嗯,这两道题目很类似,如果你掌握了思路,那么你肯定也会N数之和的算法

🤷‍♂️:可是我总找不到他们的相同出发点🤢,到底是怎么样的思路呢

🐱‍💻:这样吧,我们从最简单的开始,我给你出一道题,两数之和,你试试看能不能做出来

两数之和

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

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

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

示例 2:

/*输入:*/	nums = [-6,3,0,2,1,0,-3]
/*输出:*/	[[-3,3],[0,0]]

示例 3:

/*输入:*/	nums = [0]
/*输出:*/	[]

🐱‍💻:第一步你知道应该做什么吗?

🤷‍♂️:…嗯,我觉得是先把数组排序,这样如果有相同的元素就会集中在一起,我们能清除的知道有多少个

🐱‍💻:没错,假设经过排序后,我们得到了如下数组,你看出什么来了吗?

 [ -3 -1 0 1 2 3 ]

🤷‍♂️:啊,数组有序之后,我一下就能看出哪两个数组之和为0,分别是-3和3,-1和1

🐱‍💻:眼睛看当然容易,程序如何写呢?我先用箭头把他们标出来,显然你的任务是先找到-3和3,再找到-1和1

 [ -3 -1 0 1 2 3 ]
    ▲  ▲   ▲   ▲
    │  │   │   │

🤷‍♂️:我想想…,这里是两个数两个数找,好像有点像双指针!一头一尾,判断他们的和是否为0,然后轻松找到-3和3,可以我如何过度到-1和1呢?

 [ -3 -1 0 1 2 3 ]
    ▲          ▲
    │          │

🐱‍💻:双指针的精髓就是你如何选择移动指针,是选择移动左指针还是右指针,我再给你一个例子,你看看你选择移动左指针还是右指针:

 [ -5 -4 -2 0 1 4 ]
    ▲           ▲
    │           │

🤷‍♂️:肯定是移动左指针,然后就找到-4和4了,嘻嘻~

🐱‍💻:没错,我来告诉你你为什么会选择移动左指针,假设左、右指针指向的数为a和b,因为此时 a + b = -1,小于0,因此我们需要让a或者b其中一个数变大,才有可能让a + b ==0,因为这是一个有序数组,恰好a右边的数字一定大于a,所以选择让左指针向右移动

🤷‍♂️:(恍然大悟)原来是这样啊,如果a + b == 0的话,那就意味着左右指针都得向中心移动,因为只移动一个指针下一次相加比较肯定不会等于0(数组无重复元素)

🐱‍💻:你试着写一下程序吧

🤷‍♂️:

function TwoSum(arr) {
	arr.sort((a, b) => a - b)
    let res = []
    let i = 0
    let j = arr.length - 1
    while (i < j) {
        let result = arr[i] + arr[j]
        if (result == 0) {
            res.push([arr[i], arr[j]])
            i++
            j--
        } else if (result < 0) {
            i++
        } else {
            j--
        }
    }
    return res
}

🐱‍💻:没错,看来学弟的领悟力挺高的👏,那假如数组里面有重复的元素呢,假如按照上面代码的方法,得到的数组里面会有重复的结果,类似[[-2,2],[-2,2],[-1,1]],这就会不符合我们题目的要求

🤷‍♂️:那该怎么办呢?去除重复元素不就好啦?

🐱‍💻:这当然可以,但是去除相重复内容的数组并不方便,费事费力,我们要从源头再思考一下,能不能当重复的元素第一次遇到,我们才push进入数组呢?

🤷‍♂️:我第一个想到的是做一个标记,但好像这个并不方便

🐱‍💻:很简单,只要加上两条语句,如果之前碰到了,就直接移动指针,看起来就好像没有这些重复元素一样

if (result == 0) {   
    res.push([arr[i], arr[j]])
    while (nums[i] == nums[i + 1]) i++;
    while (nums[j] == nums[j - 1]) j--;
    i++
    j--
}

🤷‍♂️:哇!原来对数组排序有这么妙的作用,以后不管三七二十一,遇到什么题目我都排序,嘻嘻~😜

🐱‍💻:我再给你加一点难度,要求a + b == target才push进入数组,其中target是我给定的值,而不是a + b ==0

🤷‍♂️:嘻嘻~,学长终于问到我会的了,像这样就可以:

function TwoSum(arr, target) {
    arr.sort((a, b) => a - b)
    let res = []
    let i = 0
    let j = arr.length - 1
    while (i < j) {
        let result = arr[i] + arr[j]
        if (result == target) {   //0改为target
            res.push([arr[i], arr[j]])
            while (nums[i] == nums[i + 1]) i++;
            while (nums[j] == nums[j - 1]) j--;
            i++
            j--
        } else if (result < target) { //0改为target
            i++
        } else {
            j--
        }
    }
    return res
}

🐱‍💻:嗯,不错嘛,接下来你再看看这道三数之和的题目
在这里插入图片描述
🤷‍♂️:如果按照刚才的思路,这道题目是求三个数字的和,是不是得弄个三指针呢?左右分别放一个指针我知道,可是这第三个指针放哪里呢?

🐱‍💻:这样和你说吧,你知道二维和三维的关系吗?

🤷‍♂️:二维是一个面,三维是一个体,体是由多个面组成的

🐱‍💻:所以想要得到一个体,你得多次求"面"。而在这个题目中,意味着你得多次进行两数之和运算,假设题目输入了如下数组:

[ -4 -1 -1 0 1 2 ]

🤷‍♂️:我还是没想到怎么解题

🐱‍💻:这样吧,我给你四个小输入,求两数之和,让a+b等于后面的数字

1.
  [ -1 -1 0 1 2 ] a+b == -4  
2.
  [ -1 0 1 2 ]  a+b == -1
3.
  [ 0 1 2 ]  a+b == -1
4.
  [ 1 2 ]  a+b == 0

🤷‍♂️:嘿嘿,这个我刚才学会了,学长等我一下,我马上求出来:

1.
  [ -1 -1 0 1 2 ] a+b == 4   //[]
2.
  [ -1 0 1 2 ]  a+b == 1   //[[-1,2],[0,1]]
3.
  [ 0 1 2 ]  a+b == 1  //[]
4.
  [ 1 2 ]  a+b == 0  //[]

🐱‍💻:然后我再告诉你刚才那个题目的答案,你发现什么了吗?

[ -4 -1 -1 0 1 2 ]  //[[-1,-1,2][-1,0,1]]

🤷‍♂️:哇!我发现如果把target取反再加入右边的数组好像就是上面的答案了

 1.
   [ -1 -1 0 1 2 ] a+b == 4   //[]
 2.                    ┌─────────────────────┐
   [ -1 0 1 2 ]  a+b ==│1   //[[-1,2],[0,1]] │ => [[-1,-1,2][-1,0,1]]
 3.                    └─────────────────────┘
   [ 0 1 2 ]  a+b == 1  //[]
 4.
   [ 1 2 ]  a+b == 0  //[]

🐱‍💻:你已经建立起了他们的联系,试着用循环解决一下这个问题

🤷‍♂️:(思考中…),我还是不会写😢

🐱‍💻:没关系,我先给你看我写的解析的流程图


 -4 -1 -1 0 1 2
  ▲  ▲        ▲
  │  │        │
  k  i        j

 -4 -1 -1 0 1 2
  ▲     ▲     ▲
  │     │     │
  k     i     j

 -4 -1 -1 0 1 2
  ▲       ▲   ▲
  │       │   │
  k       i   j

 -4 -1 -1 0 1 2
  ▲         ▲ ▲
  │         │ │
  k         i j

 -4 -1 -1 0 1 2
     ▲  ▲     ▲
     │  │     │   => [-1,-1,2]
     k  i     j

 -4 -1 -1 0 1 2
     ▲    ▲ ▲
     │    │ │     => [-1,0,1]
     k    i j

 -4 -1 -1 0 1 2
        ▲
        │        //Meet the same value, and jump
        k

 -4 -1 -1 0 1 2
          ▲ ▲ ▲
          │ │ │
          k i j

🤷‍♂️:哇!原来是这么一个流程,我好像有一点头绪了,(忙碌中…)

🤷‍♂️:是这样吗:

function TwoSum(arr) {
    arr.sort((a, b) => a - b)
    let res = []
    for (let k = 0; k < arr.length; k++) {
    	if (k > 0 && arr[k] == arr[k - 1]) continue //消除k的重复
        let i = k + 1
        let j = arr.length - 1
        while (i < j) {
            let result = arr[k] + arr[i] + arr[j]
            if (result == 0) {
                res.push([arr[k], arr[i], arr[j]])
                while (nums[i] == nums[i + 1]) i++; //消除i的重复
                while (nums[j] == nums[j - 1]) j--; //消除j的重复
                i++;
                j--;
            } else if (result < 0) {
                i++;
            } else {
                j--;
            }
        }
    }
    return res
}

🐱‍💻:不错👍,如果你仔细地思考了我给你讲的这些,那么剩下的那道四数之和我相信你肯定也能够完成

🤷‍♂️:嗯嗯!我这就回去自己研究

(第二天)

🤷‍♂️:学长,我写出来了!你康康,四个指针[i,j,k,l]

var fourSum = function(nums, target) {
    if (nums.length < 4) return []
    nums.sort((a, b) => a - b)
    let res = []
    for (let i = 0; i < nums.length - 1; i++) {
        if (i > 0 && nums[i] == nums[i - 1]) continue //消除i的重复
        for (let j = i + 1; j < nums.length - 1; j++) {
            if (j > i + 1 && nums[j] == nums[j - 1]) continue //消除j的重复
            let k = j + 1
            let l = nums.length - 1
            while (k < l) {
                let result = nums[i] + nums[j] + nums[k] + nums[l]
                if (result === target) {
                    res.push([nums[i], nums[j], nums[k], nums[l]])
                    while (nums[k] == nums[k + 1]) k++; //消除k的重复
                    while (nums[l] == nums[l + 1]) l--; //消除l的重复
                    k++;
                    l--;
                } else if (result > target) {
                    l--;
                } else {
                    k++
                }
            }
        }
    }
    return res
};

🐱‍💻:完美啊

🤷‍♂️:😎


在这里插入图片描述

如果这篇文章帮助到了你,欢迎点赞收藏加关注,后续将会推出更优质的内容~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值