TIP
LeetCode官网
算法题目前面的序号代表题号,[ ]为题目难易程度。题目作者都刷过一遍,有些解法是自己写的。
学习视频请戳 -> 链接
转载请标明出处!!!
给定一个只包括
'('
,')'
,'{'
,'}'
,'['
,']'
的字符串s
,判断字符串是否有效。有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
示例 1:
输入:s = "()" 输出:true
示例 2:
输入:s = "()[]{}" 输出:true
示例 3:
输入:s = "(]" 输出:false
示例 4:
输入:s = "([)]" 输出:false
示例 5:
输入:s = "{[]}" 输出:true
提示:
1 <= s.length <= 10^4
s
仅由括号'()[]{}'
组成
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function(s) {
if(s.length % 2 !== 0) return false
// 转成数组
const arr = s.split("");
// 栈
let stack = [];
while(arr.length){
const c = arr.shift()
if(c === "(" || c === "{" || c === "["){
stack.push(c)
}else{
const s = stack[stack.length - 1]
if((c===")" && s === "(" )||( c==="}" && s === "{") ||( c==="]" && s === "[")){
stack.pop()
}else{
// 要及时return
return false
}
}
}
return stack.length === 0
};
写一个 RecentCounter 类来计算特定时间范围内最近的请求。
请你实现 RecentCounter 类:
RecentCounter() 初始化计数器,请求数为 0 。 int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
保证 每次对 ping 的调用都使用比之前更大的 t 值。
示例 1:
输入:
[“RecentCounter”, “ping”, “ping”, “ping”, “ping”]
[[], [1], [100], [3001], [3002]]
输出:
[null, 1, 2, 3, 3]解释:
RecentCounter recentCounter = new RecentCounter();
recentCounter.ping(1); // requests = [1],范围是 [-2999,1],返回 1
recentCounter.ping(100); // requests = [1, 100],范围是 [-2900,100],返回 2
recentCounter.ping(3001); // requests = [1, 100, 3001],范围是 [1,3001],返回 3
recentCounter.ping(3002); // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3提示:
1 <= t <= 10^9 保证每次对 ping 调用所使用的 t 值都 严格递增 至多调用 ping 方法 10^4 次
var RecentCounter = function() {
this.requests = []
};
/**
* @param {number} t
* @return {number}
*/
RecentCounter.prototype.ping = function(t) {
if(!t) return null
this.requests.push(t)
while(this.requests[0] < (t-3000)){
this.requests.shift()
}
// 返回值是在该范围内的总数
return this.requests.length
};
/**
* Your RecentCounter object will be instantiated and called as such:
* var obj = new RecentCounter()
* var param_1 = obj.ping(t)
*/
请编写一个函数,用于 删除单链表中某个特定节点 。在设计函数时需要注意,你无法访问链表的头节点 head ,只能直接访问 要被删除的节点 。
题目数据保证需要删除的节点 不是末尾节点 。
示例 1:
输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
解释:指定链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9示例 2:
输入:head = [4,5,1,9], node = 1
输出:[4,5,9]
解释:指定链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9提示:
链表中节点的数目范围是 [2, 1000] -1000 <= Node.val <= 1000 链表中每个节点的值都是 唯一 的 需要删除的节点 node 是 链表中的节点 ,且 不是末尾节点
解题思路:将被删节点的值改为下个节点的值,删除下个节点, 相当于变相删除下个节点。
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} node
* @return {void} Do not return anything, modify node in-place instead.
*/
var deleteNode = function(node) {
// node是当前要被删除的节点
node.val = node.next.val;
node.next = node.next.next;
};
给你单链表的头节点 head,请你反转链表,并返回反转后的链表。
示例一
输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
示例二
输入:head = [1,2] 输出:[2,1]
示例三
输入:head = [] 输出:[]
提示:
- 链表中节点的数目范围是
[0, 5000]
-5000 <= Node.val <= 5000
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
if(!head) return head;
// 头指针
let prev = null;
while(head){
const next = head.next;
head.next = prev
prev = head;
head = next;
}
return prev
};
// 时间复杂度:O(n),空间复杂度O(1)
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
输入:l1 = [2,4,3], l2 = [5,6,4] 输出:[7,0,8] 解释:342 + 465 = 807.
提示:
每个链表中的节点数在范围 [1, 100] 内 0 <= Node.val <= 9 题目数据保证列表表示的数字不含前导零
// ---------- edited by ahhc -----------
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var addTwoNumbers = function(l1, l2) {
// 新链表的当前节点
let cur = new ListNode();
// 进位
let c = 0;
let head = cur;
// 遍历链表
while(l1 || l2){
let node = new ListNode();
cur.next = node;
cur = node; // 相当于 cur = cur.next
const a = l1 ? l1.val : 0;
const b = l2 ? l2.val : 0;
const sum = a + b + c;
cur.val = sum >= 10 ? sum - 10 : sum;
c = sum >= 10 ? 1 : 0;
l1 && (l1 = l1.next);
l2 && (l2 = l2.next);
}
// 注意考虑加数溢出的情况
if(c === 1){
let node = new ListNode(1);
cur.next = node;
cur = node;
}
return head.next;
};
// 时间复杂度:O(n), 空间复杂度:O(1)
给定一个已排序的链表的头
head
,删除所有重复的元素,使每个元素只出现一次,返回已排序的链表。示例一
输入:head = [1,1,2] 输出:[1,2]
示例二
输入:head = [1,1,2,3,3] 输出:[1,2,3]
提示:
- 链表中节点数目在范围 [0, 300] 内
- -100 <= Node.val <= 100
- 题目数据保证链表已经按升序 排列
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var deleteDuplicates = function(head) {
// 遍历链表
let p = head;
while(p){ // 或者 while(p && p.next),就不用写?.符号
if(p.val === p.next?.val){
p.next = p.next.next
}else{
p = p.next;
}
}
return head;
};
// 时间复杂度:O(n),空间复杂度:O(1)
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例一
输入:head = [3,2,0,-4], pos = 1 输出:true 解释:链表中有一个环,其尾部连接到第二个节点。
示例二
输入:head = [1], pos = -1 输出:false 解释:链表中没有环。
提示:
链表中节点的数目范围是 [0, 104] -105 <= Node.val <= 105 pos 为 -1 或者链表中的一个 有效索引 。
// ---------- edited by ahhc ---------
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {boolean}
*/
var hasCycle = function(head) {
// 快慢指针方法,同一个起点
let fast = slow = head;
// 当快慢指针走到链表尾部,快慢指针仍未相遇时,即不存在闭环
while(slow && fast && fast.next){
slow = slow.next;
fast = fast.next.next;
if(fast === slow) return true
}
return false;
};
// 时间复杂度:O(n),空间复杂度:O(1)
给定两个数组
nums1
和nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。示例一
输入:nums1 = [1,2,2,1], nums2 = [2,2] 输出:[2]
示例二
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的提示:
1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 1000
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number[]}
*/
var intersection = function(nums1, nums2) {
return [...new Set(nums1)].filter(a => nums2.includes(a))
};
// 时间复杂度:O(n^2),空间复杂度:O(1)
给定两个数组
nums1
和nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。示例一
输入:nums1 = [1,2,2,1], nums2 = [2,2] 输出:[2]
示例二
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的提示:
1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 1000
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number[]}
*/
var intersection = function(nums1, nums2) {
// 利用字典的唯一特性
let myMap = new Map();
nums1.forEach(n => {
myMap.set(n, true)
})
let res = [];
nums2.forEach(n => {
if(myMap.get(n)){
res.push(n);
myMap.delete(n);
}
})
return res;
};
给定一个只包括
'('
,')'
,'{'
,'}'
,'['
,']'
的字符串s
,判断字符串是否有效。有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
示例 1:
输入:s = "()" 输出:true
示例 2:
输入:s = "()[]{}" 输出:true
示例 3:
输入:s = "(]" 输出:false
示例 4:
输入:s = "([)]" 输出:false
示例 5:
输入:s = "{[]}" 输出:true
提示:
1 <= s.length <= 10^4
s
仅由括号'()[]{}'
组成
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function(s) {
// 长度为奇数则直接返回
if(s.length % 2 !== 0) return false;
const myMap = new Map();
myMap.set("(",")");
myMap.set("{","}");
myMap.set("[","]");
let res = [];
// 注意:这里不能用forEach循环,return false无法阻止forEach循环
for(let i = 0; i < s.length; i++){
const ch = s[i];
if(myMap.has(ch)){
res.push(ch)
}else{
const n = res[res.length-1];
if(myMap.get(n) === ch){
res.pop()
}else{
// 记得return false!!!
return false;
}
}
}
return res.length === 0;
};
// 时间复杂度:O(n),空间复杂度:O(1)
给定一个整数数组
nums
和一个整数目标值target
,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9 输出:[0,1] 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6 输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6 输出:[0,1]
提示:
2 <= nums.length <= 104 -109 <= nums[i] <= 109 -109 <= target <= 109 只会存在一个有效答案
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
// 利用字典的特性
const myMap = new Map();
for(let i = 0; i < nums.length; i++){
const n = nums[i];
const n2 = target - n;
if(myMap.has(n2)){
return [myMap.get(n2), i];
}else{
myMap.set(n,i)
}
}
return []
};
// --------- edited by ahhc --------- set的顺序也很重要,下面那么写就多了一些不必要的判断
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
// 利用字典的特性
const myMap = new Map();
for(let i = 0; i < nums.length; i++){
let n = nums[i];
if(!myMap.has(n)){
myMap.set(n, i);
}
let minus = target - n;
// 注意:加数和被加数不能是同一个数
if(myMap.has(minus)){
const index = myMap.get(minus);
if(index !== i)
return [index, i]
}
}
return []
};
// 时间复杂度:O(n),空间复杂度:O(1)
给定一个字符串
s
,请你找出其中不含有重复字符的 最长子串 的长度。示例 1:
输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
提示:
0 <= s.length <= 5 * 104 s 由英文字母、数字、符号和空格组成
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
// 滑动窗口+字典存储滑动窗口值保证不重复
let l = 0;
let res = 0;
const map = new Map();
for(let r = 0; r < s.length; r++){
const ch = s[r];
// 左指针不能往回走
if(map.has(ch) && map.get(ch) >= l){
// 移动左指针到重复字符的下一位
l = map.get(ch) + 1;
}
res = Math.max(res, r - l + 1);
map.set(ch, r);
}
return res;
};
// 时间复杂度: O(n),空间复杂度:O(m),m是字符串的个数
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
- 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
- 如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC" 输出:"BANC"
示例 2:
输入:s = "a", t = "a" 输出:"a"
示例 3:
输入: s = "a", t = "aa" 输出: "" 解释: t 中两个字符 'a' 均应包含在 s 的子串中, 因此没有符合条件的子字符串,返回空字符串。
提示:
1 <= s.length, t.length <= 10^5 s 和 t 由英文字母组成
进阶:你能设计一个在 o(n) 时间内解决此问题的算法吗?
/**
* @param {string} s
* @param {string} t
* @return {string}
*/
var minWindow = function(s, t) {
// 统计字符串t的所有元素个数
const need = new Map();
for(let i of t){
need.set(i, need.get(i) ? need.get(i) + 1 : 1);
}
// 保存结果字符串
let res = '';
// 左右指针开始循环
let l = r = 0;
// 需要的类型个数
let needSize = need.size;
// 先循环右指针
while(r < s.length){
const n = s[r];
if(need.has(n)){
need.set(n, need.get(n) - 1)
if(need.get(n) === 0) needSize--;
}
while(needSize === 0){
// 循环左指针,缩短字符串长度
const newRes = s.substring(l, r+1); // 不包括end
if(!res || newRes.length < res.length) res = newRes;
const c = s[l];
if(need.has(c)){
need.set(c, need.get(c) + 1)
if(need.get(c) === 1) needSize++;
}
l++;
}
r++;
}
return res;
};
// 时间复杂度:O(m+n),m是字符串s的长度,n是字符串t的长度
// 空间复杂度:O(n),n是字符串t的长度
const tree = {
val: 'a',
children: [
{
val: 'b',
children: [
{
val: 'd',
children: [],
},
{
val: 'e',
children: [],
}
],
},
{
val: 'c',
children: [
{
val: 'f',
children: [],
},
{
val: 'g',
children: [],
}
],
}
],
};
const dfs = (root) => {
console.log(root.val);
root.children.forEach(dfs); // 当children为空时会停止递归
// 相当于
// root.children.forEach(child => dfs(child))
}
const tree = {
val: 'a',
children: [
{
val: 'b',
children: [
{
val: 'd',
children: [],
},
{
val: 'e',
children: [],
}
],
},
{
val: 'c',
children: [
{
val: 'f',
children: [],
},
{
val: 'g',
children: [],
}
],
}
],
};
const bfs = (root) => {
// 根节点先入队
const q = [root];
while(q.length > 0){
// 队头出队
const n = q.shift();
console.log(n.val);
// 队头的child分别入队
n.child.forEach(child => {
q.push(child)
});
}
};
- 二叉树中每个节点最多只能有两个子节点。
- 在JS中通常用Object来模拟二叉树。
const bt = {
val: 1,
left: {
val: 2,
left: {
val: 4,
left: null,
right: null,
},
right: {
val: 5,
left: null,
right: null,
},
},
right: {
val: 3,
left: {
val: 6,
left: null,
right: null,
},
right: {
val: 7,
left: null,
right: null,
},
},
};
const preorder = (root) => {
if(!root) return;
console.log(root.val)
// 先访问左子树
preorder(root.left);
preorder(root.right);
}
preorder(bt); // 1245367
const bt = {
val: 1,
left: {
val: 2,
left: {
val: 4,
left: null,
right: null,
},
right: {
val: 5,
left: null,
right: null,
},
},
right: {
val: 3,
left: {
val: 6,
left: null,
right: null,
},
right: {
val: 7,
left: null,
right: null,
},
},
};
const inorder = (root) => {
if(!root) return;
inorder(root.left);
console.log(root.val);
inorder(root.right);
};
inorder(bt); // 4251637
const bt = {
val: 1,
left: {
val: 2,
left: {
val: 4,
left: null,
right: null,
},
right: {
val: 5,
left: null,
right: null,
},
},
right: {
val: 3,
left: {
val: 6,
left: null,
right: null,
},
right: {
val: 7,
left: null,
right: null,
},
},
};
const postorder = (root) => {
if(!root) return;
postorder(root.left);
postorder(root.right);
console.log(root.val);
};
postorder(bt); // 4526731
const bt = {
val: 1,
left: {
val: 2,
left: {
val: 4,
left: null,
right: null,
},
right: {
val: 5,
left: null,
right: null,
},
},
right: {
val: 3,
left: {
val: 6,
left: null,
right: null,
},
right: {
val: 7,
left: null,
right: null,
},
},
};
// 先序遍历
const preorder = (root) => {
if(!root) return;
// 利用栈实现
const stack = [root];
while(stack.length){
// 访问根节点
const n = stack.pop();
console.log(n.val);
// 注意:这里需要右子树先入栈,因为这里用的是pop方法
n.right && stack.push(n.right);
n.left && stack.push(n.left);
}
};
preorder(bt); // 1245367
// 中序遍历
const inorder = (root) => {
if(!root) return;
const stack = [];
// 利用指针找到叶子节点
let p = root;
while(stack.length || p){
while(p){
stack.push(p);
p = p.left;
}
const n = stack.pop();
console.log(n.val);
p = n.right;
}
};
inorder(bt); // 4251637
// 后序遍历:更改顺序为根右左,利用先序遍历实现,再逆序输出
const postorder = (root) => {
if(!root) {return};
const outputStack = [];
const stack = [root];
while(stack.length){
const n = stack.pop();
outputStack.push(n);
// 后进先出
n.left && stack.push(n.left);
n.right && stack.push(n.right);
}
// 逆序输出
while(outputStack.length){
const n = outputStack.pop();
console.log(n.val)
}
}
postorder(bt); // 4526731
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7], 3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var maxDepth = function(root) {
// 存储结果变量
let res = 0;
// 深度优先搜索, l表示层数
const dfs = (root, l) => {
if(!root) return;
if(!root.left && !root.right){
res = Math.max(res, l);
}
root.left && dfs(root.left, l+1);
root.right && dfs(root.right, l + 1);
}
dfs(root, 1);
return res;
};
// 时间复杂度:o(n), n是树的节点数;空间复杂度:最差-o(n),最好-o(log2n)
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
输入:root = [3,9,20,null,null,15,7]
输出:2
示例 2:输入:root = [2,null,3,null,4,null,5,null,6]
输出:5提示:
树中节点数的范围在 [0, 10^5] 内
-1000 <= Node.val <= 1000
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var minDepth = function(root) {
if(!root) return 0;
// 广度优先搜索
let q = [[root, 1]];
while(q.length){
const [n,l] = q.shift();
if(!n.left && !n.right){
return l;
}
n.left && q.push([n.left, l+1]);
n.right && q.push([n.right, l+1]);
}
};
// 时间复杂度:o(n),n是树的节点数;空间复杂度:o(n),n是树的节点树
给你二叉树的根节点
root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:输入:root = [1]
输出:[[1]]
示例 3:输入:root = []
输出:[]提示:
树中节点数目在范围 [0, 2000] 内
-1000 <= Node.val <= 1000
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrder = function(root) {
if(!root) return [];
const q = [root]; // 记录层级
// 存储结果数组
const res = [];
while(q.length){
let len = q.length;
res.push([]);
while(len--){
const n = q.shift();
res[res.length-1].push(n.val);
n.left && q.push(n.left);
n.right && q.push(n.right)
}
}
return res;
};
// 时间复杂度:o(n),n是树的节点数;空间复杂度:o(n)
// ---------- 逐层遍历 -------------
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrder = function(root) {
if(!root) return [];
const q = [root];
const res = [];
// 逐层遍历
while(q.length){
let len = q.length;
res.push([]);
while(len--){
const n = q.shift();
res[res.length - 1].push(n.val);
n.left && q.push(n.left);
n.right && q.push(n.right);
}
}
return res;
};
给定一个二叉树的根节点
root
,返回 它的 中序 遍历 。输入:root = [1,null,2,3]
输出:[1,3,2]示例 2:
输入:root = []
输出:[]示例 3:
输入:root = [1]
输出:[1]提示:
树中节点数目在范围 [0, 100] 内 -100 <= Node.val <= 100
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function(root) {
if(!root) return [];
const q = [];
// 利用指针
let p = root;
// 存储结果数组
const res = [];
while(q.length || p){
// 找到最左的叶子节点
while(p){
q.push(p);
p = p.left;
}
const n = q.pop();
res.push(n.val);
p = n.right;
}
return res;
};
// 时间复杂度:O(n),n是树的节点数;空间复杂度:O(n)
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
叶子节点 是指没有子节点的节点。
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。输入:root = [1,2,3], targetSum = 5
输出:false
解释:树中存在两条根节点到叶子节点的路径:
(1 --> 2): 和为 3
(1 --> 3): 和为 4
不存在 sum = 5 的根节点到叶子节点的路径。示例 3:
输入:root = [], targetSum = 0
输出:false
解释:由于树是空的,所以不存在根节点到叶子节点的路径。提示:
树中节点的数目在范围 [0, 5000] 内 -1000 <= Node.val <= 1000 -1000 <= targetSum <= 1000
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @param {number} targetSum
* @return {boolean}
*/
var hasPathSum = function(root, targetSum) {
if(!root) return false;
// 存放结果
let flag = false;
// 深度优先搜索
const dfs = (root, target) => {
target = target - root.val;
if(target === 0 && !root.left && !root.right) flag = true;
root.left && dfs(root.left, target);
root.right && dfs(root.right, target);
}
dfs(root, targetSum);
return flag;
};
// 时间复杂度:O(n),n是树的节点数;空间复杂度:最坏O(n),最好O(log2n)
const json = {
a: { b: { c: 1 } },
d: [1, 2],
};
const dfs = (n, path) => {
console.log(n, path);
Object.keys(n).forEach(k=>{
dfs(n[k], path.concat(k))
})
}
dfs(json, [])
/* console.log
{ a: { b: { c: 1 } }, d: [ 1, 2 ] } []
{ b: { c: 1 } } [ 'a' ]
{ c: 1 } [ 'a', 'b' ]
1 [ 'a', 'b', 'c' ]
[ 1, 2 ] [ 'd' ]
1 [ 'd', '0' ]
2 [ 'd', '1' ]
*/
// React, 类组件内
const json = [
{
title: '一',
key: "1",
children: [
{
title: '1.1',
key: "2",
children: []
}
]
},
{
title: '二',
key: "3",
children: [
{
title: '2.1',
key: "4",
children: []
}
]
}
]
dfs = (n) => {
return (
{n.children.map(this.dfs)}
)
}
json.map(this.dfs)
图是网络结构的抽象模型,是一组由边连接的节点。
图可以表示任何二元关系,比如道路、航班。
JS中没有图,但是可以用Object和Array构建图。
图的表示法:邻接矩阵、邻接表、关联矩阵…
const graph = {
0: [1, 2],
1: [2],
2: [0, 3],
3: [3]
};
// 深度优先遍历
const visited = new Set();
const dfs = (n) => {
console.log(n);
visited.add(n);
graph[n].forEach(c => {
if(!visited.has(c)){
dfs(c);
}
});
};
dfs(2); // 起点是2, 2013
const graph = {
0: [1, 2],
1: [2],
2: [0, 3],
3: [3]
};
const visited = new Set();
const q = [2];
while (q.length) {
const n = q.shift();
console.log(n);
visited.add(n);
graph[n].forEach(c => {
if (!visited.has(c)) {
q.push(c);
}
});
}
// console.log 2031
// ------ 这种方法更好,避免q里面还有元素没有被访问到但是已经存在在visited的情况 ------
const visited = new Set();
visited.add(2);
const q = [2];
while (q.length) {
const n = q.shift();
console.log(n);
graph[n].forEach(c => {
if (!visited.has(c)) {
q.push(c);
visited.add(n);
}
});
}
有效数字(按顺序)可以分成以下几个部分:
- 一个 小数 或者 整数
- (可选)一个 ‘e’ 或 ‘E’ ,后面跟着一个整数
小数(按顺序)可以分成以下几个部分:
- (可选)一个符号字符(’+’ 或 ‘-’)
- 下述格式之一:
1. 至少一位数字,后面跟着一个点 ‘.’
2. 至少一位数字,后面跟着一个点 ‘.’ ,后面再跟着至少一位数字
3. 一个点 ‘.’ ,后面跟着至少一位数字整数(按顺序)可以分成以下几个部分:
- (可选)一个符号字符(’+’ 或 ‘-’)
- 至少一位数字
部分有效数字列举如下:[“2”, “0089”, “-0.1”, “+3.14”, “4.”, “-.9”, “2e10”, “-90E3”, “3e+7”, “+6e-1”, “53.5e93”, “-123.456e789”]
部分无效数字列举如下:[“abc”, “1a”, “1e”, “e3”, “99e2.5”, “–6”, “-+3”, “95a54e53”]
给你一个字符串 s ,如果 s 是一个 有效数字 ,请返回 true 。
示例 1:
输入:s = “0”
输出:true示例 2:
输入:s = “e”
输出:false示例 3:
输入:s = “.”
输出:false提示:
1 <= s.length <= 20 s 仅含英文字母(大写和小写),数字(0-9),加号 '+' ,减号 '-' ,或者点 '.' 。
解题思路
/**
* @param {string} s
* @return {boolean}
*/
var isNumber = function(s) {
// 构建图
const graph = {
// 状态0,blank其实没必要,直接s.trim()更好
0: {
"blank": 0,
"digit": 6,
"sign": 1,
".": 2
},
1: {
"digit": 6,
".": 2
},
2: {
"digit": 3
},
3: {
"digit": 3,
"e": 4
},
4: {
"sign": 7,
"digit": 5
},
5: {
"digit": 5
},
6: {
"digit": 6,
".": 3,
"e": 4
},
7: {
"digit": 5
}
};
// 保存状态
let state = 0;
for(let c of s){
if(c >= '0' && c <= "9"){
c = "digit";
}else if(c === "+" || c === "-"){
c = "sign";
}else if(c === " "){
c = "blank";
}else if(c === "E"){
c = "e"
}
state = graph[state][c]; // 更新状态,无路可走时为undefined
if(state === undefined) return false;
}
if(state === 3 || state === 5 || state === 6){
return true;
}
return false;
};
// 时间复杂度:O(n),n是字符串的长度;空间复杂度:O(1)
有一个
m × n
的矩形岛屿,与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界,而 “大西洋” 处于大陆的右边界和下边界。这个岛被分割成一个由若干方形单元格组成的网格。给定一个
m x n
的整数矩阵heights
,heights[r][c]
表示坐标(r, c)
上单元格 高于海平面的高度 。岛上雨水较多,如果相邻单元格的高度 小于或等于 当前单元格的高度,雨水可以直接向北、南、东、西流向相邻单元格。水可以从海洋附近的任何单元格流入海洋。
返回 网格坐标 result 的 2D列表 ,其中 result[i] = [ri, ci] 表示雨水可以从单元格 (ri, ci) 流向 太平洋和大西洋 。
示例 1:
输入: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]] 输出: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]
示例 2:
输入: heights = [[2,1],[1,2]] 输出: [[0,0],[0,1],[1,0],[1,1]]
提示:
m == heights.length
n == heights[r].length
1 <= m, n <= 200
0 <= heights[r][c] <= 10^5
解题思路
/**
* @param {number[][]} heights
* @return {number[][]}
*/
var pacificAtlantic = function(heights) {
// 特殊情况
if(!heights || !heights[0]) return [];
// 获取行数和列数
const m = heights.length;
const n = heights[0].length;
// 构建存储两个海洋的矩阵
const flow1 = Array.from({length: m}, () => new Array(n).fill(false));
const flow2 = Array.from({length: m}, () => new Array(n).fill(false));
// 图的深度优先搜索, r是行数,c是列数,flow是海洋类型
const dfs = (r, c, flow) => {
flow[r][c] = true;
// 遍历当前节点的所有相邻节点
[[r+1, c], [r-1, c], [r, c+1], [r, c-1]].forEach(([nr, nc]) => {
// 满足继续深度搜索的条件
if(
// 当前节点在当前矩阵中
nr >= 0 && nr < m && nc >= 0 && nc < n &&
// 没有被访问过,防止死循环
!flow[nr][nc] &&
// 高度大于等于当前节点
heights[nr][nc] >= heights[r][c]
){
dfs(nr, nc, flow)
}
})
}
// 从海岸线开始遍历矩阵,多管齐下
for(let r = 0; r < m; r++){
dfs(r, 0, flow1);
dfs(r, n-1, flow2);
}
for(let c = 0; c < n; c++){
dfs(0, c, flow1);
dfs(m-1, c, flow2);
}
// 遍历两个存储结果矩阵,找到同时能流进太平洋和大西洋的节点
const res = [];
for(let r = 0; r < m; r++){
for(let c = 0; c < n; c++){
if(flow1[r][c] && flow2[r][c]){
res.push([r,c])
}
}
}
return res;
};
// 时间复杂度:O(mn);空间复杂度:O(mn);m是矩阵行数,n是矩阵列数
给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。
图中的每个节点都包含它的值 val(int) 和其邻居的列表(list[Node])。
class Node { public int val; public List<Node> neighbors; }
测试用例格式:
简单起见,每个节点的值都和它的索引相同。例如,第一个节点值为 1(val = 1),第二个节点值为 2(val = 2),以此类推。该图在测试用例中使用邻接列表表示。
邻接列表 是用于表示有限图的无序列表的集合。每个列表都描述了图中节点的邻居集。
给定节点将始终是图中的第一个节点(值为 1)。你必须将 给定节点的拷贝 作为对克隆图的引用返回。
输入:adjList = [[2,4],[1,3],[2,4],[1,3]] 输出:[[2,4],[1,3],[2,4],[1,3]] 解释: 图中有 4 个节点。 节点 1 的值是 1,它有两个邻居:节点 2 和 4 。 节点 2 的值是 2,它有两个邻居:节点 1 和 3 。 节点 3 的值是 3,它有两个邻居:节点 2 和 4 。 节点 4 的值是 4,它有两个邻居:节点 1 和 3 。
输入:adjList = [[]] 输出:[[]] 解释:输入包含一个空列表。该图仅仅只有一个值为 1 的节点,它没有任何邻居。
示例 3:
输入:adjList = [] 输出:[] 解释:这个图是空的,它不含任何节点。
输入:adjList = [[2],[1]] 输出:[[2],[1]]
提示:
- 节点数不超过 100 。
- 每个节点值 Node.val 都是唯一的,1 <= Node.val <= 100。
- 无向图是一个简单图,这意味着图中没有重复的边,也没有自环。
- 由于图是无向的,如果节点 p 是节点 q 的邻居,那么节点 q 也必须是节点 p 的邻居。
图是连通图,你可以从给定节点访问到所有节点。
解题思路
/**
* // Definition for a Node.
* function Node(val, neighbors) {
* this.val = val === undefined ? 0 : val;
* this.neighbors = neighbors === undefined ? [] : neighbors;
* };
*/
/**
* @param {Node} node
* @return {Node}
*/
// ------ 深度优先遍历解法 ------
var cloneGraph = function(node) {
if(!node) return;
// 存储访问过的节点
const visited = new Map();
const dfs = (n) => {
const newNode = new Node(n.val);
visited.set(n, newNode);
(n.neighbors || []).forEach(ne => {
if(!visited.has(ne)){
dfs(ne);
}
newNode.neighbors.push(visited.get(ne));
})
}
dfs(node);
return visited.get(node);
};
// 时间复杂度:O(n); 空间复杂度:O(n)
// -------------------------------------------------------------
/**
* // Definition for a Node.
* function Node(val, neighbors) {
* this.val = val === undefined ? 0 : val;
* this.neighbors = neighbors === undefined ? [] : neighbors;
* };
*/
/**
* @param {Node} node
* @return {Node}
*/
// ------ 广度优先遍历解法 ------
var cloneGraph = function(node) {
if(!node) return;
// 存储访问过的节点
const visited = new Map();
const q = [node];
// 标记初始节点
visited.set(node, new Node(node.val));
while(q.length){
const n = q.shift(); // 队列,先进先出
(n.neighbors || []).forEach(ne => {
if(!visited.has(ne)){
q.push(ne);
visited.set(ne, new Node(ne.val));
}
visited.get(n).neighbors.push(visited.get(ne));
})
}
return visited.get(node);
};
// 时间复杂度:O(n); 空间复杂度:O(n)
应用场景:
class MinHeap {
constructor(){
this.heap = [];
}
swap(i1,i2){
const temp = this.heap[i1];
this.heap[i1] = this.heap[i2];
this.heap[i2] = temp;
}
getParentIndex(i){
return (i-1) >> 1;
}
getLeftIndex(i){
return i * 2 + 1;
}
getRightIndex(i){
return i * 2 + 2;
}
shiftUp(index){
if(index === 0) return;
// 上移操作
const parentIndex = this.getParentIndex(index);
if(this.heap[parentIndex] > this.heap[index]){
this.swap(parentIndex, index);
this.shiftUp(parentIndex);
}
}
shiftDown(index){
const leftIndex = this.getLeftIndex(index);
const rightIndex = this.getRightIndex(index);
if(this.heap[leftIndex] < this.heap[index]){
this.swap(leftIndex, index);
this.shiftDown(leftIndex);
}
if(this.heap[rightIndex] < this.heap[index]){
this.swap(rightIndex, index);
this.shiftDown(rightIndex);
}
}
// 插入操作
insert(value){
this.heap.push(value);
this.shiftUp(this.heap.length - 1);
}
// 删除堆顶元素
pop(){
this.heap[0] = this.heap.pop();
this.shiftDown(0);
}
peek(){
return this.heap[0];
}
size(){
return this.heap.length;
}
}
const h = new MinHeap();
h.insert(3);
h.insert(2);
h.insert(1);
h.pop();
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4提示:
1 <= k <= nums.length <= 104 -104 <= nums[i] <= 104
解题思路:
class MinHeap {
constructor(){
this.heap = [];
}
swap(i1,i2){
const temp = this.heap[i1];
this.heap[i1] = this.heap[i2];
this.heap[i2] = temp;
}
getParentIndex(i){
return (i-1) >> 1;
}
getLeftIndex(i){
return i * 2 + 1;
}
getRightIndex(i){
return i * 2 + 2;
}
shiftUp(index){
if(index === 0) return;
// 上移操作
const parentIndex = this.getParentIndex(index);
if(this.heap[parentIndex] > this.heap[index]){
this.swap(parentIndex, index);
this.shiftUp(parentIndex);
}
}
shiftDown(index){
const leftIndex = this.getLeftIndex(index);
const rightIndex = this.getRightIndex(index);
if(this.heap[leftIndex] < this.heap[index]){
this.swap(leftIndex, index);
this.shiftDown(leftIndex);
}
if(this.heap[rightIndex] < this.heap[index]){
this.swap(rightIndex, index);
this.shiftDown(rightIndex);
}
}
// 插入操作
insert(value){
this.heap.push(value);
this.shiftUp(this.heap.length - 1);
}
// 删除堆顶元素
pop(){
this.heap[0] = this.heap.pop();
this.shiftDown(0);
}
peek(){
return this.heap[0];
}
size(){
return this.heap.length;
}
};
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var findKthLargest = function(nums, k) {
const heap = new MinHeap();
nums.forEach(i => {
heap.insert(i);
if(heap.size() > k){
heap.pop();
}
})
return heap.peek();
};
// 时间复杂度:O(nlogk);空间复杂度:O(k)
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]示例 2:
输入: nums = [1], k = 1
输出: [1]提示:
1 <= nums.length <= 10^5 k 的取值范围是 [1, 数组中不相同的元素的个数] 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var topKFrequent = function(nums, k) {
// 时间复杂度O(nlog(n))
const map = new Map();
nums.forEach(n => {
map.set(n, map.has(n) ? map.get(n) + 1 : 1);
});
// 排序
const arr = Array.from(map);
arr.sort((a,b) => b[1] - a[1]);
return arr.slice(0,k).map( n => n[0]);
};
// ------------ 方法二:最小堆 ---------
// 最小堆轮子
class MinHeap{
constructor(){
this.heap = [];
}
// 交换两个节点的值
swap(i1,i2){
const temp = this.heap[i1];
this.heap[i1] = this.heap[i2];
this.heap[i2] = temp;
}
// 获取父节点
getParentIndex(index){
return (index-1) >> 1;
}
getLeftIndex(index){
return index * 2 + 1;
}
getRightIndex(index){
return index * 2 + 2;
}
shiftUp(index){
if(index === 0) return; // 终止条件!!
const parentIndex = this.getParentIndex(index);
if(this.heap[parentIndex] && this.heap[parentIndex].value > this.heap[index].value){
this.swap(index,parentIndex);
this.shiftUp(parentIndex);
}
}
// 下移
shiftDown(index){
const leftIndex = this.getLeftIndex(index);
const rightIndex = this.getRightIndex(index);
if(this.heap[leftIndex] && this.heap[leftIndex].value < this.heap[index].value){
this.swap(leftIndex,index);
this.shiftDown(leftIndex);
}
if(this.heap[rightIndex] && this.heap[rightIndex].value < this.heap[index].value){
this.swap(rightIndex,index);
this.shiftDown(rightIndex);
}
}
insert(value){
this.heap.push(value);
// 上移
this.shiftUp(this.heap.length-1);
}
// 删除堆顶节点
pop(){
this.heap[0] = this.heap.pop();
this.shiftDown(0);
}
peek(){
return this.heap[0];
}
size(){
return this.heap.length;
}
}
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var topKFrequent = function(nums, k) {
// 时间复杂度O(nlogk)
const map = new Map();
nums.forEach(n => {
map.set(n, map.has(n) ? map.get(n) + 1 : 1);
});
//
// 利用堆来降低时间复杂度
const h = new MinHeap();
map.forEach((value, key) => {
h.insert({value,key});
if(h.size() > k){
h.pop();
}
})
return h.heap.map(n => n.key);
};
// 空间复杂度:最坏 - O(n)
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]] 输出:[1,1,2,3,4,4,5,6] 解释:链表数组如下: [ 1->4->5, 1->3->4, 2->6 ] 将它们合并到一个有序链表中得到。 1->1->2->3->4->4->5->6
示例 2:
输入:lists = [] 输出:[]
示例 3:
输入:lists = [[]] 输出:[]
提示:
k == lists.length 0 <= k <= 10^4 0 <= lists[i].length <= 500 -10^4 <= lists[i][j] <= 10^4 lists[i] 按 升序 排列 lists[i].length 的总和不超过 10^4
解题思路:
class MinHeap {
constructor(){
this.heap = [];
}
swap(i1,i2){
const temp = this.heap[i1];
this.heap[i1] = this.heap[i2];
this.heap[i2] = temp;
}
getParentIndex(i){
return (i-1) >> 1;
}
getLeftIndex(i){
return i * 2 + 1;
}
getRightIndex(i){
return i * 2 + 2;
}
shiftUp(index){
if(index === 0) return;
// 上移操作
const parentIndex = this.getParentIndex(index);
if(this.heap[parentIndex] && this.heap[parentIndex].val > this.heap[index].val){
this.swap(parentIndex, index);
this.shiftUp(parentIndex);
}
}
shiftDown(index){
const leftIndex = this.getLeftIndex(index);
const rightIndex = this.getRightIndex(index);
if(this.heap[leftIndex] && this.heap[leftIndex].val < this.heap[index].val){
this.swap(leftIndex, index);
this.shiftDown(leftIndex);
}
if(this.heap[rightIndex] && this.heap[rightIndex].val < this.heap[index].val){
this.swap(rightIndex, index);
this.shiftDown(rightIndex);
}
}
// 插入操作
insert(value){
this.heap.push(value);
this.shiftUp(this.heap.length - 1);
}
// 删除堆顶元素
pop(){
if(this.size() === 1) return this.heap.shift();
const top = this.heap[0];
this.heap[0] = this.heap.pop();
this.shiftDown(0);
return top;
}
peek(){
return this.heap[0];
}
size(){
return this.heap.length;
}
}
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode[]} lists
* @return {ListNode}
*/
var mergeKLists = function(lists) {
// 新建链表存储结果
const res = new ListNode(0);
let p = res;
const h = new MinHeap();
// 将排序链表中的所有头节点都插入到堆中
lists.forEach(l => {
if(l) h.insert(l);
});
while(h.size()){
// 获得堆顶元素
const n = h.pop();
p.next = n;
p = p.next;
n.next && h.insert(n.next);
}
return res.next; // 注意,这里是res.next,res的第一个节点是空
};
// 时间复杂度:O(nlogk),空间复杂度:O(k)
Array.prototype.bubbleSort = function(){
for(let i = 0; i < this.length - 1; i++){
for(let j = 0; j < this.length - 1 - i; j++){
if(this[j] > this[j+1]){
const temp = this[j];
this[j] = this[j+1];
this[j+1] = temp;
}
}
}
}
const arr = [5,4,3,2,1];
arr.bubbleSort();
console.log(arr); // [1,2,3,4,5]
// 时间复杂度:O(n^2);空间复杂度:O(1)
Array.prototype.selectionSort = function(){
for(let i = 0; i < this.length - 1; i++){
let indexMin = i;
for(let j = i; j < this.length; j++){
if(this[j] < this[indexMin]){
indexMin = j;
}
}
if(indexMin !== i){
const temp = this[i];
this[i] = this[indexMin];
this[indexMin] = temp;
}
}
}
const arr = [3,4,2,1,5];
arr.selectionSort();
console.log(arr); // [1,2,3,4,5]
// 时间复杂度:O(n^2);空间复杂度:O(1)
时间复杂度也是O(n^2),不过在排序小型数组时比前面两个算法要好。
Array.prototype.insertSort = function(){
for(let i = 1; i < this.length; i++){
let temp = this[i];
let j = i;
while(j > 0){
if(this[j-1] > temp){
this[j] = this[j-1];
}else{
break; // 这里要break,阻止后面的j--操作
}
j--;
}
this[j] = temp;
}
}
const arr = [5,90,4,3,2,1];
arr.insertSort();
// 时间复杂度:O(n^2);空间复杂度:O(1)
console.log(arr); // [1,2,3,4,5,90]
分:把数组劈成两半,再递归地对子数组进行"分"操作,直到分成一个个单独的数。
合:把两个数合并为有序数组,再对有序数组进行合并,直到全部子数组合并为一个完整数组。
新建一个空数组res,用于存放最终排序后的数组。
比较两个有序数组的头部,较小者出队并推入res中。
如果两个数组还有值,就重复第二步。
Array.prototype.mergeSort = function(){
// 分, O(logn)
const rec = (arr) => {
if(arr.length === 1) {return arr;} // 递归终止条件
const mid = Math.floor(arr.length / 2);
const left = arr.slice(0, mid); // 不包括mid
const right = arr.slice(mid, arr.length);
const orderLeft = rec(left);
const orderRight = rec(right);
// 结果数组
const res = [];
while(orderLeft.length || orderRight.length){
if(orderLeft.length && orderRight.length){
res.push(orderLeft[0] < orderRight[0] ? orderLeft.shift() : orderRight.shift())
}else{
orderLeft.length && res.push(orderLeft.shift());
orderRight.length && res.push(orderRight.shift());
}
}
return res;
}
const res = rec(this);
// 拷贝一份
res.forEach((n,i) => {
this[i] = n;
});
}
const arr = [5,90,4,66,3,2,1];
arr.mergeSort();
// 时间复杂度:O(nlogn);空间复杂度:O(n)
console.log(arr); // [1, 2, 3, 4, 5, 66, 90]
Array.prototype.quickSort = function(){
const rec = (arr) => {
if(arr.length <= 1) return arr; // 注意终止条件是小于等于1,因为arr可能是空数组
const left = [];
const right = [];
const mid = arr[0];
for(let i = 1; i < arr.length; i++){
if(arr[i] < mid){
left.push(arr[i]);
}else{
right.push(arr[i]);
}
}
return [...rec(left), mid, ...rec(right)];
}
const res = rec(this);
res.forEach((n,i) => {
this[i] = n;
})
}
const arr = [5,90,4,66,3,2,1];
arr.quickSort();
// 递归的时间复杂度是O(logN),分区操作的时间复杂度是O(n);时间复杂度:O(n*logN);空间复杂度:O(n)
console.log(arr);
另外一种解法:左右指针挖坑法
问题: 是否可以通过改变选取基准数来提高快排的效率?
左右指针挖坑法:
1.先从数列中取出一个数作为基准数(通常选第一个数)。 2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。 3.再对左右区间重复第二步,直到各区间只有一个数。
// edited by Jesy_Hsu
/**
* 左右指针挖坑法
* @param nums 待排序数组
* @param low 左指针
* @param high 右指针
*/
function partition(nums, low, high) {
// 基准数
var pivot = nums[low];
while (low < high) {
while (low < high && nums[high] >= pivot) {
high--;
}
// 互换位置,结束一轮
nums[low] = nums[high];
while (low < high && nums[low] <= pivot) {
low++;
}
nums[high] = nums[low];
}
// 此时low===high
nums[low] = pivot;
return low;
}
/**
* @param {number[]} nums
* @return {number[]}
*/
var quickSort = function (nums, low, high) {
// 分治思想,划分左右区间
if (low < high) {
// 找寻基准数的正确索引
var index = partition(nums, low, high);
// 进行迭代对index之前和之后的数组进行相同的操作使整个数组变成有序
quickSort(nums, low, index - 1);
quickSort(nums, index + 1, high);
}
return nums;
};
/**
* @param {number[]} nums
* @return {number[]}
*/
var sortArray = function(nums) {
var low = 0;
var high = nums.length - 1;
return quickSort(nums, low, high);
};
复杂度分析
时间复杂度: 基于随机选取主元的快速排序时间复杂度为期望 O(nlogn),其中 n 为数组的长度。
空间复杂度: O(h),其中 h 为快速排序递归调用的层数。我们需要额外的 O(h) 的递归调用的栈空间,由于划分的结果不同导致了快速排序递归调用的层数也会不同,最坏情况下需 O(n) 的空间,最优情况下每次都平衡,此时整个递归树高度为logn,空间复杂度为 O(logn)。
Array.prototype.sequentialSearch = function(target){
for(let i = 0; i < this.length; i++){
if(this[i] === target){
return i
}
}
return -1;
}
const arr = [1,2,3,4,5];
const res = arr.sequentialSearch(3);
console.log(res);
// 时间复杂度:O(n)
前提: 数组有序
Array.prototype.binarySearch = function(target){
// 前提:数组有序
let low = 0;
let high = this.length - 1;
while(low <= high){
const mid = Math.floor((low + high) / 2);
const element = this[mid];
if(element < target){
low = mid + 1;
}else if(element > target){
high = mid - 1;
}else{
return mid;
}
}
return -1;
}
const arr = [1,2,3,4,5];
const res = arr.binarySearch(3);
console.log(res);
// 时间复杂度:O(logN)
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = [] 输出:[]
示例 3:
输入:l1 = [], l2 = [0] 输出:[0]
提示:
两个链表的节点数目范围是 [0, 50] -100 <= Node.val <= 100 l1 和 l2 均按 非递减顺序 排列
解题思路:
/**
* 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) {
let res = new ListNode(0);
let p = res;
let p1 = list1;
let p2 = list2;
while(p1 && p2){
if(p1.val < p2.val){
p.next = p1;
p1 = p1.next;
}else{
p.next= p2;
p2 = p2.next;
}
p = p.next;
}
p1 && (p.next = p1);
p2 && (p.next = p2);
return res.next;
};
// 时间复杂度:O(M+N);空间复杂度:O(1)
猜数字游戏的规则如下:
- 每轮游戏,我都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。
- 如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。
你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-1,1 或 0):
- -1:我选出的数字比你猜的数字小 pick < num
- 1:我选出的数字比你猜的数字大 pick > num
- 0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num
返回我选出的数字。
示例 1:
输入:n = 10, pick = 6 输出:6
示例 2:
输入:n = 1, pick = 1 输出:1
示例 3:
输入:n = 2, pick = 1 输出:1
示例 4:
输入:n = 2, pick = 2 输出:2
提示:
- 1 <= n <= 2^31 - 1
- 1 <= pick <= n
/**
* Forward declaration of guess API.
* @param {number} num your guess
* @return -1 if num is lower than the guess number
* 1 if num is higher than the guess number
* otherwise return 0
* var guess = function(num) {}
*/
/**
* @param {number} n
* @return {number}
*/
var guessNumber = function(n) {
// 二分搜索
// 条件:一定会找到
let low = 1;
let high = n;
while(low <= high){
const mid = Math.floor((low + high) / 2);
const res = guess(mid);
if(res === 0){
return mid;
}else if(res === 1){
low = mid + 1;
}else{
high = mid - 1;
}
}
};
// 时间复杂度:O(logn);空间复杂度:O(1)
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
示例 1:
输入:x = 4 输出:2
示例 2:
输入:x = 8 输出:2 解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
提示:
0 <= x <= 2^31 - 1
解题思路
由于 x 平方根的整数部分 ans 是满足 k2≤x 的最大 k 值,因此我们可以对 k 进行二分查找,从而得到答案。
二分查找的下界为 0,上界可以粗略地设定为 x。在二分查找的每一步中,我们只需要比较中间元素 mid 的平方与 x 的大小关系,并通过比较的结果调整上下界的范围。由于我们所有的运算都是整数运算,不会存在误差,因此在得到最终的答案 ans 后,也就不需要再去尝试 ans+1了。
/**
* @param {number} x
* @return {number}
*/
var mySqrt = function(x) {
// 二分搜索
let ans = -1;
let low = 0;
let high = x;
while(low <= high){
let mid = Math.floor((low + high) / 2);
const temp = mid * mid ;
if(temp <= x){
ans = mid;
low = mid + 1;
}else{
high = mid - 1;
}
}
return ans;
};
// 时间复杂度:O(logx);空间复杂度:O(1)
Chrome浏览器的Array.prototype.sort()
方法采用的是数组长度小于11时使用插入排序,其他使用快速排序算法,插入排序是稳定的,快速排序不稳定,JS引擎是V8。
Firefox浏览器的Array.prototype.sort()
方法采用的是归并排序算法,归并排序是稳定的,JS引擎是SpiderMonkey。
Safari浏览器的Array.prototype.sort()
方法采用的是桶排序和归并排序算法,桶排序稳定性不确定,归并排序是稳定的,内核是Nitro。
Edge和IE浏览器的Array.prototype.sort()
方法采用的是不稳定的快速排序算法,内核是Chakra。
猜数字游戏的规则如下:
- 每轮游戏,我都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。
- 如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。
你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-1,1 或 0):
- -1:我选出的数字比你猜的数字小 pick < num
- 1:我选出的数字比你猜的数字大 pick > num
- 0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num
返回我选出的数字。
示例 1:
输入:n = 10, pick = 6 输出:6
示例 2:
输入:n = 1, pick = 1 输出:1
示例 3:
输入:n = 2, pick = 1 输出:1
示例 4:
输入:n = 2, pick = 2 输出:2
提示:
- 1 <= n <= 2^31 - 1
- 1 <= pick <= n
解题思路
/**
* Forward declaration of guess API.
* @param {number} num your guess
* @return -1 if num is lower than the guess number
* 1 if num is higher than the guess number
* otherwise return 0
* var guess = function(num) {}
*/
/**
* @param {number} n
* @return {number}
*/
var guessNumber = function(n) {
// 分而治之的思想
const rec = (low, high) => {
// 递归终止条件
if(low > high) { return; }
const mid = Math.floor((low + high) / 2);
const res = guess(mid);
if(res === 0){
return mid;
}else if(res === 1){
return rec(mid + 1, high);
}else{
return rec(low, mid - 1);
}
}
return rec(1,n);
};
// 时间复杂度:O(logn),空间复杂度:O(logn);
给你一棵二叉树的根节点
root
,翻转这棵二叉树,并返回其根节点。输入:root = [4,2,7,1,3,6,9] 输出:[4,7,2,9,6,3,1]
输入:root = [2,1,3] 输出:[2,3,1]
示例 3:
输入:root = [] 输出:[]
提示:
树中节点数目范围在 [0, 100] 内 -100 <= Node.val <= 100
解题思路
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var invertTree = function(root) {
// 用递归
const rec = function(head){
if(!head?.left && !head?.right) return head;
// 获取左右子树
const leftNode = head.left;
const rightNode = head.right;
head.left = rec(rightNode);
head.right = rec(leftNode);
return head;
}
return rec(root);
};
// 时间复杂度:O(n),n为节点数;空间复杂度:O(h),h是树的高度
// ================= Method 2 ======================
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var invertTree = function(root) {
if(!root) {return null};
return{
val: root.val,
left: invertTree(root.right),
right: invertTree(root.left)
}
};
给你两棵二叉树的根节点
p
和q
,编写一个函数来检验这两棵树是否相同。如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
示例 1:
输入:p = [1,2,3], q = [1,2,3] 输出:true
示例 2:
输入:p = [1,2], q = [1,null,2] 输出:false
示例 3:
输入:p = [1,2,1], q = [1,1,2] 输出:false
提示:
两棵树上的节点数目都在范围 [0, 100] 内 -10^4 <= Node.val <= 10^4
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} p
* @param {TreeNode} q
* @return {boolean}
*/
var isSameTree = function(p, q) {
if(!p && !q) return true;
if(p && q && p.val === q.val && isSameTree(p.left, q.left) && isSameTree(p.right, q.right)){
return true;
}
return false;
};
// 时间复杂度:O(n);空间复杂度:O(h),h为树的高度。
给你一个二叉树的根节点 root , 检查它是否轴对称。
输入:root = [1,2,2,3,4,4,3] 输出:true
输入:root = [1,2,2,null,3,null,3] 输出:false
提示:
树中节点数目在范围 [1, 1000] 内 -100 <= Node.val <= 100
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
var isSymmetric = function(root) {
const rec = (left, right) => {
if(!left && !right) return true;
if(left && right && left.val === right.val && rec(left.left,right.right) && rec(left.right, right.left)){
return true
}
return false;
}
return rec(root.left, root.right);
};
// 时间复杂度:O(n);空间复杂度:O(h),h为树的高度。
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2 输出:2 解释:有两种方法可以爬到楼顶。 1. 1 阶 + 1 阶 2. 2 阶
示例 2:
输入:n = 3 输出:3 解释:有三种方法可以爬到楼顶。 1. 1 阶 + 1 阶 + 1 阶 2. 1 阶 + 2 阶 3. 2 阶 + 1 阶
提示:
- 1 <= n <= 45
解题思路
/**
* @param {number} n
* @return {number}
*/
var climbStairs = function(n) {
// 动态规划的初始值很重要
if(n < 2) return 1;
let dp0 = 1; // 初始值
let dp1 = 1;
for(let i = 2; i <= n; i++){
let temp = dp0;
dp0 = dp1;
dp1 = dp1 + temp;
}
return dp1;
};
// 时间复杂度:O(n);空间复杂度:O(1)
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1] 输出:4 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1] 输出:12 解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 偷窃到的最高金额 = 2 + 9 + 1 = 12 。
提示:
- 1 <= nums.length <= 100
- 0 <= nums[i] <= 400
解题思路
/**
* @param {number[]} nums
* @return {number}
*/
var rob = function(nums) {
if(nums.length === 1) return nums[0];
// F(n) = Math.max(F(n-1), n+F(n-2))
// 初始值是2
let dp0 = nums[0];
let dp1 = Math.max(nums[0],nums[1]);
for(let i = 2; i < nums.length; i++){
let temp = dp1;
dp1 = Math.max(dp0+nums[i], temp);
dp0 = temp;
}
return dp1;
};
// 时间复杂度:O(n);空间复杂度:O(1)
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
示例 1:
输入:nums = [2,3,2] 输出:3 解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:
输入:nums = [1,2,3,1] 输出:4 解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。
示例 3:
输入:nums = [1,2,3] 输出:3
提示:
- 1 <= nums.length <= 100
- 0 <= nums[i] <= 1000
// 分成两个打家劫舍的问题
/**
* @param {number[]} nums
* @return {number}
*/
var rob = function(nums) {
const length = nums.length;
if(length === 1) return nums[0];
const rec = (arr, l, r) => {
let dp0 = 0;
let dp1 = arr[l];
for(let i = l+1; i < r; i++){
const temp = dp1;
dp1 = Math.max(dp0 + arr[i], dp1);
dp0 = temp
}
return dp1;
}
return Math.max(rec(nums, 0, length - 1), rec(nums, 1, length));
};
// 时间复杂度:O(n),空间复杂度:O(1)
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例 1:
输入: g = [1,2,3], s = [1,1] 输出: 1 解释: 你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。 虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。 所以你应该输出1。
示例 2:
输入: g = [1,2], s = [1,2,3] 输出: 2 解释: 你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。 你拥有的饼干数量和尺寸都足以让所有孩子满足。 所以你应该输出2.
提示:
- 1 <= g.length <= 3 * 10^4
- 0 <= s.length <= 3 * 10^4
- 1 <= g[i], s[j] <= 2^31 - 1
解题思路
/**
* @param {number[]} g
* @param {number[]} s
* @return {number}
*/
var findContentChildren = function(g, s) {
// 每个孩子最多只能给一块饼干
// 先将饼干数组和胃口数组排序
g.sort((a,b) => a-b); // 升序
s.sort((a,b) => a-b);
// 遍历饼干数组
let i = 0;
s.forEach(n => {
if(n >= g[i]){
i++;
}
});
return i;
};
// 时间复杂度:O(nlogn),空间复杂度:O(1)
给定一个数组 prices ,其中 prices[i] 表示股票第 i 天的价格。
在每一天,你可能会决定购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以购买它,然后在 同一天 出售。
返回 你能获得的 最大 利润 。示例 1:
输入: prices = [7,1,5,3,6,4] 输出: 7 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: prices = [1,2,3,4,5] 输出: 4 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: prices = [7,6,4,3,1] 输出: 0 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
- 1 <= prices.length <= 3 * 10^4
- 0 <= prices[i] <= 10^4
解题思路
/**
* @param {number[]} prices
* @return {number}
*/
var maxProfit = function(prices) {
// 任何时候最多只能持有一股
let profit = 0;
for(let i = 1; i < prices.length; i++){
if(prices[i] > prices[i-1]){
profit += prices[i] - prices[i-1];
}
}
return profit;
};
// 时间复杂度:O(n),空间复杂度:O(1)
回溯算法是算法设计中的一种方法。
回溯算法是一种渐进式寻找并构建问题解决方式的策略。
回溯算法会先从一个可能的动作开始解决问题,如果不行,就回溯并选择另一个动作,直到将问题解决。
适用场景:
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1] 输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1] 输出:[[1]]
提示:
- 1 <= nums.length <= 6
- -10 <= nums[i] <= 10
- nums 中的所有整数 互不相同
解题思路
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permute = function(nums) {
// 回溯算法
const res = [];
const backtrace = (path) => {
// 递归终止条件:找到了一个全排列的结果
if(path.length === nums.length){
res.push(path)
return;
}
nums.forEach(n => {
if(path.includes(n)){ return; }
backtrace(path.concat(n));
})
}
backtrace([]);
return res;
};
// 时间复杂度:O(n!),空间复杂度:O(n)
给你一个整数数组
nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0] 输出:[[],[0]]
提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
nums
中的所有元素 互不相同
解题思路
/**
* @param {number[]} nums
* @return {number[][]}
*/
var subsets = function(nums) {
// 回溯算法
const res = [];
const backtrace = (path,l,start) => {
if(path.length === l){
res.push(path);
return;
}
for(let i = start; i < nums.length; i++){
backtrace(path.concat(nums[i]), l, i + 1);
}
}
for(let i = 0; i <= nums.length; i++){
backtrace([], i, 0) // 包含了空集
}
return res;
};
// 时间复杂度:O(2^N),因为每个元素都有两种可能(存在或不存在)
// 空间复杂度:O(N)