Hydra

Hydra blog


  • 首页

  • 归档

lc24

发表于 2022-02-27

31. 下一个排列

整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。

例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。
给你一个整数数组 nums ,找出 nums 的下一个排列。

必须 原地 修改,只允许使用额外常数空间。

示例 1:

输入:nums = [1,2,3]
输出:[1,3,2]
示例 2:

输入:nums = [3,2,1]
输出:[1,2,3]
示例 3:

输入:nums = [1,1,5]
输出:[1,5,1]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var nextPermutation = function(nums) {
let i = nums.length - 2
while(i >= 0 && nums[i] >= nums[i + 1]) i--
// console.log(i)

if(i >= 0){
let j = nums.length - 1
while(j >= 0 && nums[j] <= nums[i]) j--
[nums[i], nums[j]] = [nums[j], nums[i]]
}

let left = i + 1, right = nums.length - 1
while(left < right) {
[nums[left], nums[right]] = [nums[right], nums[left]]
left ++
right --
}
};

33. 搜索旋转排序数组

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

示例 1:

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:

输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
示例 3:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
let l = 0, r = nums.length - 1
// 找出分界点
while(l < r){
let mid = Math.floor((l + r) / 2)
if(nums[mid] >= nums[0]) l = mid + 1
else r = mid
}

// 分两次查找
let ans1 = find(target, nums, 0, l)
if(ans1 !== -1) return ans1
let ans2 = find(target, nums, l, nums.length)
return ans2

function find(key, arr, left, right){
let l = left, r = right - 1
while(l <= r){
let mid = Math.floor((l + r) / 2)
if(arr[mid] > key) r = mid - 1
else if(arr[mid] < key) l = mid + 1
else return mid
}
return -1
}

};

34. 在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:

你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
let l = 0, r = nums.length
while(l < r){
let mid = Math.floor((l + r) / 2)
if(nums[mid] >= target) r = mid
else l = mid + 1
}
if(nums[l] !== target) return [-1, -1]
let ans1 = l
l = 0, r = nums.length

while(l < r){
let mid = Math.floor((l + r) / 2 + 1)
if(nums[mid] <= target) l = mid
else r = mid - 1
}

return [ans1, l]
};

39. 组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]
示例 3:

输入: candidates = [2], target = 1
输出: []

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
var combinationSum = function(candidates, target) {
let ans = []
var arrs = new Array()
function dfs(can, tar, num, ans, start){
if(start === can.length) return
if(num < tar){
ans.push(can[start])
dfs(can, tar, num + can[start], ans, start)
ans.pop()

if(start + 1 < can.length) dfs(can, tar, num, ans, start + 1)
}
else if(num >= tar) {
if(num === tar) {
arrs.push(ans.slice())
}
return
}
}
dfs(candidates, target, 0, ans, 0)
return arrs
};

js6

发表于 2022-02-27

函数和闭包

最常见的声明:

1
2
3
4
5
function add(x,y){  
alert(x+y)
}
add(1,2) //弹窗显示:3

函数声明后会发生函数声明提升

1
2
3
4
add(1,2); //弹窗显示:3  
function add(x,y){
alert(x+y)
}

js5

发表于 2022-02-27

this

js的this设计

1
var obj = { foo:  5 };

上述代码会在内存中生成对象{foo: 5}, 然后将对象的内存地址赋值给变量obj, 如果要读取obj.foo, 引擎先从obj拿到内存地址, 从该地址读取原始对象, 返回foo属性

1
var obj = { foo: function () {} };

当属性可能是一个函数时, 引擎会将函数单独保存在内存中, 然后将地址复制给foo的value属性

由于函数是一个单独的值,它可以在不同的环境下执行

1
2
3
4
5
6
7
8
var f = function () {};
var obj = { f: f };

// 单独执行
f()

// obj 环境执行
obj.f()

环境变量

js允许在函数体内部, 引用当前环境的其他变量

由于函数能在不同环境执行, 需要一种机制能在函数内部获取当前运行环境, this指代当前运行环境

