其他

173 阅读7分钟

大数运算:

  1. 字符串相乘

题目:两个很大的字符串表示的数相乘

解法:按照竖式运算,乘法照乘。用数组保存每单个位相乘的结果。然后加法用字符串加法。

/**
 * @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('');
};
  1. 求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;
};
  1. 两整数相除

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;
};
  1. 两整数的和,没有加减法

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;
};

树:

  1. 判断一棵树是不是二叉搜索树:

tip: BST定义:左子树全部小于根,右子树全部大于根。

解法: 中序遍历,判断序列是否为升序 非递归遍历,pop出的结点视作被访问。

解法: 非递归中序遍历一棵树:

  1. 首先通过while找到最左侧结点,并不断压栈
  2. 每次pop一个结点。一个结点被pop出,意味着它的左子树被访问完了。此时访问它自己,并且将右子树,右子树的全部左子树压入栈。
  3. 除了第一个结点之外,每次访问一个结点,将值与lastValue比较。如果升序,将lastValue设置为当前值。

2.二叉树两个节点的最近公共祖先

tip: 遍历路径压栈的方式求解

  1. 删除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];
};
  1. BST某结点的后继:右子树的最左子树

  2. 删除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, '');
};
  1. 实现前缀树

题目:前缀树定义: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;
};

  1. 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;
};
  1. 前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;
};
  1. 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;

};

图:

  1. 求岛的数量:

深度优先遍历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;
};
  1. 图的复制

图用一个入口结点{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;
};

单调栈:

  1. 最大水容积

题目:给定挡板高度数组heights, 求最大能容纳的水的容积。

解法:设置单调递增数组。因为比一个隔板靠右的高度还更矮的隔板没有意义存在。

  1. 选择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';
};
  1. 升温所需要等待的时间

题目:给定温度数组,返回每天温度需要等待升温的时间

/**
 * @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;
   
};