前端面试题集每日一练Day15

问题先导

  • 说一下HTML5的drag API【html】
  • 两栏布局的实现【css布局】
  • 三栏布局的实现【css布局】
  • 常见的位运算符【js基础】
  • 为什么函数中的隐式变量arguments是类数组而不是数组?【js基础】
  • BOM和DOM的区别?【js基础】
  • 对React和Vue的理解,它们的异同【Vue】
  • Vue的优点【Vue】
  • assets和static目录的区别?【Vue】
  • 实现数组去重【手写代码】
  • 实现数组的flat方法【手写代码】
  • 实现数组的push方法【手写代码】
  • 代码输出结果(Promise相关)【代码输出】
  • 翻转二叉树【算法】
  • 三数之和【算法】

知识梳理

说一下HTML5的drag API

drag事件即拖拽事件,当元素或文本被推动时触发,同mouse事件类似,相对于mouse事件,drag事件有一套更为完整的专门针对拖放的事件,拖放的过程一般有两个元素需要侦听:被拖动的元素和接收被拖动元素的目标元素

针对两个不同的元素,有不同的事件侦听方式:

  • 被拖动元素
    • dragstart:元素被拖动时触发
    • drag:元素拖动过程中触发
    • dragend:拖动结束时触发
  • 目标元素
    • dragenter:被拖动元素进入目标元素时触发(一般也就是鼠标进入目标元素时触发)
    • dragover:被拖动元素在目标元素中移动时触发
    • dragleave:被拖动元素离开目标元素时触发
    • drop:拖动在目标元素内结束拖动时触发

值得注意的是,如果要让drop触发,需要阻止dragover的默认事件:event.preventDefault();

参考:

两栏布局的实现

一般两栏布局指的是左边一栏宽度固定,右边一栏宽度自适应

假设左侧宽度固定为200px。基本html结构为:

<div class="outer">
    <div class="left"></div>
    <div class="right"></div>
</div>
复制代码

一般有三种方案:

  1. 利用浮动定位:float

    • 左侧浮动定位,设置宽度为固定宽度;右侧设置margin-left为左侧固定宽度值,宽度设置为auto,就会自动撑满父元素。

      .outer {
          width: 500px;
          height: 500px;
          margin: 0px auto;
          border: 1px solid #ccc;
      }
      
      .left {
          float: left;
          
          width: 200px;
          height: 100px;
          background-color: #f59898;
      }
      
      .right {
          margin-left: 200px;
          
          width: auto;
          height: 300px;
          background-color: #9ed0c4;
      }
      复制代码
    • 左侧浮动定位,并设置固定宽度;右侧设置over-flow:hidden,这样右边就会触发BFC(Block Formatting Context),BFC区域不会与浮动元素重叠。

      .right {
          over-flow: hidden;
          width: auto;
          
          height: 300px;
          background-color: #9ed0c4;
      }
      复制代码
  2. 利用绝对定位

    • 将父元素设置为相对定位;左侧元素设置为绝对定位,宽度设置为固定宽度值;右侧设置margin-left为固定宽度值,宽度设置为calc(100% - 200px)。需要注意的是,display需要换成行内元素如display: inline-block

      .outer {
          margin: 0px auto;
          width: 500px;
          height: 500px;
          border: 1px solid #ccc;
      }
      
      .left {
          position: absolute;
      
          width: 200px;
          height: 100px;
          background-color: #f59898;
      }
      
      .right {
          margin-left: 200px;
          
          height: 300px;
          background-color: #9ed0c4;
      }
      复制代码
    • 将父元素设置为相对定位,左侧元素宽度设置为固定宽度,设置右侧为绝对定位,left设置为固定宽度值。

      .outer {
          position: relative;
      
          margin: 0px auto;
          width: 500px;
          height: 500px;
          border: 1px solid #ccc;
      }
      
      .left {
          width: 200px;
          height: 100px;
          background-color: #f59898;
      }
      
      .right {
          position: absolute;
          width: calc(100% - 200px);
          top: 0px;
          left: 200px;
          height: 300px;
          background-color: #9ed0c4;
      }
      复制代码
  3. 利用flex布局:设置左侧元素宽度为固定值,右侧flex设置为auto1

    .outer {
        display: flex;
        width: 500px;
        height: 500px;
        margin: 0px auto;
        border: 1px solid #ccc;
    }
    
    .left {
        width: 200px;
        height: 100px;
        background-color: #f59898;
    }
    
    .right {
        flex: 1;
        height: 300px;
        background-color: #9ed0c4;
    }
    复制代码

