diff --git a/.nycrc b/.nycrc new file mode 100644 index 0000000..da1274d --- /dev/null +++ b/.nycrc @@ -0,0 +1,16 @@ +{ + "require": [ + "babel-register", + "babel-polyfill" + ], + "sourceMap": false, + "instrument": false, + "lines": 99, + "statements": 99, + "functions": 99, + "branches": 99, + "reporter": [ + "lcov", + "text" + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 19161b2..0601cdd 100644 --- a/package.json +++ b/package.json @@ -8,18 +8,10 @@ "license": "MIT", "scripts": { "lint": "eslint src", - "test": "npm run lint && cross-env NODE_ENV=test nyc mocha src/**/*-test.js --compilers js:babel-register --require babel-polyfill", + "test": "npm run lint && cross-env NODE_ENV=test nyc mocha src/**/*-test.js", "coverage": "nyc report --reporter=text-lcov | coveralls", "release": "node ./release.js" }, - "nyc": { - "sourceMap": false, - "instrument": false, - "lines": 99, - "statements": 99, - "functions": 99, - "branches": 99 - }, "devDependencies": { "babel": "^6.23.0", "babel-core": "^6.26.0", diff --git a/src/common/tree-node.js b/src/common/tree-node.js new file mode 100644 index 0000000..2e48797 --- /dev/null +++ b/src/common/tree-node.js @@ -0,0 +1,10 @@ +/** + * Definition for a binary tree node. + */ +export default class TreeNode { + constructor(val) { + this.val = val; + this.left = null; + this.right = null; + } +} diff --git a/src/tree/binary-tree-inorder-traversal.js b/src/tree/binary-tree-inorder-traversal.js new file mode 100644 index 0000000..d637f48 --- /dev/null +++ b/src/tree/binary-tree-inorder-traversal.js @@ -0,0 +1,32 @@ +/** + * Given a binary tree, return the inorder traversal of its nodes' values. + * + * For example: + * Given binary tree [1,null,2,3], + * 1 + * \ + * 2 + * / + * 3 + * return [1,3,2]. + * + * Note: Recursive solution is trivial, could you do it iteratively? + */ +const inorderTraversal = root => { + const res = []; + + const helper = (root, res) => { + if (!root) { + return; + } + + helper(root.left, res); + res.push(root.val); + helper(root.right, res); + }; + + helper(root, res); + return res; +}; + +export default inorderTraversal; diff --git a/src/utils/__tests__/tree-util-test.js b/src/utils/__tests__/tree-util-test.js new file mode 100644 index 0000000..ae0c4fc --- /dev/null +++ b/src/utils/__tests__/tree-util-test.js @@ -0,0 +1,112 @@ +import { assert } from 'chai'; +import TreeNode from '../../common/tree-node'; +import { serializeTree, deserializeTree } from '../tree-util'; + +/** + * 1 + * / \ + * 2 3 + */ +const tree1 = () => { + const root = new TreeNode(1); + root.left = new TreeNode(2); + root.right = new TreeNode(3); + return root; +}; + +/** + * 1 + * \ + * 2 + */ +const tree2 = () => { + const root = new TreeNode(1); + root.right = new TreeNode(2); + return root; +}; + +/** + * 1 + * \ + * 2 + * / \ + * 3 4 + */ +const tree3 = () => { + const root = new TreeNode(1); + root.right = new TreeNode(2); + root.right.left = new TreeNode(3); + root.right.right = new TreeNode(4); + return root; +}; + +/** + * 1 + * \ + * 2 + * / + * 3 + */ +const tree4 = () => { + const root = new TreeNode(1); + root.right = new TreeNode(2); + root.right.left = new TreeNode(3); + return root; +}; + +/** + * 1 + * / + * 2 + * / + * 3 + */ +const tree5 = () => { + const root = new TreeNode(1); + root.left = new TreeNode(2); + root.left.left = new TreeNode(3); + return root; +}; + +describe('Tree Util', () => { + describe('Serialize', () => { + const testCases = [ + [null, null], + [tree1(), '[1,2,3]'], + [tree2(), '[1,null,2]'], + [tree3(), '[1,null,2,3,4]'], + [tree4(), '[1,null,2,3]'], + [tree5(), '[1,2,null,3]'], + ]; + + testCases.map((testCase, index) => { + it(`should serialize the tree ${index}: ${testCase[1]}`, () => { + const root = testCase[0]; + const expected = testCase[1]; + const actual = serializeTree(root); + assert.equal(actual, expected); + }); + }); + }); + + describe('deserialize', () => { + const testCases = [ + [null], + ['bad input', null], + ['[1,2,3]'], + ['[1,null,2]'], + ['[1,null,2,3,4]'], + ['[1,null,2,3]'], + ['[1,2,null,3]'], + ]; + + testCases.map((testCase, index) => { + it(`should deserialize the tree ${index}: ${testCase[0]}`, () => { + const input = testCase[0]; + const expected = testCase.length === 1 ? testCase[0] : testCase[1]; + const actual = serializeTree(deserializeTree(input)); + assert.equal(actual, expected); + }); + }); + }); +}); diff --git a/src/utils/tree-util.js b/src/utils/tree-util.js new file mode 100644 index 0000000..2b61177 --- /dev/null +++ b/src/utils/tree-util.js @@ -0,0 +1,82 @@ +import TreeNode from '../common/tree-node'; + +/** + * Encodes a tree to a single string. + * + * @param {TreeNode} root + * @return {string} + */ +const serializeTree = root => { + if (!root) { + return null; + } + + let data = []; + + // Level-order traversal + const queue = [root]; + while (queue.length > 0) { + const node = queue.shift(); + + if (node) { + data.push(node.val); + queue.push(node.left); + queue.push(node.right); + } else { + data.push(null); + } + } + + // Clean up the trailing nulls in data + while (data.length > 0 && data[data.length - 1] === null) { + data.pop(); + } + + return JSON.stringify(data); +}; + +/** + * Decodes your encoded data to tree + * + * @param {string} str + * @return {TreeNode} + */ +const deserializeTree = str => { + let data; + + // Sanity checks + try { + data = JSON.parse(str); + } catch (e) { + return null; + } + if (!Array.isArray(data) || data.length === 0) { + return null; + } + + const root = new TreeNode(data.shift()); + const queue = [root]; + + while (data.length > 0) { + const node = queue.shift(); + + // Left node + let val = data.shift(); + if (typeof val !== 'undefined' && val !== null) { + node.left = new TreeNode(val); + queue.push(node.left); + } + + // Right node + val = data.shift(); + if (typeof val !== 'undefined' && val !== null) { + node.right = new TreeNode(val); + queue.push(node.right); + } + } + + return root; +}; + +export { serializeTree }; +export { deserializeTree };