1.二叉树的后序遍历
题目:
给定一个二叉树,返回它的 后序 遍历
思路:后序遍历,就是先遍历左节点,然后遍历右节点,然后遍历根结点。
时间复杂度:O(n),空间复杂度:O(n)
递归:
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var postorderTraversal = function(root) {
const res = [];
const search = (root) => {
if (!root) return;
search(root.left);
search(root.right);
res.push(root.val);
};
search(root)
return res;
};
迭代:这里说下迭代。因为是后序遍历,所以要保证根结点在叶子节点后面推入数组,但是正常的遍历肯定是先遍历根结点的。所以第一次遇到有子节点的节点的时候,先剪除叶子节点,然后把根结点推入栈内等待下一次遍历,然后把子节点推入栈。只有在某个节点没有子节点的时候,才推入结果
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var postorderTraversal = function(root) {
const stack = [root];
const res = [];
while (stack.length) {
const item = stack.pop();
if (!item) continue;
if (item.left || item.right) {
const left = item.left;
const right = item.right;
item.left = null;
item.right = null;
stack.push(item);
stack.push(right);
stack.push(left);
} else {
res.push(item.val);
}
}
return res;
};
2.摆动排序II
题目:给定一个无序的数组 nums
,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]...
的顺序。
思路:可以先对数组进行排序,然后将前半段和后半段分开,将后半段穿插进入前半段。因为前半段和后半段有可能是相同的数字,为了避免相同的数字相邻,按照数字从大到小处理
时间复杂度:O(nlogn),空间复杂度O(n)
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var wiggleSort = function(nums) {
const l = nums.length;
nums.sort((a, b) => b - a);
const left = nums.slice(0, ~~(l / 2));
const right = nums.slice(~~(l / 2));
for (let i = 0; i < l; i++) {
if (i % 2) {
nums[i] = left.shift();
} else {
nums[i] = right.shift();
}
}
return nums;
};
来看看怎么优化。时间复杂度方面,nlogn是因为排序带来的,我们主要是要获取前后两段数组。那么,需要对全部的数字进行排序吗?不需要,我们只需要找到中位数,将数组分成大于中位数、小于中位数的两个子数组即可。
时间复杂度O(n),空间复杂度O(n)
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
const getMid = (left, right, nums) => {
const l = nums.length;
let mid = nums[~~(l / 2)];
const leftL = [],
rightL = [];
let v = 0;
for (const n of nums) {
if (n > mid) {
rightL.push(n);
} else if (n < mid) {
leftL.push(n);
} else {
v++;
}
}
if (Math.abs(left + leftL.length - right - rightL.length) <= v) return mid;
if (left + leftL.length > right + rightL.length) {
return getMid(left, rightL.length + right + v, leftL);
} else {
return getMid(left + leftL.length + v, right, rightL);
}
};
var wiggleSort = function (nums) {
const l = nums.length;
const mid = getMid(0, 0, nums.slice());
const left = [],
right = [];
let v = 0;
for (const n of nums) {
if (n > mid) {
right.push(n);
} else if (n < mid) {
left.push(n);
} else {
v++;
}
}
const c = left.length - right.length;
if (c == 0) {
left.splice(0, 0, ...new Array(Math.ceil(v / 2)).fill(mid));
right.push(...new Array(~~(v / 2)).fill(mid));
} else if (c > 0) {
v -= c;
left.splice(0, 0, ...new Array(Math.ceil(v / 2)).fill(mid));
right.push(...new Array(~~(v / 2) + c).fill(mid));
} else {
v += c;
left.splice(0, 0, ...new Array(Math.ceil(v / 2) - c).fill(mid));
right.push(...new Array(~~(v / 2)).fill(mid));
}
for (let i = 0; i < l; i++) {
if (i % 2) {
nums[i] = right.shift();
} else {
nums[i] = left.shift();
}
}
return nums;
};
3.奇偶链表
题目:
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
思路:可以将奇数节点放在一个链表,偶数节点放在一个链表,然后两个链表相连
/**
* 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 oddEvenList = function(head) {
const pre = new ListNode();
const ev = new ListNode();
let res = pre;
let odd = ev;
let v = 1;
while (head) {
if (v % 2) {
res.next = head;
head = head.next;
res = res.next;
res.next = null;
} else {
odd.next = head;
head = head.next;
odd = odd.next;
odd.next = null;
}
v++
}
res.next = ev.next;
return pre.next;
};
4.验证二叉树的前序序列化
题目:
序列化二叉树的一种方法是使用前序遍历。当我们遇到一个非空节点时,我们可以记录下这个节点的值。如果它是一个空节点,我们可以使用一个标记值记录,例如 #。
_9_
/ \
3 2
/ \ / \
4 1 # 6
/ \ / \ / \
# # # # # #
例如,上面的二叉树可以被序列化为字符串 "9,3,4,#,#,1,#,#,2,#,6,#,#",其中 # 代表一个空节点。
给定一串以逗号分隔的序列,验证它是否是正确的二叉树的前序序列化。编写一个在不重构树的条件下的可行算法。
每个以逗号分隔的字符或为一个整数或为一个表示 null 指针的 '#' 。
你可以认为输入格式总是有效的,例如它永远不会包含两个连续的逗号,比如 "1,,3" 。
思路:根据逗号分割,每个成员都是一个节点,要么是null要么是正常的节点。对于正常的节点,总节点数+2,而空节点和正常的节点都会消耗一个位置。所以如果某个时刻剩余位置为0,那么结果为false
时间复杂度:O(n),空间复杂度O(n)
/**
* @param {string} preorder
* @return {boolean}
*/
var isValidSerialization = function(preorder) {
const list = preorder.split(",");
let v = 1;
for (const n of list) {
if (v == 0) return false;
if (n !== "#") {
v++;
} else {
v--;
}
}
return !v;
};
5.重新安排行程
题目:
给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。
提示:
如果存在多种有效的行程,请你按字符自然排序返回最小的行程组合。例如,行程 ["JFK", "LGA"] 与 ["JFK", "LGB"] 相比就更小,排序更靠前
所有的机场都用三个大写字母表示(机场代码)。
假定所有机票至少存在一种合理的行程。
所有的机票必须都用一次 且 只能用一次。
思路:根据起点和重点,建立map,记录每个起点能到的终点,并排序,深度优先遍历
时间复杂度:O(n),空间复杂度:O(m)。n是总的路过的城市数量,m是行程数量
/**
* @param {string[][]} tickets
* @return {string[]}
*/
var findItinerary = function(tickets) {
const map = {};
for (const [from, to] of tickets) {
if (map[from]) {
map[from].push(to);
} else {
map[from] = [to];
}
if (!map[to]) {
map[to] = [];
}
}
for (const key of Object.keys(map)) {
map[key].sort();
}
const res = [];
const dfs = (city, res) => {
if (!map[city]) return;
const arr = map[city];
while (arr.length) {
dfs(arr.shift(), res);
}
res.unshift(city);
};
dfs("JFK", res);
return res;
};