obj.foo()通过obj找到foo, 就是在obj环境执行

一旦foo = obj.foo, 变量foo指向函数本身, 执行环境就是全局

js4

发表于 2022-02-27

原型链

一、构造函数

构造函数模式是为了创建一个自定义类,并创建这个类的实例。构造函数模式中有了类和实例的概念,实例和实例相互独立‘’

构造函数是普通的函数,不同的是构造函数需要使用new关键字调用

二、原型

在js中,每当定义一个函数数据类型(普通函数、类)时,会天生自带一个prototype属性, 它指向函数的原型对象,并且这个属性是对象数据类型的值。原型对象相当于一个公共区域,所有同一个类的实例都可以访问这个原型对象,我们可以把共有内容设置到原型对象中

构造函数和实例原型之间的关系:

三、原型链

每一个数据对象类型(普通的对象、实例、prototype)也天生自带一个__ proto __ 属性,属性值是当前实例所属原型(prototype),原型对象有一个属性constructor,它指向函数对象

1
2
3
4
function Person() {}
var person = new Person()
console.log(person.__proto__ === Person.prototype)//true
console.log(Person.prototype.constructor===Person)//true

何为原型链

person → Person → Object ,普通人继承人类,人类继承对象类

当我们访问对象的一个属性或方法时, 会先在对象自身寻找, 如果有则直接用, 没有就去原型中找, 如果找到直接使用, 如果没有就去原型的原型中找, 直到找到Object对象, Object对象的原型不存在, 如果在Object中没找到, 则返回undefine

Object是所有对象数据类型的基类, 在Object.prototype上没有__ proto __ 属性

lc23

发表于 2022-02-26

21. 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例 1:

1
2
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} list1
* @param {ListNode} list2
* @return {ListNode}
*/
var mergeTwoLists = function(list1, list2) {
if(list1 === null) return list2
else if(list2 === null) return list1
else if(list1.val > list2.val) {
list2.next = mergeTwoLists(list1, list2.next)
return list2
}
else {
list1.next = mergeTwoLists(list1.next, list2)
return list1
}
};

//自己的原始想法
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} list1
* @param {ListNode} list2
* @return {ListNode}
*/
var mergeTwoLists = function(list1, list2) {
if(list1 === null) return list2
if(list2 === null) return list1
let head = new ListNode(-1)
let i = head
while(list1 || list2){
let num1 = list1 ? list1.val : 1000
let num2 = list2 ? list2.val : 1000
let num = Math.min(num1, num2)
if(num === num1) list1 = list1.next
else list2 = list2.next

i.next = new ListNode(num)
i = i.next
}
return head.next
};

22. 括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

1
2
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @param {number} n
* @return {string[]}
*/
var generateParenthesis = function(n) {
let ans = []
let dfs = (str, i, j, n) => {

if(i < n && i >= j) dfs(str + '(', i + 1, j, n)
if(j < n && i > j) dfs(str + ')', i, j + 1, n)
if(i === n && j === n) {
ans.push(str)
return
}
}

dfs('', 0, 0, n)
return ans
};

js3

发表于 2022-02-26

promise

romise是es6进行异步编程新解决方案(旧方案是使用回调函数)

1、从语法上说promise是一个构造函数,要接受一个执行器(excutor)(执行器函数是同步调用的),参数是两个resolve和reject函数

resolve是内部定义成功时调用的函数,reject是内部定义失败时调用的函数

2、从功能上说promise对象用来封装异步操作,把异步处理对象和规则进行规范化,采用统一接口编写

Promise.protoype.then(onResolved, onRejected),成功时调用前者,失败调用后者

Promise.protoype.catch只能指定失败的回调

Promise.all([p1, p2, p3]), 括号内部是个数组,数组每一项都是promise对象,它将多个Promise实例包装成一个Promise对象

js2

发表于 2022-02-26

宏任务和微任务(面试题)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
console.log("Start");

setTimeout(function(){
console.log("SetTimeout");
},0);

