1.所有可能的满二叉树
题目:
满二叉树是一类二叉树,其中每个结点恰好有 0 或 2 个子结点。
返回包含 N 个结点的所有可能满二叉树的列表。 答案的每个元素都是一个可能树的根结点。
答案中每个树的每个结点都必须有 node.val=0。
你可以按任何顺序返回树的最终列表。
思路:首先明确,满二叉树的节点数量是奇数。
然后,对于总数量为n的二叉树,根节点占一个节点,假设左节点有m个,那么右子树有n-1-m个。
所以对于数量为n的二叉树,可以枚举所有的子树节点,。
左子树1个节点,右子树是n-1-1,左子树3个节点,右子树n-1-3....
然后为了复用,用Map记录节点数量为n时的二叉树,对应的值是数组。
时间复杂度O(2^n),空间复杂度O(2^n)
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {number} N
* @return {TreeNode[]}
*/
var allPossibleFBT = function (N) {
if (!(N % 2)) return [];
const map = new Map();
map.set(1, [new TreeNode(0)]);
const getNode = (n) => {
if (map.has(n)) return map.get(n);
if (n === 1) {
return;
} else {
const res = [];
for (let i = 1; i <= n - 2; i += 2) {
const left = getNode(i);
const right = getNode(n - 1 - i);
const l1 = left.length;
const l2 = right.length;
for (let i = 0; i < l1; i++) {
for (let j = 0; j < l2; j++) {
const root = new TreeNode(0);
root.left = left[i];
root.right = right[j];
res.push(root);
}
}
}
map.set(n, res);
return res;
}
};
return getNode(N);
};
2.子数组按位或操作
题目:
我们有一个非负整数数组 A。
对于每个(连续的)子数组 B = [A[i], A[i+1], ..., A[j]] ( i <= j),我们对 B 中的每个元素进行按位或操作,获得结果 A[i] | A[i+1] | ... | A[j]。
返回可能结果的数量。 (多次出现的结果在最终答案中仅计算一次。)
/**
* @param {number[]} arr
* @return {number}
*/
var subarrayBitwiseORs = function(arr) {
const set1 = new Set();
let set2 = new Set();
set2.add(0);
for (const x of arr) {
const set3 = new Set();
for (const y of set2) {
set3.add(x | y);
}
set3.add(x);
set2 = set3;
for (const s of set2) {
set1.add(s);
}
}
return set1.size;
};
3.RLE迭代器
题目:
编写一个遍历游程编码序列的迭代器。
迭代器由 RLEIterator(int[] A)
初始化,其中 A
是某个序列的游程编码。更具体地,对于所有偶数 i
,A[i]
告诉我们在序列中重复非负整数值 A[i + 1]
的次数。
迭代器支持一个函数:next(int n)
,它耗尽接下来的 n
个元素(n >= 1
)并返回以这种方式耗去的最后一个元素。如果没有剩余的元素可供耗尽,则 next
返回 -1
。
例如,我们以 A = [3,8,0,9,2,5]
开始,这是序列 [8,8,8,5,5]
的游程编码。这是因为该序列可以读作 “三个八,零个九,两个五”。
思路:如果用具体的数字填充,那么最后的结果是很大的,会内存溢出,所以不能这样,只能记录剩余的数字。
用栈保存每个数字和这个数字的长度,形成二维数组。在执行next时,判断栈顶的元素的长度是否大于等于n,如果大于等于,对应长度减去n,返回栈顶的元素的值。
如果小于,n减去栈顶元素的长度,并将栈顶元素弹出栈,进行下一轮循环
/**
* @param {number[]} A
*/
var RLEIterator = function (A) {
const l = A.length;
this.data = [];
for (let i = 0; i < l; i += 2) {
this.data.push([A[i], A[i + 1]]);
}
};
/**
* @param {number} n
* @return {number}
*/
RLEIterator.prototype.next = function (n) {
while (n) {
if (!this.data.length) return -1;
const item = this.data.shift();
if (item[0] >= n) {
this.data.unshift([item[0] - n, item[1]]);
n = 0;
return item[1];
} else {
n -= item[0];
}
}
};
4.股票价格跨度
题目:
编写一个 StockSpanner 类,它收集某些股票的每日报价,并返回该股票当日价格的跨度。
今天股票价格的跨度被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。
例如,如果未来7天股票的价格是 [100, 80, 60, 70, 60, 75, 85],那么股票跨度将是 [1, 1, 1, 2, 1, 4, 6]。
思路:用单调递减栈记录比当前价格小的天数。
每次执行next时,判断栈顶价格和当前的价格大小,。当前价格更大,就弹出栈顶元素,并加上对应的长度;
所以用两个数组,一个数组记录价格,另一个数组记录天数
var StockSpanner = function () {
this.data = [];
this.length = [];
};
/**
* @param {number} price
* @return {number}
*/
StockSpanner.prototype.next = function (price) {
let length = 1;
while (this.data.length && this.data[this.data.length - 1] <= price) {
this.data.pop();
length += this.length.pop();
}
this.data.push(price);
this.length.push(length);
return length
};
5.水果成篮
题目:
在一排树中,第 i 棵树产生 tree[i] 型的水果。
你可以从你选择的任何树开始,然后重复执行以下步骤:
把这棵树上的水果放进你的篮子里。如果你做不到,就停下来。
移动到当前树右侧的下一棵树。如果右边没有树,就停下来。
请注意,在选择一颗树后,你没有任何选择:你必须执行步骤 1,然后执行步骤 2,然后返回步骤 1,然后执行步骤 2,依此类推,直至停止。
你有两个篮子,每个篮子可以携带任何数量的水果,但你希望每个篮子只携带一种类型的水果。
用这个程序你能收集的水果树的最大总量是多少?
思路:滑动窗口。在窗口中只能有两个元素,所以用一个set记录当前窗口的水果类型,用Left和right记录窗口的左右边界。right每次右移时u,判断当前窗口的成员是否超过2个,超过2个时,更新窗口的左边界。每次移动时,更新最大值
为了方便,用一个额外的变量记录上一个成员第一次出现的下标。这种方式也可以理解为三指针。
时间复杂度O(n),空间复杂度O(1)
/**
* @param {number[]} tree
* @return {number}
*/
var totalFruit = function(tree) {
let max = 0;
const l = tree.length;
let left = 0,
right = 0;
let c = 0;
const set = new Set();
let last = 0;
while (right < l) {
if (set.size === 2 && !set.has(tree[right])) {
set.clear();
set.add(tree[right - 1]);
left = last;
}
set.add(tree[right]);
const curV = right - left + 1;
max = Math.max(curV, max);
if (tree[last] !== tree[right]) {
last = right;
}
right++;
}
return max;
};