三栏布局的实现

三栏布局一般指的是页面中一共有三栏,左右两栏宽度固定,中间自适应的布局

和两栏布局差不多,基本结构为:

<div class="outer">
    <div class="left"></div>
    <div class="right"></div>
    <div class="center"></div>
</div>
复制代码
  1. 利用浮动

    设置左右元素分别为左右浮动,中间元素再分别设置margin-leftmargin-right

    但是,div.center元素必须位于div.right之后,否则浮动会换行显示。

    .outer {
        width: 500px;
        height: 500px;
        margin: 0px auto;
        border: 1px solid #ccc;
    }
    
    .left {
        float: left;
        width: 200px;
        height: 100px;
        background-color: #f59898;
    }
    
    .center {
        margin-left: 200px;
        margin-right: 100px;
        width: auto;
        height: 200px;
        background-color: #9ed0c4;
    }
    
    .right {
        float: right;
        width: 100px;
        height: 300px;
        background-color: #a99ed0;
    }
    复制代码
  2. 利用绝对定位

    设置父元素为相对定位,左右两侧设置为绝对定位,中间再设置一下margin即可,和浮动定位类似,但不需要将div.center放置到div.right之后。

    .outer {
        position: relative;
        width: 500px;
        height: 500px;
        margin: 0px auto;
        border: 1px solid #ccc;
    }
    
    .left {
        position: absolute;
        left: 0px;
        top: 0px;
        width: 200px;
        height: 100px;
        background-color: #f59898;
    }
    
    .center {
        margin-left: 200px;
        margin-right: 100px;
        width: auto;
        height: 200px;
        background-color: #9ed0c4;
    }
    
    .right {
        position: absolute;
        right: 0px;
        top: 0px;
        width: 100px;
        height: 300px;
        background-color: #a99ed0;
    }
    复制代码
  3. 利用flex定位

    左右两侧设置固定宽度,中间设置flex:1即可。

    .outer {
        display: flex;
        width: 500px;
        height: 500px;
        margin: 0px auto;
        border: 1px solid #ccc;
    }
    
    .left {
        width: 200px;
        height: 100px;
        background-color: #f59898;
    }
    
    .center {
        flex: 1;
        height: 200px;
        background-color: #9ed0c4;
    }
    
    .right {
        width: 100px;
        height: 300px;
        background-color: #a99ed0;
    }
    复制代码
  4. 圣杯布局

    利用浮动负边距来实现。将中间元素放到最前面,然后设置宽度为父级元素宽度,三列均设置为左浮动,这样左右两个元素就会被挤到下一行,然后通过设置元素的margin为负值,就能将下一行的这两个浮动元素移到上一行。然后再利用相对定位来调整左右元素的位置,最后再设置父元素的padding,让左右元素处于padding之内即可。

    .outer {
        position: relative;
        margin: 0px auto;
        width: 500px;
        height: 500px;
        border: 1px solid #ccc;
        padding-left: 200px;
        padding-right: 100px;
    }
    
    .center {
        float: left;
        width: 100%;
        height: 400px;
        background-color: #9ed0c4;
    }
    
    .left {
        float: left;
        margin-left: -200px;
        position: relative;
        left: -100%;
        width: 200px;
        height: 100px;
        background-color: #f59898;
    }
    
    .right {
        float: left;
        margin-left: -100px;
        width: 100px;
        position: relative;
        left: 100px;
        height: 300px;
        background-color: #a99ed0;
    }
    复制代码

    但是圣杯布局有个问题当面板的main部分比两边的子面板宽度小的时候,布局就会乱掉。但是圣杯布局有个问题当面板的main部分比两边的子面板宽度小的时候,布局就会乱掉。想实现更完美的布局非常困难,因此双飞翼布局在主面板上选择了添加一个标签。

  5. 双飞翼布局

    双飞翼布局相对于圣杯布局来说,左右位置的保留是通过中间列的 margin 值来实现的,而不是通过父元素的 padding 来实现的。本质上来说,也是通过浮动和外边距负值来实现的。

    <div class="outer">
        <div class="center-container">
            <div class="center"></div>
        </div>
        <div class="left"></div>
        <div class="right"></div>
    </div>
    复制代码
    .outer {
        position: relative;
        margin: 0px auto;
        width: 500px;
        height: 500px;
        border: 1px solid #ccc;
    }
    
    .center-container {
        float: left;
        width: 100%;
    }
    
    .center {
        height: 400px;
        margin-left: 200px;
        margin-right: 100px;
        background-color: #9ed0c4;
    }
    
    .left {
        float: left;
        margin-left: -100%;
        width: 200px;
        height: 100px;
        background-color: #f59898;
    }
    
    .right {
        float: left;
        margin-left: -100px;
        width: 100px;
        height: 300px;
        background-color: #a99ed0;
    }
    复制代码
    • 双飞翼布局给主面板添加了一个父标签用来通过margin给子面板腾出空间
    • 圣杯采用的是padding,而双飞翼采用的margin,解决了圣杯布局的问题。
    • 双飞翼布局不用设置相对布局,以及对应的left和right值。