new Promise(function(resolve,reject){
console.log("Promise");
resolve();
}).then(function(){
console.log("Then");
});

console.log("End");
<script>
1
2
3
4
5
Start
Promise
End
Then
SetTimeout

事件循环流程分析如下:

  • 整体script 作为第一个宏任务进入主线程,遇到console.log,输出Start。
  • 遇到setTimeout,其回调函数被分发到宏任务Event Queue中。
  • 遇到Promise,new Promise直接执行,输出Promise。then被分发到微任务Event Queue中。
  • 遇到console.log,立即执行,输出End。
  • 整体代码script作为第一个宏任务执行结束,看看有哪些微任务?我们发现了then在微任务Event Queue里面,执行
  • ok,第一轮事件循环结束了,我们开始第二轮循环,当然要从宏任务Event Queue开始。我们发现了宏任务Event Queue中setTimeout对应的回调函数,立即执行。
  • 所以代码结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout1');
}, 200);
setTimeout(function() {
console.log('setTimeout2');
new Promise(function(resolve) {
resolve();
}).then(function() {
console.log('then1')
})
new Promise(function(resolve) {
console.log('Promise1');
resolve();
}).then(function() {
console.log('then2')
})
},0)
async1();
new Promise(function(resolve) {
console.log('promise2');
resolve();
}).then(function() {
console.log('then3');
});
console.log('script end');

第一轮事件循环流程分析如下:

  • 整体script作为第一个宏任务进入主线程,async1(),和async12()函数申明,但并没有执行,遇到console.log输出script start。

  • 继续向下执行,遇到setTimeout,把它的回调函数放入宏任务Event Queue。(ps:暂且叫他setTimeout1)

    宏任务 微任务
    setTimeout1
  • 继续向下执行,又遇到一个setTimeout,继续将他放入宏任务Event Queue。(ps:暂且叫他setTimeout2)

    宏任务 微任务
    setTimeout1
    setTimeout2
  • 遇到执行async1(), 进入async的执行上下文之后,遇到console.log输出 async1 start

  • 然后遇到await async2(),由于()的优先级高,所有立即执行async2(),进入async2()的执行上下文。

  • 看到console.log输出async2,之后没有返回值,结束函数,返回undefined,返回async1的执行上下文的await undefined,由于async函数使用await后得语句会被放入一个回调函数中,所以把下面的放入微任务Event Queue中。

    宏任务 微任务
    setTimeout1 async1 => awati 后面的语句
    setTimeout2
  • 结束async1() 遇到Promise,Promise本身是同步的立即执行函数 new Promise直接执行,输出Promise2。then后面的函数被分发到微任务Event Queue中

    宏任务 微任务
    setTimeout1 async1 => awati 后面的语句
    setTimeout2 new Promise() => 后的then
  • 执行完Promise(),遇到console.log,输出script end,这里一个宏任务代码块执行完毕。

  • 在主线程执行的过程中,事件触发线程一直在监听着异步事件, 当主线程空闲下来后,若微任务队列中有任务未执行,执行的事件队列(Event Queue)中有微任务,遇到new Promise()后面的回调函数,执行代码,输出then3。

  • 看到 async1中await后面的回调函数,执行代码,输出async1 end(注意:如果俩个微任务的优先级相同那么任务队列自上而下执行,但是promise的优先级高于async,所以先执行promise后面的回调函数)

  • 自此,第一轮事件循环正式结束,这一轮的结果是输出:

    1
    script start => async1 start => async2 => promise2 => script end => then3 => async1 end
    宏任务 微任务
    setTimeout1
    setTimeout2
  • 那么第二轮时间循环从setTimeout宏任务开始:

  • setTimeout和setInterval的运行机制是,将指定的代码移出本次执行,等到下一轮Event Loop时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮Event Loop时重新判断。因为setTimeout1有200ms的延时,并没到达指定时间,所以先执行setTimeout2这个宏任务

  • 进入到setTimeout2,遇到console.log首先输出setTimeout2;

  • 遇到Promise,Promise本身是同步的立即执行函数new Promise直接执行。then后面的函数被分发到微任务Event Queue中

    宏任务 微任务
    setTimeout1 new Promise() => 后的then1
  • 再次遇到Promise,Promise本身是同步的立即执行函数new Promise直接执行输出promise1。then后面的函数被分发到微任务Event Queue中

    宏任务 微任务
    setTimeout1 new Promise() => 后的then1
    空 new Promise() => 后的then2
  • 主线程执行执行空闲,开始执行微任务队列中依次输出then1和then2。

  • 第二轮事件循环正式结束。第二轮依次输出

    1
    promise1 => then1 => then2
  • 现在任务队列中只有个延时200ms的setTimeout1,在到达200ms后执行setTimeout的回调函数输出setTimeout1

  • 时间循环结束

  • 整段代码,完整的输出为

    1
    script start => async1 start => async2 => promise2 => script end => then3 => async1 end => promise1 => then1 => then2 => setTimeout1

