大数运算:
- 字符串相乘
题目:两个很大的字符串表示的数相乘
解法:按照竖式运算,乘法照乘。用数组保存每单个位相乘的结果。然后加法用字符串加法。
/**
* @param {string} num1
* @param {string} num2
* @return {string}
*/
var subMultiply = function(num1, num2) {
let result = [], rest = 0;
for (let i = num1.length - 1; i >= 0; i--) {
let cur = Number(num1[i]) * Number(num2) + rest;
result.unshift(cur % 10);
rest = Math.floor(cur / 10);
}
if (rest) {
result.unshift(rest);
}
return result.join('');
}
var multiply = function(num1, num2) {
if (num1 == '0' || num2 == '0') return '0';
let result = [], cur = 0;
//num1乘以num2的每一位,结果保存在数组(字符串数组)
for (let i = num2.length - 1; i >= 0; i--) {
cur = subMultiply(num1, num2[i]);
result.push(cur);
}
//对每一位结果padding 0
result = result.map((num, index) => {
let zeroStr = '';
while(index > 0) {
zeroStr += '0';
index--;
}
num += zeroStr;
return num.split('').reverse().join('');
});
let sum = [], rest = 0;
for (let i = 0; i < result[result.length - 1].length; i++) {
cur = 0;
result.forEach(num => {
if (num[i]) {
cur += Number(num[i]);
}
});
cur += rest;
let bit = cur % 10;
rest = Math.floor(cur / 10);
sum.push(bit);
}
if (rest) {
sum.push(rest);
}
return sum.reverse().join('');
};
- 求Pow(x, n)
tip: 每个整数都能够被分解为2 ** k次幂的序列和。
/**
* @param {number} x
* @param {number} n
* @return {number}
*/
var myPow = function(x, n) {
if (n == 0) return 1;
if (x == 1 || x == -1 && n % 2 == 0) return 1;
if (x == -1 && n % 2 == 1) return -1;
let flag1 = 1, flag2 = 1;
if (x < 0 && n % 2 == 1) {
flag1 = -1;
}
x = Math.abs(x);
flag2 = n > 0 ? 1 : -1;
if (flag2 == -1 && n == 0 - 2 ** 31) {
flag2 = -2;
n = 2 ** 31 - 1;
} else {
n = Math.abs(n);
}
let tmp = x, result = 1, base = 1, sum = 0;
//幂次之和没有到n,复杂度logn
while(sum < n) {
//第base位是1
if ((n & base) > 0) {
result *= tmp;
sum += base;
}
//X^2^k -> x^2^(k+1)
tmp = tmp * tmp;
base = base << 1;
}
if (flag2 == -1) {
result = 1 / result;
}
if (flag2 == -2) {
result = 1 / result / 2;
}
if (flag1 == -1) {
result = 0 - result;
}
return result;
};
- 两整数相除
tip: 转化为负数不会溢出 相除的结果要求为整数,那么也就是2的幂次的和
/**
* @param {number} dividend
* @param {number} divisor
* @return {number}
*/
var subdivide = function(dividend, divisor) {
//被除数大于除数,证明被除数绝对值小。结果下取整,为0
let ans = 0, tmp = divisor;
//每次膨胀2倍。可以理解为结果总是可以表示为2的幂次的和。
//-10是否小于-3 * 2(证明结果大于2 ** 1)
while (dividend < 0) {
if (dividend > divisor) return ans;
//被除数大于2倍除数,结果下取整后为1
if (dividend <= divisor && dividend > divisor + divisor) return ans + 1;
i = 1;
tmp = divisor;
while (dividend <= tmp + tmp) {
tmp = tmp << 1;
i = i * 2;
}
ans += i;
dividend -= tmp;
}
return ans;
};
var divide = function(dividend, divisor) {
//被除数为0,则结果为0
if (dividend == 0) return 0;
//除数为1,结果为被除数
if (divisor == 1) return dividend;
//除数为-1
if (divisor == -1) {
//被除数不是最小负数,则为被除数取反
if (dividend > 0 - 2**31) {
return 0 - dividend;
//被除数为最小负数,则结果为最大正数
} else {
return 2**31 - 1;
}
}
//结果符号
let negative = false;
//如果被除数和除数符号相反,则结果为负。
if (dividend > 0 && divisor < 0 || dividend < 0 && divisor > 0) {
negative = true;
}
//全转化为负数计算,不会溢出
dividend = dividend > 0 ? 0 - dividend : dividend;
divisor = divisor > 0 ? 0 - divisor : divisor;
//开始计算
let result = subdivide(dividend, divisor);
if (negative) {
result = 0 - result;
}
return result;
};
- 两整数的和,没有加减法
tip: 两数相与,结果是相加后进位 两数相异或,结果是相加后结果。
解法:将两数相异或的结果作为a, 将相与的结果作为b b <<1 因为b是进位结果。 循环相加,知道不再产生进位(b == 0)
var getSum = function(a, b) {
let sum = a ^ b,
carry = (a & b) << 1;
while (b != 0) {
a = sum;
b = carry;
sum = a ^ b;
carry = (a & b) << 1;
}
return a;
};
树:
- 判断一棵树是不是二叉搜索树:
tip: BST定义:左子树全部小于根,右子树全部大于根。
解法: 中序遍历,判断序列是否为升序 非递归遍历,pop出的结点视作被访问。
解法: 非递归中序遍历一棵树:
- 首先通过while找到最左侧结点,并不断压栈
- 每次pop一个结点。一个结点被pop出,意味着它的左子树被访问完了。此时访问它自己,并且将右子树,右子树的全部左子树压入栈。
- 除了第一个结点之外,每次访问一个结点,将值与lastValue比较。如果升序,将lastValue设置为当前值。
2.二叉树两个节点的最近公共祖先
tip: 遍历路径压栈的方式求解
- 删除BST中节点
一个结点的前驱是左子树最右结点, 没有的话如果是结点是左子树,向上遍历祖先,直到有右子树的祖先,前驱是该祖先左子树的最右结点或者祖先本身。 如果结点是右子树,那么是祖先。
后继是右子树的最左节点, 没有的话如果结点是右子树,一直向上遍历祖先直到祖先有左结点,后继是该祖先右子树最左结点或者祖先本身。 如果结点是左子树,那么是祖先。
var buildStack = function(root, node, path) {
if (!root) return false;
path.push(root);
if (root == node || buildStack(root.left, node, path) || buildStack(root.right, node, path)) {
return true;
} else {
path.pop();
return false;
}
}
var lowestCommonAncestor = function(root, p, q) {
let stack1 = [], stack2 = [];
buildStack(root, p, stack1);
buildStack(root, q, stack2);
let p1 = stack1.length - 1, p2 = stack2.length - 1;
while (p1 > p2) {
p1--;
}
while(p2 > p1) {
p2--;
}
while(stack1[p1] != stack2[p2]) {
p1--;
p2--;
}
return stack1[p1];
};
-
BST某结点的后继:右子树的最左子树
-
删除BST节点
/**
* 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} key
* @return {TreeNode}
*/
var subDeleteNode = function(head, root, key, parent, flag) {
if (!root) return null;
//找到了被删除节点
if (root.val == key) {
let node = null, pre = null;
//如果有前驱,使用前驱替代该节点。前驱是左子树的最后一个右节点(没有就是左子树本身)
if (root.left) {
//确认前驱,并赋值给node
node = root.left;
while (node.right) {
pre = node;
node = node.right;
}
//前驱需要继承被删除节点的右子树(前驱肯定没有右子树)
node.right = root.right;
//前驱如果自己不是被删除节点的左子树,则它自己可能具有左子树,它的左子树需要移交它父节点的右子数(父节点右子树为前驱本身,即将么有),它的新左子树为被删除元素的左子树
if (pre) {
pre.right = node.left;
node.left = root.left;
}
if (!parent) {
return node;
} else {
parent[flag] = node;
return head;
}
//如果没有前驱,则用后继代替,后继为右子树的最左子树
} else if (root.right) {
node = root.right;
pre = null;
while(node.left) {
pre = node;
node = node.left;
}
//如果后继不是被删除节点的右子树,
//那么后继的右子树需要变成后继父亲的左子树(后继本身是父亲的左子树,然后后继本身要移走所以父亲没有左子树了。后继肯定没有左子树)
//同时后继的新右子树是被删除节点的右子树
//如果后继是被删除节点的右子树,那么平移就行了,肯定没有左子树。
if (pre) {
pre.left = node.right;
node.right = root.right;
}
node.left = root.left;
if (!parent) {
return node;
} else {
parent[flag] = node;
return head;
}
}
if (parent) {
parent[flag] = null;
return head;
} else {
return null;
}
} else if (key < root.val){
return subDeleteNode(head, root.left, key, root, 'left') || head;
} else {
return subDeleteNode(head, root.right, key, root, 'right') || head;
}
}
var deleteNode = function(root, key) {
if (!root) return null;
return subDeleteNode(root, root, key, null, '');
};
- 实现前缀树
题目:前缀树定义:N叉树。同样的路径不重复建子树
/**
* Initialize your data structure here.
*/
var Trie = function() {
this.root = {};
};
/**
* Inserts a word into the trie.
* @param {string} word
* @return {void}
*/
Trie.prototype.insert = function(word) {
var current = this.root;
word.split("").forEach((letter, index) => {
if (current[letter]) {
current = current[letter];
} else {
current[letter] = {};
current = current[letter];
}
if (index == word.length - 1) {
current.end = true;
}
});
};
/**
* Returns if the word is in the trie.
* @param {string} word
* @return {boolean}
*/
Trie.prototype.search = function(word) {
let current = this.root, i = 0;
for (; i < word.length; i++) {
if (current[word[i]]) {
current = current[word[i]];
} else break;
}
return i == word.length && current.end == true;
};
/**
* Returns if there is any word in the trie that starts with the given prefix.
* @param {string} prefix
* @return {boolean}
*/
Trie.prototype.startsWith = function(prefix) {
let current = this.root, i = 0;
for (; i < prefix.length; i++) {
if (current[prefix[i]]) {
current = current[prefix[i]];
} else break;
}
return i == prefix.length;
};
- BST剪枝:保留固定区间结点并且不改变结点结构
tip: 树的题目一定要善于用递归,尤其是本来思路就是前中后序遍历的时候。
/**
* 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} low
* @param {number} high
* @return {TreeNode}
*/
var trimBST = function(root, low, high) {
if (!root) return null;
if (root.val < low) {
return trimBST(root.right, low, high);
}
if (root.val > high) {
return trimBST(root.left, low, high);
}
root.left = trimBST(root.left, low, high);
root.right = trimBST(root.right, low, high);
return root;
};
- 前k个高频单词:堆排序
/**
* @param {string[]} words
* @param {number} k
* @return {string[]}
*/
var compare = function(a, b) {
return (a.count > b.count || (a.count == b.count && a.word < b.word)) ? 1 : -1;
};
//维护一个大小为k的最小堆
var heapMinify = function(heap, start) {
while (start < heap.length) {
let min = start;
let left = 2 * start + 1, right = 2 * start + 2;
if (left < heap.length && compare(heap[left], heap[start]) == -1) {
min = left;
}
if (right < heap.length && compare(heap[right], heap[min]) == -1) {
min = right;
}
if (start == min) {
return;
}
let tmp = heap[min];
heap[min] = heap[start];
heap[start] = tmp;
start = min;
}
};
var buildMinHeap = function(heap) {
if (heap.length == 1) return;
let n = heap.length, i = Math.floor(n / 2) - 1;
while (i >= 0) {
heapMinify(heap, i);
i--;
}
};
var topKFrequent = function(words, k) {
let map = {};
for (let i = 0; i < words.length; i++) {
if (!map[words[i]]) {
map[words[i]] = {
word: words[i],
count: 1
};
} else {
map[words[i]].count++;
}
}
let items = Object.values(map),
heap = items.slice(0, k);
buildMinHeap(heap);
for (let i = k; i < items.length; i++) {
if (compare(items[i], heap[0]) == 1) {
heap[0] = items[i];
heapMinify(heap, 0);
}
}
let ans = [];
while (heap.length) {
ans.push(heap[0]);
heap[0] = heap[heap.length - 1];
heap.pop();
heapMinify(heap, 0);
}
return ans.map(item => item.word).reverse();
};
7、 找出BST中第k小的数
tip: 利于递归。 如果在左子树找到了,直接返回左子树返回的值 如果左子树没有,计数+1 如果刚好为k,证明当前节点是。 否则去右子树找,返回右子树返回的值。
/**
* 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} k
* @return {number}
*/
var kthSmallest = function(root, k) {
let count = 0;
var inordertraverse = function (root) {
if (!root) return -1;
let value = inordertraverse(root.left);
if (value != -1) return value;
count++;
if (count == k) return root.val;
return inordertraverse(root.right);
};
return inordertraverse(root);
};
8、重构字符串
题目:重排字符串,让每两个字符不相邻
解法:首先统计每个字符出现的次数,每次从剩余字符中选一个剩余次数最多的。使用大根堆
/**
* @param {string} s
* @return {string}
*/
//没有一个字符超过次数出现一半即可
//每次从非本身字符中选一个剩余个数最多的
//维护一个大根堆
var heapMaxify = function(arr, index) {
let endIndex = Math.floor(arr.length / 2) - 1;
while (index <= endIndex) {
let left = 2 * index + 1, right = 2 * index + 2,
max = index;
if (left < arr.length && arr[left].count > arr[index].count) {
max = left;
}
if (right < arr.length && arr[right].count > arr[max].count) {
max = right;
}
let tmp = arr[index];
arr[index] = arr[max];
arr[max] = tmp;
if (max != index) {
index = max;
} else {
break;
}
}
};
//在这之后不是排好序了,而只是建了一个大根堆、
var buildHeap = function(arr) {
let endIndex = Math.floor(arr.length / 2) - 1;
for (let i = endIndex; i >= 0; i--) {
heapMaxify(arr, i);
}
};
var reorganizeString = function(s) {
let map = {};
for (let i = 0; i < s.length; i++) {
if (!map[s[i]]) {
map[s[i]] = 1;
} else {
map[s[i]]++;
if (map[s[i]] > Math.ceil(s.length / 2)) {
return "";
}
}
}
let items = [], ans = '';
for (let key in map) {
if (map.hasOwnProperty(key)) {
items.push({
letter: key,
count: map[key]
});
}
}
buildHeap(items);
for (let i = 0; i < s.length; i++) {
//如果是第一个元素,或者上一个元素和堆顶不同,直接取堆顶
if (ans.length == 0 || ans[ans.length - 1] != items[0].letter) {
ans += items[0].letter;
items[0].count--;
} else {
let max = 0;
//默认取左子树
if (items.length > 1) {
max = 1;
}
//比较右子树
if (items.length > 2 && items[2].count > items[max].count) {
max = 2;
}
//交换位置
let tmp = items[0];
items[0] = items[max];
items[max] = tmp;
//取顶部三角最大值
ans += items[0].letter;
items[0].count--;
}
//如果堆顶元素个数为0,证明该字符无法被继续使用,将最后叶子节点移到堆顶,调整.
if (items[0].count == 0) {
items[0] = items.pop();
}
//调整堆顶
heapMaxify(items, 0);
}
return ans;
};
9、 输出和为target的路径
/**
* 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 {number[][]}
*/
var pathSum = function(root, targetSum) {
if (!root) return [];
let ans = [], path = [root.val];
const tarverse = (node, sum) => {
if (!node.left && !node.right && sum == targetSum) {
ans.push([...path]);
return;
}
if (node.left) {
path.push(node.left.val);
tarverse(node.left, sum + node.left.val);
path.pop();
}
if (node.right) {
path.push(node.right.val);
tarverse(node.right, sum + node.right.val);
path.pop();
}
};
tarverse(root, root.val);
return ans;
};
- BST转双向链表
tips: 递归
/**
* // Definition for a Node.
* function Node(val, left, right) {
* this.val = val;
* this.left = left;
* this.right = right;
* };
*/
/**
* @param {Node} root
* @return {Node}
*/
var treeToDoublyList = function(root) {
if (!root) return null;
var inorderConvert = function(node) {
if (!node) return [null, null];
let left = inorderConvert(node.left);
let right = inorderConvert(node.right);
node.left = left[1];
node.right = right[0];
if (left[1]) {
left[1].right = node;
}
if (right[0]) {
right[0].left = node;
}
return [left[0] ? left[0] : node, right[1] ? right[1] : node];
}
let [head, tail] = inorderConvert(root);
head.left = tail;
tail.right = head;
return head;
};
图:
- 求岛的数量:
深度优先遍历or广度优先遍历。 栈空的时候证明一个岛遍历完成了(一个连通图遍历完成了) 然后通过双重循环寻找下一个连通图结点。 采用将访问过的结点的值改为0的方式,来代替重新申明visited数组,节省空间
/**
* @param {character[][]} grid
* @return {number}
*/
var numIslands = function(grid) {
if (!grid || grid.length == 0) return 0;
let nr = grid.length,
nc = grid[0].length,
result = 0,
queue = [];
for (let i = 0; i < nr; i++) {
for (let j = 0; j < nc; j++) {
if (grid[i][j] == '1') {
result++;
grid[i][j] = '0';
//加入当前节点(以一维展开的位置计算作为唯一标识)
queue.push([i, j]);
while(queue.length) {
let cur = queue.shift(),
row = cur[0],
col = cur[1];
if (row > 0 && grid[row-1][col] == '1') {
queue.push([row - 1, col]);
grid[row-1][col] = '0';
}
if (row < nr - 1 && grid[row + 1][col] == '1') {
queue.push([row + 1, col]);
grid[row + 1][col] = '0';
}
if (col > 0 && grid[row][col-1] == '1') {
queue.push([row, col-1]);
grid[row][col-1] = '0';
}
if (col < nc - 1 && grid[row][col + 1] == '1') {
queue.push([row, col + 1]);
grid[row][col + 1] = '0';
}
}
}
}
}
return result;
};
- 图的复制
图用一个入口结点{val, neighbours}代表
var buildGragh = function(originNode, newNode, map) {
map[originNode.val] = newNode;
originNode.neighbors.forEach(neighbor => {
let newNeighbor = map[neighbor.val] ? map[neighbor.val] : buildGragh(neighbor, {val: neighbor.val, neighbors: []}, map);
newNode.neighbors.push(newNeighbor);
});
return newNode;
};
var cloneGraph = function(node) {
if (!node) return null;
return buildGragh(node, {val: node.val, neighbors: []}, {});
};
3、有向连通图中寻找欧拉路径
欧拉路径:经过所有节点并且每条边只走一次
解法:从出发节点开始DFS每个节点,每次选择下一个节点的原则是字典序最小。 并且将遍历过的边记录为visited或者从邻接表中删除,记录为访问过。 最先无法继续被遍历的点最后到达。所以是首先压栈然后逐个弹出(数组逆序)
/**
* @param {string[][]} tickets
* @return {string[]}
*/
//连通图中寻找欧拉路径
var findItinerary = function(tickets) {
let map = {}, stack = [];
var dfs = (src) => {
while (map[src] && map[src].length) {
let nextDest = map[src].shift();
dfs(nextDest);
}
stack.push(src);
}
for (let i = 0; i < tickets.length; i++) {
let [src, dest] = tickets[i];
if (!map[src]) {
map[src] = [dest];
} else {
map[src].push(dest);
}
}
for (let key in map) {
if (map.hasOwnProperty(key)) {
map[key].sort();
}
}
dfs('JFK');
stack.reverse();
return stack;
};
单调栈:
- 最大水容积
题目:给定挡板高度数组heights, 求最大能容纳的水的容积。
解法:设置单调递增数组。因为比一个隔板靠右的高度还更矮的隔板没有意义存在。
- 选择k个数字去掉后,剩下的数字最小
题目:字符串表示的数字num, 去掉其中k个字符,使得剩余字符串表示的数字最小
解法: 单调递增栈,遇到降序(比栈顶数字小),就进行弹栈,直到栈顶元素比当前元素小或者到达k个数。如果是前者,将Nums[i]压入栈。
var removeKdigits = function(num, k) {
if (num == '0') return '0';
let stack = [num[0]], i = 1;
for (; i < num.length && k > 0; i++) {
if (num[i] >= stack[stack.length - 1]) {
stack.push(num[i]);
} else {
while(stack.length && stack[stack.length - 1] > num[i] && k > 0) {
stack.pop();
k--;
}
stack.push(num[i]);
}
}
while(k > 0) {
stack.pop();
k--;
}
while (i < num.length) {
stack.push(num[i++]);
}
let start = 0;
while(stack[start] == '0') start++;
stack = stack.slice(start);
return stack.length ? stack.join('') : '0';
};
- 升温所需要等待的时间
题目:给定温度数组,返回每天温度需要等待升温的时间
/**
* @param {number[]} T
* @return {number[]}
*/
var dailyTemperatures = function(T) {
//维护一个单调递减栈, 栈中存放元素下标,元素对应的温度递减
let stack = [], result = new Array(T.length), top = 0;
stack.push(0);
result[0] = 0;
for (let i = 1; i < T.length; i++) {
result[i] = 0;
while(top >= 0 && T[stack[top]] < T[i]) {
result[stack[top]] = i - stack[top];
stack.pop();
top--;
}
stack.push(i);
top++;
}
return result;
};