更多细节可参考:什么是圣杯布局以及双飞翼布局

如果对网格布局有了解,两列布局和三列布局均可用网格布局实现。

常见的位运算符有哪些?

二进制之间的运算就称为位运算。常见的位运算有以下几种:

运算符 描述 运算规则
& 两个位都为1时,结果才为1
` 只要有一个为1,结果就为1
^ 异或 两个位相同为0,相异为1
~ 取反 0变1,1变0
<< 左移 各二进制位全部左移若干位,高位丢弃,低位补0
>> 右移 各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃

位运算由于直接使用二进制进行计算,节约了内存,使程序计算效率更高,不同的位运算有不同的用途。

在js中,可以通过以下运算符进行运算:

按位与(&)

都为1时结果为1,比如3&5 = 1写成二进制就是:

0000 0011 &
0000 0101 =
0000 0001
复制代码

用途:判断数字奇偶:n & 1 === 0n % 2 == 0是一样的。

按位或(|)

只要有一个为1,结果就为1。比如3 | 5 = 7

0000 0011 |
0000 0101 =
0000 0111
复制代码

按位异或(^)

相同为0,相异为1。所以3 ^ 5 = 6

0000 0011 ^
0000 0101 = 
0000 0 110
复制代码

异或是很有规律的一种位运算:

  • 自反性:a ^ a = 0a ^ 0 = a
  • 结合律:a ^ b ^ c = a ^ (b ^ c)
  • 交换律:a ^ b = b ^ a(这没什么好说的,别的位运算也这样)

其中最主要的就是自反性,因为交换律和结合律别的运算符一般也有这个性质。

由自反性得:a ^ b ^ b = a ^ 0 = a。我们可以通过这个性质交换ab的值而不使用中间变量。

a = a ^ b
b = a ^ b
a = a ^ b
复制代码

取反运算符(~)

没什么好说的,0和1交换即可。

左移运算(>>)

将一个运算对象的各二进制位全部左移若干位,左边的二进制位丢弃,右边补0

有符号右移(>>)

该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,拷贝最左侧的位以填充左侧。由于新的最左侧的位总是和以前相同,符号位没有被改变。所以被称作“符号传播”。

无符号右移(>>>)

该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,左侧用0填充。因为符号位变成了 0,所以结果总是非负的。(译注:即便右移 0 个比特,结果也是非负的。)

原码、反码和补码

值得注意的是,二进制在计算机中均以补码进行存储,计算的时候同样使用的是补码。

数字有正负,一般以第一位比特表示正负,0表示正数,1表示负数,后面存储的位数才是真实的数值。

我们发现二进制是没有减法运算的,或者说不方便进行减法运算,那么对于减法运算我们作何处理呢?

那就是看成加法运算,比如1 - 2等同于1 + (-2)。百科上关于补码的概念引入了“模”的概念,比如一个时钟,能表示的时间只有0-11小时,这种有限范围的计量系统,就称之为模。计量系统的最大计量范围也称为模,所以这里的时钟可以看做模为12的计量系统,在时钟系统中,如果要把8时调整为6时,有两种方法,一是往回拨2时刻,二是往前拨10时刻,也就是做减法和做加法的区别:即8 - 2 = 8 + 10,对于8 + 10由于超过了模的计量范围,就只能保存余数:18 - 12 = 6,因此这里的加法和减法是能互相转换的,而这个性质对于所有模计量系统都符合。对于减法8 - 2转换为加法就是8 + (-2),我们用模加上这个负数,就得到了它的“补码”10,用补码就能方便地进行加法运算了。

计算机也是一种模计量系统,因为计算机的存储位数是有限的,当超过这个限制,数字就会丢失,只保留能存储的数字,比如某个计算机的存储位数只有4位,那么最大的存储数字就为1111,如果再往上加1,就变成了10000,由于模的限制,只能保存4位,又变回了0000

由于二进制不方便进行减法运算,我们都要转换为加法运算,在模系统中,减法转加法就是加上它的补码即可,为了方便计算,计算机存储的都是补码,以方便直接进行运算,我们知道,补码 + 源码 = 模,对于二进制的计算机系统来说,模是不固定的,但我们找到了计算负数补码的方式:补码 = 反码 + 1,反码就是除符号位外,其余位取反即可。这里需要注意的是,补码和反码都是针对负数而言的,因为正数不需要进行减法转加法换算。

补码的计算原理很简单:原码+ 反码 = 11111...,即全是1,再加1,就得到模,所以有原码 + 反码 + 1 = 模,所以有补码 = 反码 + 1

为什么函数中的隐式变量arguments是类数组而不是数组?

为什么函数的隐式变量arguments是类数组而不是数组?

argument是一个对象,是专门用于收集函数参数的内置变量,argument.length可以表示收集到的参数数量,而且可以使用arguments[n](n ∈ [0, arguments.length – 1])来访问数字,而且这些下标可迭代,到这里,arguments和数组一样访问元素,但arguments不具备数组的其他属性和方法,而且有自己的属性如callee,因此,arguments只是类数组而不是真正的数组,要转为数组,只需要使用Array.from(arguments)即可。

BOM和DOM的区别?

BOM全称Browser Object Mode,即浏览器对象模型,为了方便和JavaScript进行交互,浏览器被抽象为了一个JavaScript对象,这就是BOM,可以将其理解为浏览器的抽象,也就是我们常用的Window对象。

而DOM全称Document Object Mode,即文档模型对象,这里的文档,就是HTML文档,同样为了方便和js交互,被抽象为了一个Document对象,也是一个js对象。

而DOM对象是被包含在BOM对象中的。

对React和Vue的理解,它们的异同

官方自己进行了对比说明:对比其他框架

相似之处:

  • 都将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库;
  • 都有自己的构建工具,能让你得到一个根据最佳实践设置的项目模板;
  • 都使用了Virtual DOM(虚拟DOM)提高重绘性能;
  • 都有props的概念,允许组件间的数据传递;
  • 都鼓励组件化应用,将应用分拆成一个个功能明确的模块,提高复用性。

不同之处 :

1)数据流

Vue默认支持数据双向绑定,而React一直提倡单向数据流

2)虚拟DOM

Vue2.x开始引入”Virtual DOM”,消除了和React在这方面的差异,但是在具体的细节还是有各自的特点。

  • Vue宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
  • 对于React而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过 PureComponent/shouldComponentUpdate这个生命周期方法来进行控制,但Vue将此视为默认的优化。

3)组件化

React与Vue最大的不同是模板的编写。

  • Vue鼓励写近似常规HTML的模板。写起来很接近标准 HTML元素,只是多了一些属性。
  • React推荐你所有的模板通用JavaScript的语法扩展——JSX书写。

具体来讲:React中render函数是支持闭包特性的,所以import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以 import 一个组件完了之后,还需要在 components 中再声明下。 4)监听数据变化的实现原理不同

  • Vue 通过 getter/setter 以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能
  • React 默认是通过比较引用的方式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的vDOM的重新渲染。这是因为 Vue 使用的是可变数据,而React更强调数据的不可变。

5)高阶组件

react可以通过高阶组件(HOC)来扩展,而Vue需要通过mixins来扩展。

高阶组件就是高阶函数,而React的组件本身就是纯粹的函数,所以高阶函数对React来说易如反掌。相反Vue.js使用HTML模板创建视图组件,这时模板无法有效的编译,因此Vue不能采用HOC来实现。

6)构建工具

两者都有自己的构建工具:

  • React ==> Create React APP
  • Vue ==> vue-cli

7)跨平台

  • React ==> React Native
  • Vue ==> Weex

Vue的优点

轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十 kb

简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;

双向数据绑定:保留了 angular 的特点,在数据操作方面更为简单;

组件化:保留了 react 的优点,实现了 html 的封装和重用,在构建单页面应用方面有着独特的优势;

视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;

虚拟DOM:dom 操作是非常耗费性能的,不再使用原生的 dom 操作节点,极大解放 dom 操作,但具体操作的还是 dom 不过是换了另一种方式;

运行速度更快:相比较于 react 而言,同样是操作虚拟 dom,就性能而言, vue 存在很大的优势。

assets和static目录的区别?

相同点: assetsstatic 两个都是存放静态资源文件。项目中所需要的资源文件图片,字体图标,样式文件等都可以放在这两个文件下。

不相同点:assets 中存放的静态资源文件在项目打包时,也就是运行 npm run build 时会将 assets 中放置的静态资源文件进行打包上传,所谓打包简单点可以理解为压缩体积,代码格式化。而压缩后的静态资源文件最终也都会放置在 static 文件中跟着 index.html 一同上传至服务器。static 中放置的静态资源文件就不会要走打包压缩格式化等流程,而是直接进入打包好的目录,直接上传至服务器。因为避免了压缩直接进行上传,在打包时会提高一定的效率,但是 static 中的资源文件由于没有进行压缩等操作,所以文件的体积也就相对于 assets 中打包后的文件提交较大点。在服务器中就会占据更大的空间。

建议: 将项目中 template需要的样式文件js文件等都可以放置在 assets 中,走打包这一流程。减少体积。而项目中引入的第三方的资源文件如iconfoont.css 等文件可以放置在 static 中,因为这些引入的第三方文件已经经过处理,不再需要处理,直接上传。

实现数组去重

利用一些存不重复图元的数据结构进行去重,比如哈希表,也就是普通对象;或者使用集合Set,然后在转为数组。

/**
 * 数组去重
 * @param {any[]} arr 
 */
function uniqueArr(arr) {
  if(!arr) {
    return [];
  }
  return Array.from(new Set(arr));
}

function uniqueArr2(arr) {
  if(!arr) {
    return [];
  }
  const map = new Map();
  arr.forEach(item => {
    map.set(item, undefined);
  });
  return Array.from(map.keys());
}
复制代码

实现数组的flat方法

flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

也就是数组扁平化的功能,flat函数接收一个参数,表示提取的嵌套数组的结构深度,默认为1,

/**
 * 数组的扁平化
 * @param {number} depth 
 */
Array.prototype.myFlat = function(depth) {
  const res = [];
  depth = depth || 1;
  this.forEach(item => {
    if(Array.isArray(item) && item.length >= depth) {
      res.push(...this.myFlat.call(item, depth));
    } else {
      res.push(item);
    }
  });
  return res;
};
复制代码

参考:

实现数组的push方法

push也就是往数组中添加一个或多个参数。

Array.prototype.push = function() {
  const arr = arguments.length > 0 ? Array.from(arguments) : [undefined]
  arr.forEach(item => {
    this[this.length] = item;
  });
  return arr.length;
};
复制代码

代码输出结果(Promise相关)

代码片段:

async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
复制代码

考察asyncawait的用法,记住,这两个关键字只是语法糖,await相当于Promise.then()链式调用。

async function async1 () {
  console.log('async1 start'); // 3.打印async1 start
  await new Promise(resolve => { // 4.执行new Promise(),打印promise1
    console.log('promise1')
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start') // 1.打印srcipt start
async1().then(res => console.log(res)) // 2.执行async1
console.log('srcipt end') // 5.打印srcipt end
复制代码

打印结果为:

srcipt start
async1 start        
promise1
srcipt end
复制代码

代码片段:

async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
    resolve('promise1 resolve')
  }).then(res => console.log(res))
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')

复制代码

执行逻辑分析:

async function async1 () {
  console.log('async1 start'); // 3.打印async1 start
  await new Promise(resolve => { // 4.执行new Promise()
    console.log('promise1')   // 5.打印promise1
    resolve('promise1 resolve') // 6.更新promise状态,then的回调进入异步微任务
  }).then(res => console.log(res)) // 8.打印promise1 resolve,更新promise状态,await后的代码进入异步微任务
  console.log('async1 success'); // 9.打印async1 success
  return 'async1 end' // 10.返回一个状态值为async1 end的成功Promise
}
console.log('srcipt start') // 1.打印srcipt start
async1().then(res => console.log(res)) // 2.执行async1() ; // 11.打印async1 end
console.log('srcipt end') // 7.打印srcipt end,宏任务执行结束,开始执行微任务
复制代码

打印结果:

srcipt start
async1 start        
promise1
srcipt end
promise1 resolve    
async1 success      
async1 end
复制代码

翻转二叉树

输入:
     4
   /   \
  2     7
 / \   / \
1   3 6   9
输出:
     4
   /   \
  7     2
 / \   / \
9   6 3   1
复制代码

利用迭代翻转左右子树即可。

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */
var invertTree = function(root) {
    change(root);
    return root;
};

function change(root){
    if(!root){
        return;
    }
    const left = root.left;
    const right = root.right;
    root.left = right;
    root.right = left;
    change(left);
    change(right);
}
复制代码

三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。

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

输入:nums = [0]
输出:[]
复制代码

本题是两数之和的升级版,如果使用暴力解法就是三种循环,为了优化解题方法,我们需要找到几个关键点:

  • 三元组中的元素不重复:可利用排序后的数组来遍历元素,跳过重复的数字
  • 复杂度降低为两数之和,然后利用双指针找到所有解
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        # 先排序
        nums.sort()
        ans = list()
        
        # 枚举 a
        for first in range(n):
            # 需要和上一次枚举的数不相同
            if first > 0 and nums[first] == nums[first - 1]:
                continue
            # c 对应的指针初始指向数组的最右端
            third = n - 1
            target = -nums[first] # 降为两数之和
            # 枚举 b
            for second in range(first + 1, n):
                # 需要和上一次枚举的数不相同
                if second > first + 1 and nums[second] == nums[second - 1]:
                    continue
                # 需要保证 b 的指针在 c 的指针的左侧
                while second < third and nums[second] + nums[third] > target:
                    third -= 1
                # 如果指针重合,随着 b 后续的增加
                # 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
                if second == third:
                    break
                if nums[second] + nums[third] == target:
                    ans.append([nums[first], nums[second], nums[third]])
        
        return ans
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享