总结

  1. 在执行栈中执行一个宏任务。
  2. 在执行过程中遇到微任务和宏任务,分别添加到微任务队列和宏任务队列中去。
  3. 当前宏任务执行完毕,立即执行微任务队列中的任务(**微任务存在优先级,优先级高的先执行(**promise的优先级高于async**)**)。
  4. 当前微任务队列中的任务执行完毕,检查渲染,GUI线程接管渲染。
  5. 继续执行下一个宏任务从事件队列中取。

js1

发表于 2022-02-26

一、为什么JavaScript是单线程?

js的单线程是由js的诞生原因决定的。早期的js定位是在浏览器中执行的脚本语言,负责与用户进行交互、操作dom等任务。

如果是多线程会带来很大的数据同步问题,HTML5新标准没有改变js单线程的本质

二、任务队列

单线程执行任务会发生排队,不同的任务执行的速度不一样,在IO设备读取数据的时候速度很慢。所以主线程挂起处于等待的任务,运行排在后面的任务,所以任务可以分成两种:同步任务(synchronous)异步任务(asynchronous)。同步任务是指只有前一个任务执行完毕,才能执行后一个任务 异步任务:不进入主线程、而进入“任务队列”的任务,只有“任务队列”通知主线程某个异步任务可以执行了,该任务才会进入主线程

异步执行运行机制

1、所有同步任务都在主线程执行,形成一个执行栈(excution content stack)

2、主线程之外还存在一个“任务队列”(task queue),只要异步任务有了结果,一个事件就会被放入task queue中

3、一旦执行栈中所有同步任务执行完毕,系统就会读取任务队列,对应异步任务结束等待,进入执行栈执行

4、主线程不断重复上面的步骤

三、事件和回调函数

“任务队列”除了io设备事件外,还包括用户产生的事件(鼠标点击、页面滚动),只要执行过回调函数,这些事件发生时就会进入“任务队列”,等待主线程读取

“回调函数”是被主线程挂起的代码,异步函数必须制定回调函数,当主线程开始执行异步任务时,就是在执行回调函数

主线程读取是自动的,只要执行栈一清空,任务队列第一位的事件就进入主线程

四、Event Loop

主线程从“任务队列”读取事件,过程是不断循环的,这种运行机制被称为Event Loop

主线程运行时产生堆和栈,栈中代码调用各种api,他们在“任务队列”中加入各种事件(click,load,done),只要栈中代码执行完毕后,主线程读取“任务队列”,执行事件指定的回调函数

五、定时器

除了防止异步事件,“任务队列”还能放置定时事件,这叫“定时器”。定时器主要由setTimeout和setTimeinterval函数执行,内部运行机制一样,区别在于前者只会执行一次,后者会不断执行

setTimeout有两个参数,第一个是回调函数,第二个是推迟时间

六、Node.js的Event Loop

Node.js也是单线程Event Loop,运行机制不同于浏览器

运行机制

1、V8引擎解析js脚本

2、解析后的代码,调用Node api

3、libuv库负责Node api执行,它将不同任务分配给不同线程,形成loop event,以异步方式将任务返回给v8引擎

4、v8引擎将结果返回用户

此外nodejs还提供了两个与任务队列有关的方法

process.nextTick,setImmediate

process.nextTick在“执行栈”尾部,下一次loop event之前执行回调函数

setImmediate则是在当前“任务队列”尾部添加事件,总是在下一次Event Loop执行

lc22

发表于 2022-02-25

5. 最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = “babad”
输出:”bab”
解释:”aba” 同样是符合题意的答案。
示例 2:

输入:s = “cbbd”
输出:”bb”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* @param {string} s
* @return {string}
*/
var longestPalindrome = function(s) {
let n = s.length
if (n < 2) return s
let begin = 0, maxLen = 1

const arr = new Array(n).fill(false).map(() => new Array(n).fill(false))
for(let i = 0; i < n; i++) arr[i][i] = true
// L为枚举长度
for(let L = 2; L <= n; L++){
for(let i = 0; i < n; i++){
// 由于 j - i + 1 = L
let j = L + i - 1
// 如果长度超出, 则break
if(j >= n) break

if(s[i].charCodeAt() !== s[j].charCodeAt()){
arr[i][j] = false
}
else {
if(j - i < 3) arr[i][j] = true
else arr[i][j] = arr[i + 1][j - 1]
}
if(arr[i][j] && j - i + 1 > maxLen){
maxLen = j - i + 1
begin = i
}
}
}
return s.substr(begin, maxLen)
};

11. 盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明:你不能倾斜容器。

avatar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @param {number[]} height
* @return {number}
*/
var maxArea = function(height) {
function sum (h, t) {
return (t - h) * Math.min(height[h], height[t])
}

let n = height.length
if(n < 3) return Math.min(height[0], height[1])
let head = 0, tail = n - 1
let maxSize = sum(head, tail)
while(head < tail){
if(height[head] < height[tail]) {
maxSize = Math.max(sum(head + 1, tail), maxSize)
++head
}
else {
maxSize = Math.max(sum(head, tail - 1), maxSize)
--tail
}
}
return maxSize
};

15. 三数之和

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

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

示例 1:

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

输入:nums = []
输出:[]
示例 3:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function(nums) {
let ans = []
let n = nums.length
if(n < 3) return ans
//排序后就能用双指针了
nums.sort((a, b) => a - b)
// 固定第一个数
for(let i = 0; i < n; i++){
if(nums[i] > 0) return ans

if(i > 0 && nums[i] === nums[i - 1]) continue

// 双向遍历, 双指针必要条件之一
let left = i + 1, right = n - 1

while(left < right){
// 大了就right--
if(nums[left] + nums[right] > -nums[i]) --right
// 小了就left++
else if(nums[left] + nums[right] < -nums[i]) ++left
// 正好满足条件, 保存答案, 移动左右指针一格
else {
ans.push([nums[left], nums[right], nums[i]])

++left
--right

// 第二 第三个数也不能重复
while(left < right && nums[left] === nums[left - 1]) left++
while(left < right && nums[right] === nums[right + 1]) right--
}
}
}
return ans
};

17. 电话号码的字母组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// dfs

/**
* @param {string} digits
* @return {string[]}
*/
var letterCombinations = function(digits) {
// 初始化数据
const board = ['', '', 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz']
const ans = [], n = digits.length
// 构建递归函数, 另一种构造函数方法
const dfs = (letter, i) => {
if(n === i)
{
ans.push(letter)
return
}
// 展开数组写法
let s = [...board[parseInt(digits[i])]]

i++
for(item of s){
dfs(letter + item, i)
}
}
// 递归入口
dfs('', 0)
// 判断结果长度, 为1则证明是空数组
return ans.length < 2 ? [] : ans
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// bfs

/**
* @param {string} digits
* @return {string[]}
*/
var letterCombinations = function(digits) {
const board = ['', '', 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz']

// 创建队列
let que = ['']

// 广搜的层数
for(let i = 0; i < digits.length; i++){
let curNum = que.length //当前队列中的元素个数
let newQue = []
for(let j = 0; j < curNum; j++){
let newLetter = que.shift() //取出第一个字符串

for(idx of board[parseInt(digits[i])]){
newQue.push(newLetter + idx) //放入新数组
}
}
// 指向新数组
que = newQue
}
return que.length < 2 ? [] : que
};

20. 有效的括号

给定一个只包括 ‘(‘,’)’,’{‘,’}’,’[‘,’]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function(s) {
// 建立映射
const rules = new Map([['(', ')'], ['{', '}'], ['[', ']']])
const stk = [] //模拟栈
for(i of s){
if(i === '(' || i === '[' || i === '{') stk.push(i)
else {
if(stk.length === 0) return false
else {
let pop = stk.pop()
if(i !== rules.get(pop)) return false
}
}
}
// 如果栈不为空 错误
if(stk.length !== 0) return false
return true
};

lc-day21

发表于 2021-12-13

114. 二叉树展开为链表

题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var flatten = function(root) {
const list = [];
dfs(root, list);
const size = list.length;
for(let i = 1; i < size; i++){
const pre = list[i - 1], cur = list[i];
pre.left = null;
pre.right = cur;
}
};

const dfs = (root, list) => {
if(root == null) return;
list.push(root);
dfs(root.left, list);
dfs(root.right, list);
}

3. 无重复字符的最长子串

题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {

const p = new Array(128).fill(0);
const a = 0;
let res = 0;
for(let i = 0, j = 0; i < s.length; i++){
p[s.charCodeAt(i)]++;
while(p[s.charCodeAt(i)] > 1){
p[s.charCodeAt(j)]--;
j++;
}
res = Math.max(i - j + 1, res);

}
return res;
};

使用最小花费爬楼梯

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @param {number[]} cost
* @return {number}
*/
var minCostClimbingStairs = function(cost) {
if(cost.length == 2) return Math.min(cost[0], cost[1]);

for(let i = 2; i < cost.length; i++){
cost[i] += Math.min(cost[i - 1], cost[i - 2]);
}
return Math.min(cost[cost.length - 1], cost[cost.length - 2]);
};

一个简单题还做了半天, 没脸了

62. 不同路径

题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @param {number} m
* @param {number} n
* @return {number}
*/
// 二维数组版本
var uniquePaths = function(m, n) {
const f = new Array(m).fill(0).map(() => new Array(n).fill(0));
for(let i = 0; i < m; i++) f[i][0] = 1;
for(let i = 0; i < n; i++) f[0][i] = 1;

for(let i = 1; i < m; i++){
for(let j = 1; j < n; j++){
f[i][j] += f[i - 1][j] + f[i][j - 1];
}
}
return f[m - 1][n - 1];
};

我们发现数组每一次循环都可以重复利用上一次的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @param {number} m
* @param {number} n
* @return {number}
*/
var uniquePaths = function(m, n) {
const f = new Array(n).fill(0);
//定义一个n列的数列

for(let i = 0; i < n; i++) f[i] = 1;
//初始时所有的格子路径数为1

for(let i = 1; i < m; i++){
//m次循环
for(let j = 1; j < n; j++){
//更新n列, 每次都是在上次的基础上相加
f[j] += f[j - 1];
}
}
return f[n - 1];
};

记忆化搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> dp(m, vector<int>(n, -1));
return dfs(0, 0, m, n, dp);
}
int dfs(int i, int j, int m, int n, vector<vector<int>>& dp){
if(i >= m || j >= n) return 0;
if(i == m - 1 && j == n - 1) return 1;
if(dp[i][j] != -1) return dp[i][j];

int sum = 0;
sum += dfs(i + 1, j, m, n, dp);
sum += dfs(i, j + 1, m, n, dp);
dp[i][j] = sum;
return sum;
}
};
<i class="fa fa-angle-left"></i>1234…7<i class="fa fa-angle-right"></i>

63 日志
1 分类
14 标签
© 2022 hydra
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4