面试题3 -- 搜索二维矩阵

写出一个高效的算法来搜索 m × n矩阵中的值。

这个矩阵具有以下特性:

1. 每行中的整数从左到右是排序的。

2. 每行的第一个数大于上一行的最后一个整数。

思路:二分位置0 ~ n * m - 1

 public class Solution {
/**
* @param matrix, a list of lists of integers
* @param target, an integer
* @return a boolean, indicate whether matrix contains target
*/
public boolean searchMatrix(int[][] matrix, int target) {
// write your code here
if (matrix == null || matrix.length == 0) {
return false;
}
int rows = matrix.length;
int columns = matrix[0].length;
int start = 0;
int end = rows * columns - 1;
while (start + 1 < end) {
int mid = start + (end - start) / 2;
if (matrix[mid / columns][mid % columns] == target) {
return true;
} else if (matrix[mid / columns][mid % columns] < target) {
start = mid;
} else {
end = mid;
}
}
if (matrix[start / columns][start % columns] == target) {
return true;
} else if (matrix[end / columns][end % columns] == target) {
return true;
}
return false;
}
}

拓展 -- 搜索二维矩阵 II

写出一个高效的算法来搜索m×n矩阵中的值,返回这个值出现的次数。

这个矩阵具有以下特性:

1. 每行中的整数从左到右是排序的。

2. 每一列的整数从上到下是排序的。

3. 在每一行或每一列中没有重复的整数。

思路:从二维矩阵的右上角(左下角)开始搜索,每次当该位置的值等于target,等于target的个数+1,删除该位置所在的行和列,移动行指针和列指针;小于target,删除该位置所在的行,移动行指针;大于target,删除该位置所在的列,移动列指针。

 public class Solution {
/**
* @param matrix: A list of lists of integers
* @param: A number you want to search in the matrix
* @return: An integer indicate the occurrence of target in the given matrix
*/
public int searchMatrix(int[][] matrix, int target) {
// write your code here
if (matrix == null || matrix.length == 0) {
return 0;
}
int rows = matrix.length;
int columns = matrix[0].length;
int row = 0;
int column = columns - 1;
int count = 0;
while (row <= rows - 1 && column >= 0) {
if (matrix[row][column] == target) {
count++;
row++;
column--;
} else if (matrix[row][column] > target) {
column--;
} else {
row++;
}
}
return count;
}
}

求Fibonacci第n个数(从1开始)

初级:递归(当n很大时时间复杂度很高,会有重复递归问题)。

 class Solution {
/**
* @param n: an integer
* @return an integer f(n)
*/
public int fibonacci(int n) {
// write your code here
if (n == 1) {
return 0;
}
if (n == 2) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
}

中级:使用map存储递归过程中得到的F[i],键值对-[i, F(i)],可以避免重复递归问题。

 class Solution {
/**
* @param n: an integer
* @return an integer f(n)
*/
public int fibonacci(int n) {
// write your code here
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
return fibonacci(n, map);
}
public int fibonacci(int n, Map<Integer, Integer> map) {
if (n == 1) {
map.put(1, 0);
return 0;
}
if (n == 2) {
map.put(2, 1);
return 1;
}
int sum = 0;
if (map.containsKey(n - 1)) {
sum += map.get(n - 1);
} else {
int temp = fibonacci(n - 1, map);
map.put(n - 1, temp);
sum += temp;
}
if (map.containsKey(n - 2)) {
sum += map.get(n - 2);
} else {
int temp = fibonacci(n - 2, map);
map.put(n - 2, temp);
sum += temp;
}
return sum;
}
}

高级:直接根据F(1)和F(2)求的F(3),再由F(2)和F(3)求的F(4)......以此类推知道求得F(n)停止。

 class Solution {
/**
* @param n: an integer
* @return an integer f(n)
*/
public int fibonacci(int n) {
// write your code here
if (n == 1) {
return 0;
}
if (n == 2) {
return 1;
}
int fibNMinusTwo = 0;
int fibNMinusOne = 1;
int fibN = 0;
for (int i = 3; i <= n; i++) {
fibN = fibNMinusTwo + fibNMinusOne;
fibNMinusTwo = fibNMinusOne;
fibNMinusOne = fibN;
}
return fibN;
}
}

拓展 -- 爬楼梯

假设你正在爬楼梯,需要n步你才能到达顶部。但每次你只能爬一步或者两步,你能有多少种不同的方法爬到楼顶部?

思路I:类似斐波那契数列的求解过程

 public class Solution {
/**
* @param n: An integer
* @return: An integer
*/
public int climbStairs(int n) {
// write your code here
if (n <= 1) {
return 1;
}
int stairsNMinusTwo = 1;
int stairsNMinusOne = 1;
int stairsN = 0;
for (int i = 2; i <= n; i++) {
stairsN = stairsNMinusTwo + stairsNMinusOne;
stairsNMinusTwo = stairsNMinusOne;
stairsNMinusOne = stairsN;
}
return stairsN;
}
}

思路II:dp + 滚动数组优化

 public class Solution {
/**
* @param n: An integer
* @return: An integer
*/
public int climbStairs(int n) {
// write your code here
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
int[] dp = new int[2];
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i % 2] = dp[(i - 1) % 2] + dp[(i - 2) % 2];
}
return dp[n % 2];
}
}

拓展 -- 爬楼梯II

一个小孩爬一个 n 层台阶的楼梯。他可以每次跳 1 步, 2 步 或者 3 步。实现一个方法来统计总共有多少种不同的方式爬到最顶层的台阶。

 public class Solution {
/**
* @param n an integer
* @return an integer
*/
public int climbStairs2(int n) {
// Write your code here
if (n <= 1) {
return 1;
}
if (n == 2) {
return 2;
}
int stairsNMinusThree = 1;
int stairsNMinusTwo = 1;
int stairsNMinusOne = 2;
int stairsN = 0;
for (int i = 3; i <= n; i++) {
stairsN = stairsNMinusThree + stairsNMinusTwo + stairsNMinusOne;
stairsNMinusThree = stairsNMinusTwo;
stairsNMinusTwo = stairsNMinusOne;
stairsNMinusOne = stairsN;
}
return stairsN;
}
}

空格替换

设计一种方法,将一个字符串中的所有空格替换成 %20。你可以假设该字符串有足够的空间来加入新的字符,且你得到的是“真实的”字符长度。

你的程序还需要返回被替换后的字符串的长度。如果使用 Java 或 Python, 程序中请用字符数组表示字符串。

 public class Solution {
/**
* @param string: An array of Char
* @param length: The true length of the string
* @return: The true length of new string
*/
public int replaceBlank(char[] string, int length) {
// Write your code here
if (string == null || string.length == 0) {
return 0;
}
int prePointer = 0;
int afterPointer = 0;
int blankNumber = 0;
for (int i = 0; string[i] != '\0'; i++) {
if (string[i] == ' ') {
blankNumber++;
}
prePointer++;
}
afterPointer = prePointer + 2 * blankNumber;
int result = afterPointer;
while (prePointer >= 0 && prePointer != afterPointer) {
if (string[prePointer] != ' ') {
string[afterPointer] = string[prePointer];
afterPointer--;
} else {
string[afterPointer--] = '0';
string[afterPointer--] = '2';
string[afterPointer--] = '%';
}
prePointer--;
}
return result;
}
}

面试题10 -- 二进制中1的个数

计算在一个 32 位的整数的二进制表式中有多少个 1

常规:1不断左移一位并与给定的数⬅️与运算,如果为0说明该位为1,count++,左移次数从0 - 31.

 public class Solution {
/**
* @param num: an integer
* @return: an integer, the number of ones in num
*/
public int countOnes(int num) {
// write your code here
int count = 0;
int flag = 1;
for (int i = 0; i < 32; i++) {
if ((num & flag) != 0) {
count++;
}
flag = flag << 1;
}
return count;
}
}

高级:把一个整数减去1,再和原整数作与运算,会把该整数最右边一个1变成0。

 public class Solution {
/**
* @param num: an integer
* @return: an integer, the number of ones in num
*/
public int countOnes(int num) {
// write your code here
int count = 0;
while (num != 0) {
num = (num - 1) & num;
count++;
}
return count;
}
}

面试题16 -- 翻转链表

一刷的时候没有注意到先建一个临时节点来保存当前节点cur的下一个节点,导致cur移到下一个节点的时候出现错误!!!不保存下一个节点的话链表会断开!!!

 /**
* Definition for ListNode.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int val) {
* this.val = val;
* this.next = null;
* }
* }
*/
public class Solution {
/**
* @param head: The head of linked list.
* @return: The new head of reversed linked list.
*/
public ListNode reverse(ListNode head) {
// write your code here
if (head == null || head.next == null) {
return head;
}
ListNode prev = head;
ListNode cur = head.next;
while (cur != null) {
//这句代码必不可少!操作前必须先保存下一个节点!
ListNode after = cur.next;
cur.next = prev;
prev = cur;
cur = after;
}
head.next = null;
return prev;
}
}

拓展 -- 翻转链表II

翻转链表中第m个节点到第n个节点的部分。m,n满足1 ≤ m ≤ n ≤ 链表长度。

 /**
* Definition for ListNode
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
/**
* @param ListNode head is the head of the linked list
* @oaram m and n
* @return: The head of the reversed ListNode
*/
public ListNode reverseBetween(ListNode head, int m , int n) {
// write your code
if (head == null || head.next == null) {
return head;
}
ListNode fakeHead = new ListNode(0);
fakeHead.next = head;
ListNode pre = fakeHead;
for (int i = 0; i < m - 1; i++) {
pre = pre.next;
}
ListNode cur = pre.next;
ListNode after = cur.next;
for (int i = 0; i < n - m; i++) {
cur.next = after.next;
after.next = pre.next;
pre.next = after;
after = cur.next;
}
return fakeHead.next;
}
}

版本II

 /**
* Definition for ListNode
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/ public class Solution {
/*
* @param head: ListNode head is the head of the linked list
* @param m: An integer
* @param n: An integer
* @return: The head of the reversed ListNode
*/
public ListNode reverseBetween(ListNode head, int m, int n) {
// write your code here
if (head == null || head.next == null) {
return head;
}
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode nodeMMinus = dummy;
for (int i = 0; i < m - 1; i++) {
nodeMMinus = nodeMMinus.next;
}
ListNode nodeM = nodeMMinus.next;
ListNode pre = null, cur = nodeM;
for (int i = 0; i < n - m + 1; i++) {
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
ListNode nodeN = pre;
nodeMMinus.next = nodeN;
nodeM.next = cur;
return dummy.next;
}
}

面试题6 -- 前序遍历和中序遍历树构造二叉树

你可以假设树中不存在相同数值的节点

注意构造二叉树的过程分析。

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/
public class Solution {
/**
*@param preorder : A list of integers that preorder traversal of a tree
*@param inorder : A list of integers that inorder traversal of a tree
*@return : Root of a tree
*/
public TreeNode buildTree(int[] preorder, int[] inorder) {
// write your code here
return helper(preorder, inorder, 0, 0, inorder.length - 1);
}
public TreeNode helper(int[] preorder, int[] inorder, int preStart,
int inStart, int inEnd) {
if (inStart > inEnd) {
return null;
}
TreeNode root = new TreeNode(preorder[preStart]);
int inRootIndex = 0;
for (int i = 0; i < inorder.length; i++) {
if (preorder[preStart] == inorder[i]) {
inRootIndex = i;
}
}
root.left = helper(preorder, inorder, preStart + 1, inStart, inRootIndex - 1);
root.right = helper(preorder, inorder,
preStart + inRootIndex - inStart + 1, inRootIndex + 1, inEnd);
return root;
}
}

拓展 -- 中序遍历和后序遍历树构造二叉树

根据中序遍历和后序遍历树构造二叉树,你可以假设树中不存在相同数值的节点。

思路同上。

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/
public class Solution {
/**
*@param inorder : A list of integers that inorder traversal of a tree
*@param postorder : A list of integers that postorder traversal of a tree
*@return : Root of a tree
*/
public TreeNode buildTree(int[] inorder, int[] postorder) {
// write your code here
return buildTreeHelper(inorder, postorder, postorder.length - 1,
0, inorder.length - 1);
}
public TreeNode buildTreeHelper(int[] inorder, int[] postorder,
int postStart, int inStart, int inEnd) {
if (inStart > inEnd) {
return null;
}
TreeNode root = new TreeNode(postorder[postStart]);
int inRootIndex = 0;
for (int i = 0; i < inorder.length; i++) {
if (postorder[postStart] == inorder[i]) {
inRootIndex = i;
}
}
root.left = buildTreeHelper(inorder, postorder,
postStart - (inEnd - inRootIndex) - 1, inStart, inRootIndex - 1);
root.right = buildTreeHelper(inorder, postorder, postStart - 1,
inRootIndex + 1, inEnd);
return root;
}
}

面试题7 -- 用栈实现队列

正如标题所述,你需要使用两个栈来实现队列的一些操作。

队列应支持push(element),pop() 和 top(),其中pop是弹出队列中的第一个(最前面的)元素。

pop和top方法都应该返回第一个元素的值。

核心思路:stack1只管用来将元素压栈,stack2只管将元素弹出栈,如果为空就从stack1里将元素全部压入stack2再弹出,注意判空操作。

 public class Queue {
private Stack<Integer> stack1;
private Stack<Integer> stack2; public Queue() {
// do initialization if necessary
stack1 = new Stack<Integer>();
stack2 = new Stack<Integer>();
}
public void push(int element) {
// write your code here
stack1.push(element);
} public int pop() {
// write your code here
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
if (stack2.isEmpty()) {
return 0;
}
return stack2.pop();
} public int top() {
// write your code here
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
if (stack2.isEmpty()) {
return 0;
}
return stack2.peek();
}
}

面试题8 -- 寻找旋转排序数组中的最小值

假设一个旋转排序的数组其起始位置是未知的(比如0 1 2 4 5 6 7 可能变成是4 5 6 7 0 1 2)。

你需要找到其中最小的元素。

你可以假设数组中不存在重复的元素。

 public class Solution {
/**
* @param nums: a rotated sorted array
* @return: the minimum number in the array
*/
public int findMin(int[] nums) {
// write your code here
if (nums == null || nums.length == 0) {
return 0;
}
int start = 0;
int end = nums.length - 1;
int mid = start;
while (nums[start] > nums[end]) {
if (end - start == 1) {
mid = end;
break;
}
mid = start + (end - start) / 2;
if (nums[mid] > nums[start]) {
start = mid;
} else if (nums[mid] < nums[end]) {
end = mid;
}
}
return nums[mid];
}
}
 public class Solution {
/*
* @param nums: a rotated sorted array
* @return: the minimum number in the array
*/
public int findMin(int[] nums) {
// write your code here
if (nums == null || nums.length == 0) {
return -1;
}
int start = 0, end = nums.length - 1;
int target = nums[nums.length - 1];
while (start + 1 < end) {
int mid = start + (end - start) / 2;
if (nums[mid] > target) {
start = mid;
} else {
end = mid;
}
}
if (nums[start] > nums[end]) {
return nums[end];
} else {
return nums[start];
}
}
}
 public class Solution {
/*
* @param nums: a rotated sorted array
* @return: the minimum number in the array
*/
public int findMin(int[] nums) {
// write your code here
if (nums == null || nums.length == 0) {
return -1;
}
int start = 0, end = nums.length - 1;
while (start + 1 < end) {
int mid = start + (end - start) / 2;
if (nums[mid] > nums[end]) {
start = mid;
} else {
end = mid;
}
}
if (nums[start] > nums[end]) {
return nums[end];
}
return nums[start];
}
}

拓展 -- 寻找旋转排序数组中的最小值 II

假设一个旋转排序的数组其起始位置是未知的(比如0 1 2 4 5 6 7 可能变成是4 5 6 7 0 1 2)。

你需要找到其中最小的元素。

数组中可能存在重复的元素。

 public class Solution {
/**
* @param num: a rotated sorted array
* @return: the minimum number in the array
*/
public int findMin(int[] num) {
// write your code here
if (num == null || num.length == 0) {
return 0;
}
int start = 0;
int end = num.length - 1;
int mid = start;
while (num[start] >= num[end]) {
if (end - start == 1) {
mid = end;
break;
}
mid = start + (end - start) / 2;
if (num[start] == num[mid] && num[mid] == num[end]) {
return findMinInOrder(num, start, end);
}
if (num[mid] >= num[start]) {
start = mid;
} else if (num[mid] <= num[end]) {
end = mid;
}
}
return num[mid];
}
public int findMinInOrder(int[] num, int start, int end) {
int result = num[start];
for (int i = start + 1; i <= end; i++) {
if (num[i] < result) {
result = num[i];
break;
}
}
return result;
}
}
 public class Solution {
/*
* @param nums: a rotated sorted array
* @return: the minimum number in the array
*/
public int findMin(int[] nums) {
// write your code here
if (nums == null || nums.length == 0) {
return -1;
}
int start = 0, end = nums.length - 1;
while (start + 1 < end) {
int mid = start + (end - start) / 2;
if (nums[mid] == nums[end]) {
end--;
} else if (nums[mid] > nums[end]) {
start = mid;
} else {
end = mid;
}
}
if (nums[start] > nums[end]) {
return nums[end];
}
return nums[start];
}
}

面试题14 -- 奇偶分割数组

分割一个整数数组,使得奇数在前偶数在后

思路:前后指针,前指针找到偶数,后指针找到奇数,交换。

 public class Solution {
/*
* @param nums: an array of integers
* @return: nothing
*/
public void partitionArray(int[] nums) {
// write your code here
if (nums == null || nums.length <= 1) {
return;
}
int start = 0, end = nums.length - 1;
while (start < end) {
while (start < end && (nums[start] & 1) == 1) {
start++;
}
while (start < end && (nums[end] & 1) == 0) {
end--;
}
if (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
}
}

面试题11 -- 快速幂

计算a ^ n % b,其中a,b和n都是32位的整数。

 class Solution {
/*
* @param a, b, n: 32bit integers
* @return: An integer
*/
public int fastPower(int a, int b, int n) {
// write your code here
if (n == 0) {
return 1 % b;
}
if (n == 1) {
return a % b;
}
long product = fastPower(a, b, n >> 1);
product = (product * product) % b;
if ((n & 1) != 0) {
product = (product * a) % b;
}
return (int) product;
}
}

面试题12 -- 用递归打印数字

用递归的方法找到从1到最大的N位整数。

 public class Solution {
/**
* @param n: An integer.
* return : An array storing 1 to the largest number with n digits.
*/
public List<Integer> numbersByRecursion(int n) {
// write your code here
List<Integer> results = new ArrayList<Integer>();
dfsHelper(results, n, 0);
return results;
}
public void dfsHelper(List<Integer> results, int n, int sum) {
if (n == 0) {
if (sum != 0) {
results.add(sum);
}
return ;
}
for (int i = 0; i < 10; i++) {
dfsHelper(results, n - 1, sum * 10 + i);
}
}
}

面试题13 -- 在O(1)时间复杂度删除链表节点

给定一个单链表中的一个等待被删除的节点(非表头或表尾)。请在在O(1)时间复杂度删除该链表节点。

 /**
* Definition for ListNode.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int val) {
* this.val = val;
* this.next = null;
* }
* }
*/
public class Solution {
/**
* @param node: the node in the list should be deleted
* @return: nothing
*/
public void deleteNode(ListNode node) {
// write your code here
if (node == null) {
return;
}
node.val = node.next.val;
node.next = node.next.next;
}
}

面试题15 -- 删除链表中倒数第n个节点

给定一个链表,删除链表中倒数第n个节点,返回链表的头节点。

注意事项:链表中的节点个数大于等于n(代码对n输入不合法也判断了,鲁棒性更好)
 /**
* Definition for ListNode.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int val) {
* this.val = val;
* this.next = null;
* }
* }
*/
public class Solution {
/**
* @param head: The first node of linked list.
* @param n: An integer.
* @return: The head of linked list.
*/
ListNode removeNthFromEnd(ListNode head, int n) {
// write your code here
if (head == null || n <= 0) {
return head;
}
ListNode fakeHead = new ListNode(0);
fakeHead.next = head;
ListNode ahead = fakeHead;
for (int i = 0; i < n; i++) {
if (ahead.next != null) {
ahead = ahead.next;
} else {
return head;
}
}
ListNode behind = fakeHead;
while (ahead.next != null) {
ahead = ahead.next;
behind = behind.next;
}
behind.next = behind.next.next;
return fakeHead.next;
}
}

面试题17 -- 合并两个排序链表

将两个排序链表合并为一个新的排序链表

方法一:递归

 /**
* Definition for ListNode.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int val) {
* this.val = val;
* this.next = null;
* }
* }
*/
public class Solution {
/**
* @param ListNode l1 is the head of the linked list
* @param ListNode l2 is the head of the linked list
* @return: ListNode head of linked list
*/
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// write your code here
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
ListNode head = null;
if (l1.val < l2.val) {
head = l1;
head.next = mergeTwoLists(l1.next, l2);
} else {
head = l2;
head.next = mergeTwoLists(l1, l2.next);
}
return head;
}
}

方法二:循环

 /**
* Definition for ListNode.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int val) {
* this.val = val;
* this.next = null;
* }
* }
*/
public class Solution {
/**
* @param ListNode l1 is the head of the linked list
* @param ListNode l2 is the head of the linked list
* @return: ListNode head of linked list
*/
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// write your code here
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
ListNode fakeHead = new ListNode(0);
ListNode cur = fakeHead;
while (l1 != null || l2 != null) {
ListNode newNode = new ListNode(0);
if (l1 != null && l2 != null) {
if (l1.val < l2.val) {
newNode.val = l1.val;
l1 = l1.next;
} else {
newNode.val = l2.val;
l2 = l2.next;
}
} else if (l1 != null && l2 == null) {
newNode.val = l1.val;
l1 = l1.next;
} else {
newNode.val = l2.val;
l2 = l2.next;
}
cur.next = newNode;
cur = cur.next;
}
cur.next = null;
return fakeHead.next;
}
}

面试题18 -- 子树

有两个不同大小的二进制树: T1 有上百万的节点; T2有好几百的节点。请设计一种算法,判定 T2 是否为 T1的子树。

注意事项:若 T1 中存在从节点 n 开始的子树与 T2 相同,我们称 T2 是 T1 的子树。也就是说,如果在 T1 节点 n 处将树砍断,砍断的部分将与 T2 完全相同。

变形 -- 求子结构,此时整体思路一样,只需要把逻辑判断改一下,具体看程序中的注释。

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/
public class Solution {
/**
* @param T1, T2: The roots of binary tree.
* @return: True if T2 is a subtree of T1, or false.
*/
public boolean isSubtree(TreeNode T1, TreeNode T2) {
// write your code here
if (T2 == null) {
return true;
}
boolean result = false;
if (T1 != null && T2 != null) {
if (T1.val == T2.val) {
result = isT1HasT2(T1, T2);
}
if (!result) {
result = isSubtree(T1.left, T2);
}
if (!result) {
result = isSubtree(T1.right, T2);
}
}
return result;
}
public boolean isT1HasT2(TreeNode T1, TreeNode T2) {
//注意如果题目换成判断是否为树的子结构的话,只需改一下此处的逻辑判断,即当T1 != null && T2 == null时要return true而不是false,即改成如下代码:
// if (T2 == null) {
// return true;
// }
// if (T1 == null) {
// retrun false;
// }
if (T1 == null && T2 == null) {
return true;
} else if (T1 == null || T2 == null) {
return false;
}
//接下来步骤一样
if (T1.val != T2.val) {
return false;
}
return isT1HasT2(T1.left, T2.left) && isT1HasT2(T1.right, T2.right);
}
}

面试题20 -- 螺旋矩阵

给定一个包含 m x n 个要素的矩阵,(m 行, n 列),按照螺旋顺序,返回该矩阵中的所有要素。

注意此题打印矩阵一圈的分析思路,首先至少一行,直接打印;然后必须至少两行才能打印最右边的列,必须两行两列才能打印最下面的行,必须三行两列才能打印最左边的列。

 public class Solution {
/**
* @param matrix a matrix of m x n elements
* @return an integer list
*/
public List<Integer> spiralOrder(int[][] matrix) {
// Write your code here
List<Integer> result = new ArrayList<Integer>();
if (matrix == null || matrix.length == 0) {
return result;
}
int m = matrix.length;
int n = matrix[0].length;
int start = 0;
int endM = m - 1;
int endN = n - 1;
while (m > start * 2 && n > start * 2) {
endM = m - 1 - start;
endN = n - 1 - start;
printMatrixInCircle(matrix, start, endM, endN, result);
start++;
}
return result;
}
public void printMatrixInCircle(int[][] matrix, int start,
int endM, int endN, List<Integer> result) {
for (int i = start; i <= endN; i++) {
result.add(matrix[start][i]);
}
if (endM > start) {
for (int i = start + 1; i <= endM; i++) {
result.add(matrix[i][endN]);
}
}
if (endM > start && endN > start) {
for (int i = endN - 1; i >= start; i--) {
result.add(matrix[endM][i]);
}
}
if (endM > start + 1 && endN > start) {
for (int i = endM - 1; i > start; i--) {
result.add(matrix[i][start]);
}
}
}
}

自己写的容易理解的解法

 public class Solution {
/*
* @param matrix: a matrix of m x n elements
* @return: an integer list
*/
public List<Integer> spiralOrder(int[][] matrix) {
// write your code here
List<Integer> result = new ArrayList<>();
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return result;
}
int n = matrix.length, m = matrix[0].length;
int startN = 0, endN = n - 1, startM = 0, endM = m - 1;
while (startN <= endN && startM <= endM) {
for (int i = startM; i <= endM; i++) {
result.add(matrix[startN][i]);
}
if (endN > startN) {
for (int i = startN + 1; i <= endN; i++) {
result.add(matrix[i][endM]);
}
}
if (endN > startN && endM > startM) {
for (int i = endM - 1; i >= startM; i--) {
result.add(matrix[endN][i]);
}
}
if (endN - startN >= 2 && endM > startM) {
for (int i = endN - 1; i >= startN + 1; i--) {
result.add(matrix[i][startM]);
}
}
startN++;
startM++;
endN--;
endM--;
}
return result;
}
}

拓展 -- Spiral Matrix II

Given an integer n, generate a square matrix filled with elements from 1 to n^2 in spiral order.

Given n = 3,
You should return the following matrix:
[
[ 1, 2, 3 ],
[ 8, 9, 4 ],
[ 7, 6, 5 ]
]

题目

 public class Solution {
/**
* @param n an integer
* @return a square matrix
*/
public int[][] generateMatrix(int n) {
// Write your code here
if (n < 0) {
return null;
}
int[][] result = new int[n][n];
int xStart = 0;
int yStart = 0;
int num = 1;
while (n > 0) {
if (n == 1) {
result[xStart][yStart] = num++;
break;
}
for (int i = 0; i < n - 1; i++) {
result[xStart][yStart + i] = num++;
}
for (int i = 0; i < n - 1; i++) {
result[xStart + i][yStart + n - 1] = num++;
}
for (int i = 0; i < n - 1; i++) {
result[xStart + n - 1][yStart + n - 1 - i] = num++;
}
for (int i = 0; i < n - 1; i++) {
result[xStart + n - 1 - i][yStart] = num++;
}
xStart++;
yStart++;
n -= 2;
}
return result;
}
}
 public class Solution {
public int[][] generateMatrix(int n) {
if (n <= 0) {
return new int[0][0];
}
int[][] matrix = new int[n][n];
for (int i = 0; n > 2 * i; i++) {
copyOneCircle(matrix, i);
}
return matrix;
}
public void copyOneCircle(int[][] matrix, int start) {
int n = matrix.length;
int rows = n - 2 * start;
int columns = n - 2 * start;
int num = 0;
if (start == 0) {
num = 1;
} else {
num = matrix[start - 1][start - 1] + 4 * (rows + 1);
}
if (rows >= 1) {
for (int i = start; i <= n - 1 - start; i++) {
matrix[start][i] = num++;
}
}
if (rows >= 2) {
for (int i = start + 1; i <= n - 1 - start; i++) {
matrix[i][n - 1 - start] = num++;
}
}
if (rows >= 2 && columns >= 2) {
for (int i = n - 2 - start; i >= start; i--) {
matrix[n - 1 - start][i] = num++;
}
}
if (rows >= 3 && columns >= 2) {
for (int i = n - 2 - start; i >= start + 1; i--) {
matrix[i][start] = num++;
}
}
}
}

自己写的容易理解的解法

 public class Solution {
/*
* @param n: An integer
* @return: a square matrix
*/
public int[][] generateMatrix(int n) {
// write your code here
int[][] matrix = new int[n][n];
if (n <= 0) {
return matrix;
}
int start = 0, end = n - 1;
int count = 1;
for (start = 0; start <= (n + 1) / 2; start++, end--) {
for (int i = start; i <= end; i++) {
matrix[start][i] = count;
count++;
}
if (end > start) {
for (int i = start + 1; i <= end; i++) {
matrix[i][end] = count;
count++;
}
}
if (end > start) {
for (int i = end - 1; i >= start; i--) {
matrix[end][i] = count;
count++;
}
}
if (end > start && end > start + 1) {
for (int i = end - 1; i >= start + 1; i--) {
matrix[i][start] = count;
count++;
}
}
}
return matrix;
}
}

面试题36 -- 数组中的逆序对

题目:数组中前一个数字大于后一个数字则这两个数字构成一个逆序对,统计数组中所有的逆序对个数。

思路:暴力算法两个指针便利整个数组求得,时间复杂度O(n^2),空间复杂度O(1)。

   换一种思路,归并排序,注意复制到temp数组中是从后往前复制,如果前一个数组元素大于后一个,count加上有一个数组相应位置开始到其开始位置的元素个数,然后继续。求子数组内部的逆序对有可以递归的求两个子子数组之间的逆序对个数。时间复杂度即为归并排序的时间复杂度O(nlogn),空间复杂度O(n)。O(n)空间换时间效率。

 public class Solution {
/**
* @param A an array
* @return total of reverse pairs
*/
public long reversePairs(int[] A) {
// Write your code here
long count = 0;
if (A == null || A.length == 0) {
return count;
}
return mergeSort(A, new int[A.length], 0, A.length - 1);
}
public int mergeSort(int[] A, int[] temp, int start, int end) {
if (start == end) {
return 0;
}
int count = 0;
int mid = start + (end - start) / 2;
int leftCount = mergeSort(A, temp, start, mid);
int rightCount = mergeSort(A, temp, mid + 1, end);
int left = mid;
int right = end;
int indexTemp = end;
while (left >= start && right >= mid + 1) {
if (A[left] > A[right]) {
count += right - mid;
temp[indexTemp--] = A[left];
left--;
} else {
temp[indexTemp--] = A[right];
right--;
}
}
while (left >= start) {
temp[indexTemp--] = A[left--];
}
while (right >= mid + 1) {
temp[indexTemp--] = A[right--];
}
for (int i = start; i <= end; i++) {
A[i] = temp[i];
}
return leftCount + rightCount + count;
}
}

面试题64 -- 数据流中位数

思路I:建立一个大根堆和一个小根堆,分别来存储前半部分和后半部分的数据,最后返回大根堆的堆顶元素即可。设立一个计数器,偶数的时候加入大根堆,加入过程中注意所加入的值比小根堆堆顶元素大的话就吧该数插入到小根堆,小根堆堆顶元素加入到大根堆。奇数的时候加入小根堆,加入过程同上。

数字是不断进入数组的,在每次添加一个新的数进入数组的同时返回当前新数组的中位数。
中位数的定义:
中位数是排序后数组的中间值,如果有数组中有n个数,则中位数为A[(n-1)/2]。
比如:数组A=[1,2,3]的中位数是2,数组A=[1,19]的中位数是1。
样例
持续进入数组的数的列表为:[1, 2, 3, 4, 5],则返回[1, 1, 2, 2, 3]
持续进入数组的数的列表为:[4, 5, 1, 3, 2, 6, 0],则返回 [4, 4, 4, 3, 3, 3, 3]
持续进入数组的数的列表为:[2, 20, 100],则返回[2, 2, 20]

题目

 public class Solution {
public int count = 0;
PriorityQueue<Integer> maxHeap;
PriorityQueue<Integer> minHeap;
/**
* @param nums: A list of integers.
* @return: the median of numbers
*/
public int[] medianII(int[] nums) {
// write your code here
if (nums == null || nums.length == 0) {
return new int[0];
}
int[] result = new int[nums.length];
maxHeap = new PriorityQueue<Integer>(nums.length,
new Comparator<Integer>() {
public int compare(Integer a, Integer b) {
return b - a;
}
});
minHeap = new PriorityQueue<Integer>();
for (int i = 0; i < nums.length; i++) {
addNumber(nums[i]);
result[i] = maxHeap.peek();
}
return result;
}
public void addNumber(int number) {
if (count % 2 == 0) {
if (minHeap.size() == 0
|| minHeap.size() > 0 && number <= minHeap.peek()) {
maxHeap.offer(number);
} else {
minHeap.offer(number);
maxHeap.offer(minHeap.poll());
}
} else {
if (maxHeap.size() == 0
|| maxHeap.size() > 0 && number >= maxHeap.peek()) {
minHeap.offer(number);
} else {
maxHeap.offer(number);
minHeap.offer(maxHeap.poll());
}
}
count++;
}
}

思路II:同样建立大根堆和小根堆,但是不用计数器。每次加入元素分两种情况调整:minHeap.size() > maxHeap.size()和maxHeap.size() > minHeap.size() + 1。这种写法更简洁,推荐此种写法。

 public class Solution {
public int count = 0;
PriorityQueue<Integer> maxHeap;
PriorityQueue<Integer> minHeap;
/**
* @param nums: A list of integers.
* @return: the median of numbers
*/
public int[] medianII(int[] nums) {
// write your code here
if (nums == null || nums.length == 0) {
return new int[0];
}
int[] result = new int[nums.length];
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(nums.length, Collections.reverseOrder());
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int i = 0; i < nums.length; i++) {
if (maxHeap.isEmpty() || nums[i] < maxHeap.peek()) {
maxHeap.offer(nums[i]);
} else {
minHeap.offer(nums[i]);
}
if (minHeap.size() > maxHeap.size()) {
maxHeap.offer(minHeap.poll());
}
if (maxHeap.size() > minHeap.size() + 1) {
minHeap.offer(maxHeap.poll());
}
result[i] = maxHeap.peek();
}
return result;
}
}

Follow Up -- 滑动窗口中位数(Sliding Window Median)

Given an array of n integer, and a moving window(size k), move the window at each iteration from the start of the array, find the median of the element inside the window at each moving. (If there are even numbers in the array, return the N/2-th number after sorting the element in the window. )

Example
For array [1,2,7,8,5], moving window size k = 3. return [2,7,7] At first the window is at the start of the array like this [ | 1,2,7 | ,8,5] , return the median 2; then the window move one step forward. [1, | 2,7,8 | ,5], return the median 7; then the window move one step forward again. [1,2, | 7,8,5 | ], return the median 7;

思路:做法类似,但是要注意加入元素和删除元素都要进行两种情况调整!

 public class Solution {
/**
* @param nums: A list of integers.
* @return: The median of the element inside the window at each moving.
*/
public ArrayList<Integer> medianSlidingWindow(int[] nums, int k) {
// write your code here
ArrayList<Integer> result = new ArrayList<>();
if (nums == null || nums.length == 0 || k <= 0 || k > nums.length) {
return result;
}
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, Collections.reverseOrder());
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int i = 0; i < nums.length; i++) {
if (maxHeap.isEmpty() || nums[i] <= maxHeap.peek()) {
maxHeap.offer(nums[i]);
} else {
minHeap.offer(nums[i]);
}
if (minHeap.size() > maxHeap.size()) {
maxHeap.offer(minHeap.poll());
}
if (maxHeap.size() > minHeap.size() + 1) {
minHeap.offer(maxHeap.poll());
}
if (i >= k - 1) {
result.add(maxHeap.peek());
if (nums[i - k + 1] <= maxHeap.peek()) {
maxHeap.remove(nums[i - k + 1]);
} else {
minHeap.remove(nums[i - k + 1]);
}
if (minHeap.size() > maxHeap.size()) {
maxHeap.offer(minHeap.poll());
}
if (maxHeap.size() > minHeap.size() + 1) {
minHeap.offer(maxHeap.poll());
}
}
}
return result;
}
}

面试题33 -- 把数组排成最小的数

给定一个整数数组,请将其重新排序,以构造最小值。
给定 [3, 32, 321],通过将数组重新排序,可构造 6 个可能性数字: 3+32+321=332321
3+321+32=332132
32+3+321=323321
32+321+3=323213
321+3+32=321332
321+32+3=321323
其中,最小值为 321323,所以,将数组重新排序后,该数组变为 [321, 32, 3]。

题目

思路:注意此题实际上是定义两个数的排序规则,不再是两个数的数值大小的排序规则。规定两个数字a,b组成的数ab < ba时a < b。注意拼接成ab可能ab会有溢出,因此用String来存放。注意去掉前缀"0"。

 public class Solution {
/**
* @param nums n non-negative integer array
* @return a string
*/
public String minNumber(int[] nums) {
// Write your code here
if (nums == null || nums.length == 0) {
return null;
}
String[] temp = new String[nums.length];
for (int i = 0; i < nums.length; i++) {
temp[i] = String.valueOf(nums[i]);
}
Comparator<String> cmp = new Comparator<String>() {
public int compare(String s1, String s2) {
String a = s1.concat(s2);
String b = s2.concat(s1);
return a.compareTo(b);
}
};
Arrays.sort(temp, cmp);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < nums.length; i++) {
sb.append(temp[i]);
}
String s = sb.toString();
int i = 0;
for (i = 0; i < s.length(); i++) {
if (s.charAt(i) != '0') {
break;
}
}
if (i == s.length()) {
return "0";
}
return s.substring(i);
}
}

面试题32 -- 统计数字

统计数字
计算数字k在0到n中的出现的次数,k可能是0~9的一个值 样例
例如n=12,k=1,在 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],我们发现1出现了5次 (1, 10, 11, 12

暴力解法:若每一位为O(lonn),则时间复杂度为O(nlogn)。依次统计每一个数的0的个数。

 class Solution {
/*
* param k : As description.
* param n : As description.
* return: An integer denote the count of digit k in 1..n
*/
public int digitCounts(int k, int n) {
// write your code here
if (k < 0 || k > 9 || n < 0) {
return 0;
}
int count = 0;
for (int i = 0; i <= n; i++) {
count += singleCount(k, i);
}
return count;
}
public int singleCount(int k, int num) {
if (k == 0 && num == 0) {
return 1;
}
int count = 0;
while (num != 0) {
if (num % 10 == k) {
count++;
}
num = num / 10;
}
return count;
}
}

优雅解法:此解法只使用k取1 - 9的情况,因此k取0的情况单独采用暴力法来处理,注意同时k=0和n=0的特殊情况处理,返回1而不是0。

k = 1 - 9情况处理如下:

当某一位的数字小于i时,那么该位出现i的次数为:更高位数字x当前位数
当某一位的数字等于i时,那么该位出现i的次数为:更高位数字x当前位数+低位数字+1
当某一位的数字大于i时,那么该位出现i的次数为:(更高位数字+1)x当前位数
 class Solution {
/*
* param k : As description.
* param n : As description.
* return: An integer denote the count of digit k in 1..n
*/
public int digitCounts(int k, int n) {
// write your code here
if (k < 0 || k > 9 || n < 0) {
return 0;
}
if (k == 0) {
return boundaryCount(k, n);
}
return generalCount(k, n);
}
public int boundaryCount(int k, int n) {
int count = 0;
for (int i = 0; i <= n; i++) {
count += singleCount(k, i);
}
return count;
}
public int singleCount(int k, int num) {
if (k == 0 && num == 0) {
return 1;
}
int count = 0;
while (num != 0) {
if (num % 10 == 0) {
count++;
}
num = num / 10;
}
return count;
}
public int generalCount(int k, int n) {
int count = 0;
int low = 0;
int cur = 0;
int high = 0;
int factor = 1;
while (n / factor != 0) {
low = n - (n / factor) * factor;
cur = (n / factor) % 10;
high = n / (factor * 10);
if (cur < k) {
count += high * factor;
} else if (cur == k) {
count += high * factor + low + 1;
} else {
count += (high + 1) * factor;
}
factor *= 10;
}
return count;
}
}

面试题29 -- 数组中出现次数超过一半的数字

给定一个整型数组,找出主元素,它在数组中的出现次数严格大于数组元素个数的二分之一。

注意事项
You may assume that the array is non-empty and the majority number always exist in the array. 样例
给出数组[1,1,1,1,2,2,2],返回 1

思路:遍历数组时保存两个值:数组中的数字和次数,每遍历一个数,如果和保存数字相同,次数+1,否则次数减1,当次数为0就保存当前数字,并且次数置为1,最后保留下来的数字即为所求。

 public class Solution {
/**
* @param nums: a list of integers
* @return: find a majority number
*/
public int majorityNumber(ArrayList<Integer> nums) {
// write your code
if (nums == null || nums.size() == 0) {
return -1;
}
int candidate = 0;
int count = 0;
for (int i = 0; i < nums.size(); i++) {
if (count == 0) {
candidate = nums.get(i);
count = 1;
} else if (candidate == nums.get(i)) {
count++;
} else {
count--;
}
}
return candidate;
}
}

拓展I:

给定一个整型数组,找到主元素,它在数组中的出现次数严格大于数组元素个数的三分之一。

注意事项
数组中只有唯一的主元素 样例
给出数组[1,2,1,2,1,3,3] 返回 1

思路:三三抵销法,但是也有需要注意的地方:

1. 我们对cnt1,cnt2减数时,相当于丢弃了3个数字(当前数字,candidate1, candidate2)。也就是说,每一次丢弃数字,我们是丢弃3个不同的数字。

而Majority number超过了1/3所以它最后一定会留下来。

设定总数为N, majority number次数为m。丢弃的次数是x。则majority 被扔的次数是x

而m > N/3, N - 3x > 0.

3m > N,  N > 3x 所以 3m > 3x, m > x 也就是说 m一定没有被扔完

最坏的情况,Majority number每次都被扔掉了,但它一定会在n1,n2中。

2. 为什么最后要再检查2个数字呢(从头开始统计,而不用剩下的count1, count2)?因为数字的编排可以让majority 数被过度消耗,使其计数反而小于n2,或者等于n2.前面举的例子即是。

另一个例子:1 1 1 1 2 3 2 3 4 4 4 这个 1就会被消耗过多,最后余下的反而比4少。

 public class Solution {
/**
* @param nums: A list of integers
* @return: The majority number that occurs more than 1/3
*/
public int majorityNumber(ArrayList<Integer> nums) {
// write your code
if (nums == null || nums.size() == 0) {
return 0;
}
int candidate1 = 0;
int candidate2 = 0;
int count1 = 0;
int count2 = 0;
for (int i = 0; i < nums.size(); i++) {
int temp = nums.get(i);
if (count1 == 0) {
candidate1 = temp;
count1 = 1;
} else if (candidate1 == temp) {
count1++;
}
if (count2 == 0 && temp != candidate1) {
candidate2 = temp;
count2 = 1;
} else if (candidate2 == temp) {
count2++;
}
if (candidate1 != temp && candidate2 != temp) {
count1--;
count2--;
}
}
count1 = 0;
count2 = 0;
for (int i = 0; i < nums.size(); i++) {
if (candidate1 == nums.get(i)) {
count1++;
}
if (candidate2 == nums.get(i)) {
count2++;
}
}
if (count1 > count2) {
return candidate1;
}
return candidate2;
}
}

自己写的易于理解的解法,跟前面的面试题29的程序结构类似。注意最后不能根据剩下的count1和count2的个数来确定,因为超过1 / 3的数的个数可能提前被消耗的过多。要重新遍历集合统计出两个侯选数candidate1和candidate2的次数才能决定。

 public class Solution {
/*
* @param nums: a list of integers
* @return: The majority number that occurs more than 1/3
*/
public int majorityNumber(List<Integer> nums) {
// write your code here
if (nums == null || nums.size() == 0) {
return 0;
}
int candidate1 = 0, candidate2 = 0;
int count1 = 0, count2 = 0;
for (int i = 0; i < nums.size(); i++) {
if (count1 == 0) {
candidate1 = nums.get(i);
count1 = 1;
} else if (candidate1 == nums.get(i)) {
count1++;
} else if (count2 == 0) {
candidate2 = nums.get(i);
count2 = 1;
} else if (candidate2 == nums.get(i)) {
count2++;
} else {
count1--;
count2--;
}
}
count1 = 0;
count2 = 0;
for (int i = 0; i < nums.size(); i++) {
if (nums.get(i) == candidate1) {
count1++;
} else if (nums.get(i) == candidate2) {
count2++;
}
}
if (count1 > count2) {
return candidate1;
}
return candidate2;
}
}

拓展II

给定一个整型数组,找到主元素,它在数组中的出现次数严格大于数组元素个数的1/k。

注意事项

数组中只有唯一的主元素

样例
给出数组 [3,1,2,3,2,3,3,4,4,4] ,和 k = 3,返回 3

思路:与拓展一相同,只不过为了加快查找速度用HashMap存储数字和次数。注意:当次数为0时本次不能把新元素放进去!!!!!!要等下一轮方可放入新元素。

 public class Solution {
/**
* @param nums: A list of integers
* @param k: As described
* @return: The majority number
*/
public int majorityNumber(ArrayList<Integer> nums, int k) {
// write your code
if (nums == null || nums.size() == 0 || k <= 1) {
return 0;
}
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.size(); i++) { int temp = nums.get(i);
if (map.containsKey(temp)) {
map.put(temp, map.get(temp) + 1);
}
if (!map.containsKey(temp) && map.size() < k - 1) {
map.put(temp, 1);
}
if (!map.containsKey(temp) && map.size() == k - 1) {
ArrayList<Integer> removeList = new ArrayList<>();
for (int num : map.keySet()) {
map.put(num, map.get(num) - 1);
if (map.get(num) == 0) {
removeList.add(num);
}
}
for (Integer num : removeList) {
map.remove(num);
}
}
}
for (Integer key : map.keySet()) {
map.put(key, 0);
}
for (int i = 0; i < nums.size(); i++) {
int element = nums.get(i);
if (map.containsKey(element)) {
map.put(element, map.get(element) + 1);
}
}
int maxCount = 0;
int maxKey = 0;
for (Integer key : map.keySet()) {
if (map.get(key) > maxCount) {
maxCount = map.get(key);
maxKey = key;
}
}
return maxKey;
}
}

自己写的易于理解的解法,跟前面的面试题29的程序结构类似。

 public class Solution {
/*
* @param nums: A list of integers
* @param k: An integer
* @return: The majority number
*/
public int majorityNumber(List<Integer> nums, int k) {
// write your code here
if (nums == null || nums.size() == 0 || k <= 1) {
return 0;
}
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.size(); i++) {
int element = nums.get(i);
if (!map.containsKey(element) && map.size() < k - 1) {
map.put(element, 1);
} else if (!map.containsKey(element) && map.size() == k - 1) {
List<Integer> removeList = new ArrayList<>();
for (int key : map.keySet()) {
map.put(key, map.get(key) - 1);
if (map.get(key) == 0) {
removeList.add(key);
}
}
for (int key : removeList) {
map.remove(key);
}
} else {
map.put(element, map.get(element) + 1);
}
}
for (int key : map.keySet()) {
System.out.println(key);
map.put(key, 0);
}
for (int i = 0; i < nums.size(); i++) {
int element = nums.get(i);
if (map.containsKey(element)) {
map.put(element, map.get(element) + 1);
}
}
int maxKey = 0;
int maxCount = 0;
for (int key : map.keySet()) {
if (map.get(key) > maxCount) {
maxCount = map.get(key);
maxKey = key;
}
}
return maxKey;
}
}

面试题47 -- 不用加减乘除做加法

A + B Problem

Write a function that add two numbers A and B. You should not use + or any arithmetic operators.

Are a and b both 32-bit integers?
Yes. Can I use bit operation?
Sure you can. Example
Given a=1 and b=2 return 3

思路:十进制加法准则应用到二进制,先不算进位的将二进制的每位相加,相加规则就是异或运算,0+0=0,1+1=0,0+1=1,1+0=0。同时记录下进位,进位的求得就是按位与并且左一,将不算进位的相加结果再和进位结果相加,依次循环,知道进位为0即可。

 class Solution {
/*
* param a: The first integer
* param b: The second integer
* return: The sum of a and b
*/
public int aplusb(int a, int b) {
// write your code here, try to do it without arithmetic operators.
int sum = a ^ b;
int carry = (a & b) << 1;
while (carry != 0) {
a = sum;
b = carry;
sum = a ^ b;
carry = (a & b) << 1;
}
return sum;
}
}

面试题42 -- 翻转单词顺序

Reverse Words in a String

Given an input string, reverse the string word by word.

For example,
Given s = "the sky is blue",
return "blue is sky the". What constitutes a word?
A sequence of non-space characters constitutes a word.
Could the input string contain leading or trailing spaces?
Yes. However, your reversed string should not contain leading or trailing spaces.
How about multiple spaces between two words?
Reduce them to a single space in the reversed string.
 public class Solution {
/**
* @param s : A string
* @return : A string
*/
public String reverseWords(String s) {
// write your code
if (s == null || s.length() == 0) {
return "";
}
String[] strs = s.split(" ");
StringBuilder sb = new StringBuilder();
for (int i = strs.length - 1; i >= 0; i--) {
sb.append(strs[i]).append(" ");
}
if (sb.length() == 0) {
return "";
} else {
return sb.substring(0, sb.length() - 1);
}
}
}

面试题40 -- 数组中只出现一次的数字(Single Number)

Given 2*n + 1 numbers, every numbers occurs twice except one, find it.

Example
Given [1,2,2,1,3,4,3], return 4

思路:对数组中的每个元素做异或运算,同一个数字和它本省异或相抵消,最后的结果即为数组中只出现一次的数字。

 public class Solution {
/**
*@param A : an integer array
*return : a integer
*/
public int singleNumber(int[] A) {
// Write your code here
if (A == null || A.length == 0) {
return 0;
}
int sum = 0;
for (int i = 0; i < A.length; i++) {
sum = sum ^ A[i];
}
return sum;
}
}

Follow Up I

Given 2*n + 2 numbers, every numbers occurs twice except two, find them.

Example
Given [1,2,2,3,4,4,5,3] return 1 and 5

思路:将问题转化为数组中只出现一次的数字问题,首先数组中每个元素异或,由于有两个不同的数字,最后结果一定不为0,也就是说结果的二进制表示中一定有一位为1,现在找出这一位,然后根据这一位是否为1将数组分成两个子数组,两个只出现一次的数字必然被分到两个不同子数组中,剩下成对出现的数字也是成对被分到某一子数组中,这样两个子数组每个子数组都含有只出现一次的数字和成对出现的数字,问题转化为求子数组中只出现一次的数字问题。得解。

 public class Solution {
/**
* @param A : An integer array
* @return : Two integers
*/
public List<Integer> singleNumberIII(int[] A) {
// write your code here
List<Integer> result = new ArrayList<>();
if (A == null || A.length < 2) {
return result;
}
int num = 0;
for (int i = 0; i < A.length; i++) {
num ^= A[i];
}
int indexBit = 0;
for (int i = 0; i < 32; i++) {
if ((num & (1 << i)) != 0) {
indexBit = i;
break;
}
}
int num0 = 0;
int num1 = 0;
for (int i = 0; i < A.length; i++) {
if ((A[i] & (1 << indexBit)) != 0) {
num1 ^= A[i];
} else {
num0 ^= A[i];
}
}
result.add(num0);
result.add(num1);
return result;
}
}

Follow Up II

Given 3*n + 1 numbers, every numbers occurs triple times except one, find it.

Example
Given [1,1,2,3,3,3,2,2,4,1] return 4

思路:统计每个数字的对应二进制位上1的个数然后模3,余数即为只出现一次的数字的对应的二进制为的结果,由于是int,32位,循环32次即可。最后每一位的结果做或运算即可得到最后的结果。

同理,这个思路可以推广到k *n + 1问题,即数组中除了一个数字只出现一次,其余数字均出现k次(k >= 2)。

 public class Solution {
/**
* @param A : An integer array
* @return : An integer
*/
public int singleNumberII(int[] A) {
// write your code here
if (A == null || A.length == 0) {
return 0;
}
int result = 0;
for (int i = 0; i < 32; i++) {
int sum = 0;
for (int j = 0; j < A.length; j++) {
if ((A[j] & (1 << i)) != 0) {
sum++;
}
}
result |= (sum % 3) << i;
}
return result;
}
}

面试题49 -- 把字符串转换成整数

Implement function atoi to convert a string to an integer.

If no valid conversion could be performed, a zero value is returned.

If the correct value is out of the range of representable values, INT_MAX (2147483647) or INT_MIN (-2147483648) is returned.

Example
"10" => 10
"-1" => -1
"123123123123123" => 2147483647
"1.0" => 1

思路:注意三点即可。1.字符串事先做trim()处理消除首位空格  2.首位为+或-的时候索引才加一,否则不加。相应地只有为-符号为才为-,其余为+。  3.注意Integer.MIN_VALUE情况的处理,由于我们是去掉符号算和,注意和sum定义为long型以处理溢出的情况,所以这种情况溢出但是实际上是不算做溢出的,应当返回原来的值Integer.MIN_VALUE而不是-Integer.MAX_VALUE。

 public class Solution {
/**
* @param str: A string
* @return An integer
*/
public int atoi(String str) {
// write your code here
if (str == null || str.length() == 0) {
return 0;
}
str = str.trim();
boolean flag = true;
int start = 0;
//只有当第一位为+或者-start才加一,其余符号start统统从0开始!!!
if (str.charAt(0) == '-') {
flag = false;
start++;
} else if (str.charAt(0) == '+') {
start++;
}
long sum = 0;
while (start < str.length() && str.charAt(start) >= '0' && str.charAt(start) <= '9') {
sum = sum * 10 + str.charAt(start) - '0';
//为了处理"- 2 ^ 31 - 1"这种情况
if (sum > (long) Integer.MAX_VALUE + 1) {
if (flag) {
return Integer.MAX_VALUE;
} else {
return Integer.MIN_VALUE;
}
}
if (sum == (long) Integer.MAX_VALUE + 1) {
if (flag) {
return Integer.MAX_VALUE;
}
}
start++;
}
if (!flag) {
sum = -sum;
}
return (int) sum;
}
}

面试题53 -- 通配符匹配

Implement wildcard pattern matching with support for '?' and '*'.

'?' Matches any single character.
'*' Matches any sequence of characters (including the empty sequence).
The matching should cover the entire input string (not partial). Example
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "*") → true
isMatch("aa", "a*") → true
isMatch("ab", "?*") → true
isMatch("aab", "c*a*b") → false

思路:tricky

 public class Solution {
/**
* @param s: A string
* @param p: A string includes "?" and "*"
* @return: A boolean
*/
public boolean isMatch(String s, String p) {
// write your code here
if (s == null || p == null) {
return false;
}
int indexS = 0;
int match = 0;
int indexP = 0;
int startIndex = -1;
while (indexS < s.length()) {
if (indexP < p.length() && (s.charAt(indexS) == p.charAt(indexP)
|| p.charAt(indexP) == '?')) {
indexS++;
indexP++;
}
else if (indexP < p.length() && p.charAt(indexP) == '*') {
startIndex = indexP;
indexP++;
match = indexS;
}
else if (startIndex != -1) {
indexP = startIndex + 1;
match++;
indexS = match;
}
else {
return false;
}
}
while (indexP < p.length() && p.charAt(indexP) == '*') {
indexP++;
}
return indexP == p.length();
}
}

Fizz Buzz

Given number n. Print number from 1 to n. But:

when number is divided by 3, print "fizz".
when number is divided by 5, print "buzz".
when number is divided by both 3 and 5, print "fizz buzz". If n = 15, you should return: [
"1", "2", "fizz",
"4", "buzz", "fizz",
"7", "8", "fizz",
"buzz", "11", "fizz",
"13", "14", "fizz buzz"
]

题目

public class Solution {
/*
* @param n: An integer
* @return: A list of strings.
*/
public List<String> fizzBuzz(int n) {
// write your code here
List<String> result = new ArrayList<>();
for (int i = 1; i <= n; i++) {
if (i % 15 == 0) {
result.add("fizz buzz");
} else if (i % 3 == 0) {
result.add("fizz");
} else if (i % 5 == 0) {
result.add("buzz");
} else {
// 只能放String类型,需要加上""将int类型转成String类型
result.add(i + "");
}
}
return result;
}
}

面试题6 - 前序 + 中序 重建二叉树

给定二叉树的前序遍历数组和中序遍历数组,构建二叉树,不含重复的元素。比如前序遍历[1, 2, 4, 7, 3, 5, 6, 8]和中序遍历[4, 7, 2, 1, 5, 3, 8, 6],构建该二叉树。

思路:前序遍历第一个元素为根节点,然后根据该值在中序遍历中找到根节点的位置,这样左子树的前序遍历和遍历就找到了,右子树的前序遍历和中序遍历也找到了,然后递归下去即可。

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/**
*@param preorder : A list of integers that preorder traversal of a tree
*@param inorder : A list of integers that inorder traversal of a tree
*@return : Root of a tree
*/
public TreeNode buildTree(int[] preorder, int[] inorder) {
// write your code here
if (preorder == null || preorder.length == 0 || inorder == null
|| inorder.length == 0 || preorder.length != inorder.length) {
return null;
}
return helper(preorder, 0, inorder, 0, inorder.length - 1);
}
public TreeNode helper(int[] preorder, int preStart,
int[] inorder, int inStart, int inEnd) {
if (inStart > inEnd) {
return null;
}
TreeNode root = new TreeNode(preorder[preStart]);
int inRootIndex = 0;
for (int i = inStart; i <= inEnd; i++) {
if (preorder[preStart] == inorder[i]) {
inRootIndex = i;
}
}
root.left = helper(preorder, preStart + 1, inorder, inStart, inRootIndex - 1);
root.right = helper(preorder, preStart + 1 + inRootIndex - inStart, inorder, inRootIndex + 1, inEnd);
return root;
}
}

Follow Up - 中序 + 后序 重建二叉树

思路同上

 public class Solution {
/*
* @param inorder: A list of integers that inorder traversal of a tree
* @param postorder: A list of integers that postorder traversal of a tree
* @return: Root of a tree
*/
public TreeNode buildTree(int[] inorder, int[] postorder) {
// write your code here
if (inorder == null || inorder.length == 0 || postorder == null
|| postorder.length == 0 || inorder.length != postorder.length) {
return null;
}
return helper(inorder, 0, inorder.length - 1,
postorder, postorder.length - 1);
}
public TreeNode helper(int[] inorder, int inStart, int inEnd,
int[] postorder, int postStart) {
if (inStart > inEnd) {
return null;
}
TreeNode root = new TreeNode(postorder[postStart]);
int inRootIndex = 0;
for (int i = inStart; i <= inEnd; i++) {
if (inorder[i] == postorder[postStart]) {
inRootIndex = i;
}
}
root.left = helper(inorder, inStart, inRootIndex - 1, postorder, postStart - 1 - (inEnd - inRootIndex));
root.right = helper(inorder, inRootIndex + 1, inEnd, postorder, postStart - 1);
return root;
}
}

Follow Up - 层序 + 中序 重建二叉树

需要注意左右子树的层序遍历集合融合在一起,需要分开

import java.util.*;

public class Main {

    private static List<Integer> preorder = new ArrayList<>();
private static List<Integer> postorder = new ArrayList<>(); public static void main(String[] args) { Scanner in = new Scanner(System.in);
String[] s1 = in.nextLine().split(" ");
List<Integer> layer = new ArrayList<>();
for (String s : s1) {
layer.add(Integer.parseInt(s));
}
String[] s2 = in.nextLine().split(" ");
int[] inorder = new int[s2.length];
for (int i = 0; i < inorder.length; i++) {
inorder[i] = Integer.parseInt(s2[i]);
}
TreeNode root = constructFromLayerInOrder(layer, inorder, 0, inorder.length - 1);
preorder(root);
postorder(root);
System.out.println(preorder);
System.out.println(postorder);
} public static TreeNode constructFromLayerInOrder(List<Integer> layer, int[] inorder, int inStart, int inEnd) {
if (inStart > inEnd) {
return null;
}
TreeNode root = new TreeNode(layer.get(0));
int rootIndex = inStart;
for (; rootIndex <= inEnd; rootIndex++) {
if (inorder[rootIndex] == layer.get(0)) {
break;
}
}
List<Integer> leftLayer = new ArrayList<>();
List<Integer> rightLayer = new ArrayList<>();
for (int i = 1; i < layer.size(); i++) {
boolean isLeft = false;
for (int j = inStart; j < rootIndex; j++) {
if (inorder[j] == layer.get(i)) {
isLeft = true;
}
}
if (isLeft == true) {
leftLayer.add(layer.get(i));
} else {
rightLayer.add(layer.get(i));
}
}
root.left = constructFromLayerInOrder(leftLayer, inorder, inStart, rootIndex - 1);
root.right = constructFromLayerInOrder(rightLayer, inorder, rootIndex + 1, inEnd);
return root;
} public static void preorder(TreeNode root) {
if (root == null) {
return;
}
preorder.add(root.val);
preorder(root.left);
preorder(root.right);
} public static void postorder(TreeNode root) {
if (root == null) {
return;
}
postorder(root.left);
postorder(root.right);
postorder.add(root.val);
} } class TreeNode {
int val;
TreeNode left;
TreeNode right;
public TreeNode(int val) {
this.val = val;
}
}

二叉树前序遍历

递归

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/*
* @param root: A Tree
* @return: Preorder in ArrayList which contains node values.
*/
public List<Integer> preorderTraversal(TreeNode root) {
// write your code here
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
dfsHelper(result, root);
return result;
}
public void dfsHelper(List<Integer> result, TreeNode root) {
if (root == null) {
return;
}
result.add(root.val);
dfsHelper(result, root.left);
dfsHelper(result, root.right);
}
}

非递归

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/*
* @param root: A Tree
* @return: Preorder in ArrayList which contains node values.
*/
public List<Integer> preorderTraversal(TreeNode root) {
// write your code here
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
return result;
}
}

分治

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/*
* @param root: A Tree
* @return: Preorder in ArrayList which contains node values.
*/
public List<Integer> preorderTraversal(TreeNode root) {
// write your code here
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
List<Integer> leftResult = preorderTraversal(root.left);
List<Integer> rightResult = preorderTraversal(root.right);
result.add(root.val);
result.addAll(leftResult);
result.addAll(rightResult);
return result;
}
}

二叉树中序遍历

递归

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/*
* @param root: A Tree
* @return: Inorder in ArrayList which contains node values.
*/
public List<Integer> inorderTraversal(TreeNode root) {
// write your code here
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
dfsHelper(result, root);
return result;
}
public void dfsHelper(List<Integer> result, TreeNode root) {
if (root == null) {
return;
}
dfsHelper(result, root.left);
result.add(root.val);
dfsHelper(result, root.right);
}
}

非递归

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/*
* @param root: A Tree
* @return: Inorder in ArrayList which contains node values.
*/
public List<Integer> inorderTraversal(TreeNode root) {
// write your code here
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
result.add(cur.val);
cur = cur.right;
}
return result;
}
}

分治

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/*
* @param root: A Tree
* @return: Inorder in ArrayList which contains node values.
*/
public List<Integer> inorderTraversal(TreeNode root) {
// write your code here
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
List<Integer> leftResult = inorderTraversal(root.left);
List<Integer> rightResult = inorderTraversal(root.right);
result.addAll(leftResult);
result.add(root.val);
result.addAll(rightResult);
return result;
}
}

二叉树后序遍历

递归

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/*
* @param root: A Tree
* @return: Postorder in ArrayList which contains node values.
*/
public List<Integer> postorderTraversal(TreeNode root) {
// write your code here
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
dfsHelper(result, root);
return result;
}
public void dfsHelper(List<Integer> result, TreeNode root) {
if (root == null) {
return;
}
dfsHelper(result, root.left);
dfsHelper(result, root.right);
result.add(root.val);
}
}

非递归

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/*
* @param root: A Tree
* @return: Postorder in ArrayList which contains node values.
*/
public List<Integer> postorderTraversal(TreeNode root) {
// write your code here
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
TreeNode pre = null, cur = root;
while (!stack.isEmpty()) {
cur = stack.peek();
if (pre == null || pre.left == cur || pre.right == cur) {
if (cur.left != null) {
stack.push(cur.left);
} else if (cur.right != null) {
stack.push(cur.right);
}
} else if (cur.left == pre) {
if (cur.right != null) {
stack.push(cur.right);
}
} else {
stack.pop();
result.add(cur.val);
}
pre = cur;
}
return result;
}
}

分治

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/*
* @param root: A Tree
* @return: Postorder in ArrayList which contains node values.
*/
public List<Integer> postorderTraversal(TreeNode root) {
// write your code here
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
List<Integer> leftResult = postorderTraversal(root.left);
List<Integer> rightResult = postorderTraversal(root.right);
result.addAll(leftResult);
result.addAll(rightResult);
result.add(root.val);
return result;
}
}

层序遍历 -- 从上到下(BFS)

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/*
* @param root: A Tree
* @return: Level order a list of lists of integer
*/
public List<List<Integer>> levelOrder(TreeNode root) {
// write your code here
List<List<Integer>> result = new ArrayList<>();
if (root == null) {
return result;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
List<Integer> tempList = new ArrayList<>();
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
tempList.add(node.val);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
result.add(tempList);
}
return result;
}
}

层序遍历 -- 从下到上(BFS)

思路同上,只是调用的是List的add(int index, E element)方法result.add(0, tempList)

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/*
* @param root: A tree
* @return: buttom-up level order a list of lists of integer
*/
public List<List<Integer>> levelOrderBottom(TreeNode root) {
// write your code here
List<List<Integer>> result = new ArrayList<>();
if (root == null) {
return result;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
List<Integer> tempList = new ArrayList<>();
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
tempList.add(node.val);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
result.add(0, tempList);
}
return result;
}
}

之字型遍历 -- 先从左往右,再从右往左,以此类推。

思路同上,要定义一个标记为startLeft标识是否要从左往右打印,如果是,调用tempList.add(node.val),如果不是,调用tempList.add(0, node.val)

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/*
* @param root: A Tree
* @return: A list of lists of integer include the zigzag level order traversal of its nodes' values.
*/
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
// write your code here
List<List<Integer>> result = new ArrayList<>();
if (root == null) {
return result;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
boolean startLeft = true;
while (!queue.isEmpty()) {
List<Integer> tempList = new ArrayList<>();
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
if (startLeft) {
tempList.add(node.val);
} else {
tempList.add(0, node.val);
}
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
result.add(tempList);
startLeft = !startLeft;
}
return result;
}
}

Binary Tree Path Sum -- 二叉树中和为某一值的从根节点到叶节点的路径

注意:前序遍历,每访问到一个节点时,添加进路径中,并累加当前和。如果该节点为叶节点,则判断当前和是否等于给定值,如果等于则将路径加入到结果集中。记得每次递归返回上一层时把当前节点从路径中删除。

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/*
* @param root: the root of binary tree
* @param target: An integer
* @return: all valid paths
*/
public List<List<Integer>> binaryTreePathSum(TreeNode root, int target) {
// write your code here
List<List<Integer>> result = new ArrayList<>();
if (root == null) {
return result;
}
dfsHelper(result, new ArrayList<Integer>(), root, target);
return result;
}
public void dfsHelper(List<List<Integer>> result, List<Integer> tempList, TreeNode root, int target) {
tempList.add(root.val);
if (root.left == null && root.right == null) {
if (target == root.val) {
result.add(new ArrayList<Integer>(tempList));
}
tempList.remove(tempList.size() - 1);
return;
}
if (root.left != null) {
dfsHelper(result, tempList, root.left, target - root.val);
}
if (root.right != null) {
dfsHelper(result, tempList, root.right, target - root.val);
}
tempList.remove(tempList.size() - 1);
}
}

Clone Binary Tree -- 复制二叉树

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/*
* @param root: The root of binary tree
* @return: root of new tree
*/
public TreeNode cloneTree(TreeNode root) {
// write your code here
if (root == null) {
return null;
}
TreeNode newRoot = new TreeNode(root.val);
newRoot.left = cloneTree(root.left);
newRoot.right = cloneTree(root.right);
return newRoot;
}
}

Clone Mirror Binary Tree -- 二叉树镜像

对于每个非叶节点,交换其左右子节点,递归下去。

 public class Solution {
public void mirrorBinaryTree(TreeNode root) {
if (root == null) {
return;
}
if (root.left == null && root.right == null) {
return;
}
TreeNode tempNode = root.left;
root.left = root.right;
root.right = tempNode;
if (root.left != null) {
mirrorBinaryTree(root.left);
}
if (root.right != null) {
mirrorBinaryTree(root.right);
}
}
}

String Permutation -- 判断一个串是否是另一个串的全排列

思路:建一个长度为256的int型数组,在字符串A中增加次数,在字符串B中减去次数,最后遍历一遍数组,如果全部元素为0才能说明A是B的全排列,否则不是。

 public class Solution {
/*
* @param A: a string
* @param B: a string
* @return: a boolean
*/
public boolean Permutation(String A, String B) {
// write your code here
int[] count = new int[256];
for (int i = 0; i < A.length(); i++) {
count[A.charAt(i)]++;
}
for (int i = 0; i < B.length(); i++) {
count[B.charAt(i)]--;
}
for (int i = 0; i < 256; i++) {
if (count[i] != 0) {
return false;
}
}
return true;
}
}

String Permutation II -- 求一个字符串的所有全排列字符串

Min Stack

思路:一个栈存值,另一个栈存当先栈中的最小值。

 public class MinStack {

     Stack<Integer> stack;
Stack<Integer> minStack; public MinStack() {
// do intialization if necessary
stack = new Stack<Integer>();
minStack = new Stack<Integer>();
} /*
* @param number: An integer
* @return: nothing
*/
public void push(int number) {
// write your code here
stack.push(number);
if (minStack.isEmpty()) {
minStack.push(number);
} else {
minStack.push(Math.min(number, minStack.peek()));
}
} /*
* @return: An integer
*/
public int pop() {
// write your code here
minStack.pop();
return stack.pop();
} /*
* @return: An integer
*/
public int min() {
// write your code here
if (minStack.isEmpty()) {
return -1;
}
return minStack.peek();
}
}

二叉搜索树转双向链表

Convert a binary search tree to doubly linked list with in-order traversal.

Example
Given a binary search tree: 4
/ \
2 5
/ \
1 3
return 1<->2<->3<->4<->5

思路:建立一个辅助函数helper,其函数返回类型为ResultType,ResultType记录当前节点为根节点的树中的最左边的双向链表节点和最右边的双向链表节点。

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
* Definition for Doubly-ListNode.
* public class DoublyListNode {
* int val;
* DoublyListNode next, prev;
* DoublyListNode(int val) {
* this.val = val;
* this.next = this.prev = null;
* }
* }
*/ class ResultType {
DoublyListNode first;
DoublyListNode last;
public ResultType(DoublyListNode first, DoublyListNode last) {
this.first = first;
this.last = last;
}
} public class Solution {
/*
* @param root: The root of tree
* @return: the head of doubly list node
*/
public DoublyListNode bstToDoublyList(TreeNode root) {
// write your code here
if (root == null) {
return null;
}
ResultType result = helper(root);
return result.first;
}
public ResultType helper(TreeNode root) {
if (root == null) {
return null;
}
ResultType result = new ResultType(null, null);
DoublyListNode node = new DoublyListNode(root.val);
ResultType left = helper(root.left);
ResultType right = helper(root.right);
if (left == null) {
result.first = node;
} else {
result.first = left.first;
node.prev = left.last;
left.last.next = node;
}
if (right == null) {
result.last = node;
} else {
result.last = right.last;
node.next = right.first;
right.first.prev = node;
}
return result;
}
}

Maximum Subarray -- 连续子数组的最大和

思路:sum当前和  minSum到当前位置的和最小的子数组(index从0开始)  max当前位置为止连续子数组的最大和

 public class Solution {
/*
* @param nums: A list of integers
* @return: A integer indicate the sum of max subarray
*/
public int maxSubArray(int[] nums) {
// write your code here
if (nums == null || nums.length == 0) {
return 0;
}
int sum = 0, minSum = 0;
int max = Integer.MIN_VALUE;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
if (sum - minSum > max) {
max = sum - minSum;
}
minSum = Math.min(minSum, sum);
}
return max;
}
}

Maximum Subarray II

找两个不重叠的子数组使得两个子数组的和加起来最大

思路:从前往后做一遍Maximum Subarray,用一个数组left记录到当前位置为止子数组的最大和;从后往前做一遍Maximum Subarray,用一个数组right记录到当前位置为止子数组的最大和;然后两个子数组的和等价于left[i] + right[i + 1],遍历i求得该最大值。

 public class Solution {
/*
* @param nums: A list of integers
* @return: An integer denotes the sum of max two non-overlapping subarrays
*/
public int maxTwoSubArrays(List<Integer> nums) {
// write your code here
if (nums == null || nums.size() <= 1) {
return 0;
}
int sum = 0, minSum = 0;
int max = Integer.MIN_VALUE;
int[] left = new int[nums.size()];
for (int i = 0; i < nums.size(); i++) {
sum += nums.get(i);
if (sum - minSum > max) {
max = sum - minSum;
}
minSum = Math.min(minSum, sum);
left[i] = max;
}
sum = 0;
minSum = 0;
max = Integer.MIN_VALUE;
int[] right = new int[nums.size()];
for (int i = nums.size() - 1; i >= 0; i--) {
sum += nums.get(i);
if (sum - minSum > max) {
max = sum - minSum;
}
minSum = Math.min(minSum, sum);
right[i] = max;
}
max = Integer.MIN_VALUE;
for (int i = 0; i < nums.size() - 1; i++) {
if (left[i] + right[i + 1] > max) {
max = left[i] + right[i + 1];
}
}
return max;
}
}

Maximum Subarray III

思路:

local[i][k]表示前i个元素取k个子数组并且必须包含第i个元素的最大和。

global[i][k]表示前i个元素取k个子数组不一定包含第i个元素的最大和。

local[i][k]的状态函数:

max(global[i-1][k-1], local[i-1][k]) + nums[i-1]

有两种情况,第一种是第i个元素自己组成一个子数组,则要在前i-1个元素中找k-1个子数组,第二种情况是第i个元素属于前一个元素的子数组,因此要在i-1个元素中找k个子数组(并且必须包含第i-1个元素,这样第i个元素才能合并到最后一个子数组中),取两种情况里面大的那个。

global[i][k]的状态函数:

max(global[i-1][k],local[i][k])

有两种情况,第一种是不包含第i个元素,所以要在前i-1个元素中找k个子数组,第二种情况为包含第i个元素,在i个元素中找k个子数组且必须包含第i个元素,取两种情况里面大的那个。

注意这一题中DP的方向是先从上往下,再从左往右,而不是通常的先从左往右,再从上往下。这样做是因为localMax[j - 1][j]和globalMax[j - 1][j]置为Integer.MIN_VALUE的次数取决于列循环次数,而不是取决于行循环次数,否则二维数组下标会越界。

 public class Solution {
/*
* @param nums: A list of integers
* @param k: An integer denote to find k non-overlapping subarrays
* @return: An integer denote the sum of max k non-overlapping subarrays
*/
public int maxSubArray(int[] nums, int k) {
// write your code here
if (nums == null || nums.length == 0 || k <= 0 || k > nums.length) {
return 0;
}
int[][] localMax = new int[nums.length + 1][k + 1];
int[][] globalMax = new int[nums.length + 1][k + 1];
for (int j = 1; j <= k; j++) {
localMax[j - 1][j] = Integer.MIN_VALUE;
globalMax[j - 1][j] = Integer.MIN_VALUE;
for (int i = j; i <= nums.length; i++) {
localMax[i][j] = Math.max(globalMax[i - 1][j - 1], localMax[i - 1][j]) + nums[i - 1];
globalMax[i][j] = Math.max(globalMax[i - 1][j], localMax[i][j]);
}
}
return globalMax[nums.length][k];
}
}

Reverse Pairs -- 求数组中逆序对的个数

思路:归并排序中统计逆序对的个数,统计的时候顺便把temp也赋值了,此时mergeSort函数要有返回值,表示当前数组的逆序对个数。

 public class Solution {
/*
* @param A: an array
* @return: total of reverse pairs
*/
public long reversePairs(int[] A) {
// write your code here
if (A == null || A.length == 0) {
return 0;
}
return mergeSort(A, new int[A.length], 0, A.length - 1);
}
public int mergeSort(int[] A, int[] temp, int start, int end) {
if (start == end) {
return 0;
}
int count = 0;
int mid = start + (end - start) / 2;
int leftCount = mergeSort(A, temp, start, mid);
int rightCount = mergeSort(A, temp, mid + 1, end);
int left = mid, right = end;
int index = end;
while (left >= start && right >= mid + 1) {
if (A[left] > A[right]) {
count += right - mid;
temp[index--] = A[left];
left--;
} else {
temp[index--] = A[right];
right--;
}
}
while (left >= start) {
temp[index--] = A[left--];
}
while (right >= mid + 1) {
temp[index--] = A[right--];
}
for (int i = start; i <= end; i++) {
A[i] = temp[i];
}
return count + leftCount + rightCount;
}
}

Reorder array to construct the minimum number -- 把数组排成最小的数

思路:由于组合之后的数很大,这是一个大数问题,所以用字符串来处理。先把整型数组转成字符串数组,再定义比较器来排序:Arrays.sort(temp, cmp),关键是比较器的比较规则的定义。最后注意前导0的处理,挑选出第一个不为'0'的位置,然后截取字符串返回即可,这里可能字符串全是0,这种情况特殊处理,返回"0"

Kth Largest Element -- 找第K大元素

思路I:小根堆

 class Solution {
/*
* @param k : description of k
* @param nums : array of nums
* @return: description of return
*/
public int kthLargestElement(int k, int[] nums) {
// write your code here
if (nums == null || nums.length == 0 || k < 1 || k > nums.length) {
return 0;
}
PriorityQueue<Integer> pq = new PriorityQueue<Integer>(k);
for (int i = 0; i < nums.length; i++) {
if (pq.size() < k) {
pq.offer(nums[i]);
} else {
if (nums[i] > pq.peek()) {
pq.poll();
pq.offer(nums[i]);
}
}
}
return pq.peek();
}
};

思路II:快排。从大到小排序,选第K大的数。

 class Solution {
/*
* @param k : description of k
* @param nums : array of nums
* @return: description of return
*/
public int kthLargestElement(int k, int[] nums) {
// write your code here
if (nums == null || nums.length == 0 || k < 1 || k > nums.length) {
return 0;
}
return quickSort(nums, 0, nums.length - 1, k);
}
public int quickSort(int[] nums, int start, int end, int k) {
if (start == end) {
return nums[start];
}
int mid = start + (end - start) / 2;
int pivot = nums[mid];
int left = start, right = end;
while (left <= right) {
while (left <= right && nums[left] > pivot) {
left++;
}
while (left <= right && nums[right] < pivot) {
right--;
}
if (left <= right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
right--;
}
}
if (k <= right - start + 1) {
return quickSort(nums, start, right, k);
}
if (k >= left - start + 1) {
return quickSort(nums, left, end, k - (left - start));
}
return nums[right + 1];
}
};

Single Number -- 数组中一个数出现1次,其余数出现2次

思路:异或,最后得出的数即为所求。a ^ a = 0  0 ^ a = a

 public class Solution {
/*
* @param A: An integer array
* @return: An integer
*/
public int singleNumber(int[] A) {
// write your code here
if (A == null || A.length % 2 == 0) {
return 0;
}
int result = 0;
for (int i = 0; i < A.length; i++) {
result ^= A[i];
}
return result;
}
}

Single Number II -- 数组中一个数出现1次,其余数出现3次

思路:对于数组中所有数,某一位中bit 1的个数为 3 * n + 1,说明这个bit位上落单的1就是那个在数组中只出现1次的数的,循环32次找出所有落单的数的bit 1

 public class Solution {
/*
* @param A: An integer array
* @return: An integer
*/
public int singleNumberII(int[] A) {
// write your code here
if (A == null || A.length % 3 != 1) {
return 0;
}
int result = 0;
for (int i = 0; i < 32; i++) {
int count = 0;
for (int j = 0; j < A.length; j++) {
if ((A[j] & (1 << i)) != 0) {
count++;
}
}
result |= (count % 3) << i;
}
return result;
}
}

Single Number III -- 数组中有2个数出现一次,其余数出现2次

思路:将只出现1次的两个数分开,再利用Single Number的解法即可。关键是符合分开这两个数:方法是先将数组中所有元素进行一遍异或运算,得到的结果是这两个数异或的结果,然后取这个结果的最右边开始的为bit 1的位置。在这个位置上,一个数在该位置上为bit 1,另一个数在该位置上为bit 0,这样通过判断数组中一个数在该位置上的bit位是否为1就可以分开这两个数,至于其余出现两次的数,这些数字成对的落在哪个出现一次的数的区域都可以,不影响结果。

 public class Solution {
/*
* @param A: An integer array
* @return: An integer array
*/
public List<Integer> singleNumberIII(int[] A) {
// write your code here
List<Integer> result = new ArrayList<>();
if (A == null || A.length == 0 || A.length % 2 != 0) {
return result;
}
int num = 0;
for (int i = 0; i < A.length; i++) {
num ^= A[i];
}
int index = 0;
for (int i = 0; i < 32; i++) {
if ((num & (1 << i)) != 0) {
index = i;
break;
}
}
int num1 = 0, num2 = 0;
for (int i = 0; i < A.length; i++) {
if ((A[i] & (1 << index)) != 0) {
num1 ^= A[i];
} else {
num2 ^= A[i];
}
}
result.add(num1);
result.add(num2);
return result;
}
}

Two Sum -- 数组中找两个数的和为给定值target,返回这两个数的索引

思路:注意题目要求的是返回这两个数的索引,而不是返回这两个数。所以常规的做法(先对数组排序,然后前后指针来求得这两个数,其和等于target)行不通,就算用Map来在排序前存储好每个数和其索引的映射也不行,因为有可能是 3 + 3 = 6这种情况,此时Map不能同时存储两个3的索引。

所以只能用另一种方法:遍历数组的每个元素,每次都用Map存储  target - 当前元素值 --> 当前元素索引的映射关系,如果当前map含有target - 当前元素值,说明找到了,返回这两个索引。

 public class Solution {
/*
* @param numbers: An array of Integer
* @param target: target = numbers[index1] + numbers[index2]
* @return: [index1 + 1, index2 + 1] (index1 < index2)
*/
public int[] twoSum(int[] numbers, int target) {
// write your code here
if (numbers == null || numbers.length < 2) {
return new int[0];
}
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < numbers.length; i++) {
if (map.containsKey(numbers[i])) {
return new int[] {map.get(numbers[i]), i};
}
map.put(target - numbers[i], i);
}
return new int[0];
}
}

A + B Problem -- 不用加减乘除做加法

思路:既然不能用四则运算,那么只能用位运算了。加法分为两步:1. 不进位做加法 -- 类似于二进制中的异或运算 0 ^ 0 = 0, 0 ^ 1 = 0, 1 ^ 0 = 0, 1 & 1 = 1  2. 进位与不进位做加法的结果相加 -- 类似于二进制的先位与运算 0 & 0 = 0,0 & 1 = 0,1 & 0 = 0,1 & 1 = 1,再左移一位。

 public class Solution {
/**
* @param a: An integer
* @param b: An integer
* @return: The sum of a and b
*/
public int aplusb(int a, int b) {
// write your code here
int sum = a ^ b;
int carry = (a & b) << 1;
while (carry != 0) {
a = sum;
b = carry;
sum = a ^ b;
carry = (a & b) << 1;
}
return sum;
}
}

Maximum Depth of Binary Tree -- 二叉树最大深度

思路I: 分治法Divide Conquer

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/
public class Solution {
/**
* @param root: The root of binary tree.
* @return: An integer.
*/
public int maxDepth(TreeNode root) {
// write your code here
if (root == null) {
return 0;
}
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return 1 + Math.max(left, right);
}
}

思路II:遍历法Traverse

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/
public class Solution {
/**
* @param root: The root of binary tree.
* @return: An integer.
*/
private int depth = 0;
public int maxDepth(TreeNode root) {
// write your code here
if (root == null) {
return 0;
}
helper(root, 1);
return depth;
}
public void helper(TreeNode root, int curDepth) {
if (root == null) {
return;
}
if (curDepth > depth) {
depth = curDepth;
}
helper(root.left, curDepth + 1);
helper(root.right, curDepth + 1);
}
}

Reverse Words in a String -- 翻转单词顺序

Given an input string, reverse the string word by word.

For example,
Given s = "the sky is blue",
return "blue is sky the". Have you met this question in a real interview? Yes
Clarification
What constitutes a word?
A sequence of non-space characters constitutes a word.
Could the input string contain leading or trailing spaces?
Yes. However, your reversed string should not contain leading or trailing spaces.
How about multiple spaces between two words?
Reduce them to a single space in the reversed string.

题目

思路I:调用String的split(" ")的方法 + StringBuilder从split的数组后往前加入单词和" ",如果split的数组中有""说明这里有大于1个的空格字符,这种情况就不要加入到StringBuilder中了。

 public class Solution {
/*
* @param s: A string
* @return: A string
*/
public String reverseWords(String s) {
// write your code here
if (s == null || s.length() == 0) {
return "";
}
String[] str = s.split(" ");
StringBuilder sb = new StringBuilder();
for (int i = str.length - 1; i >= 0; i--) {
if (str[i] == "") {
continue;
}
sb.append(str[i]).append(" ");
}
if (sb.length() == 0) {
return "";
}
return sb.substring(0, sb.length() - 1);
}
}

变种:要求保留原有的所有空格

思路:两步翻转法:1 翻转整个字符串  2 翻转每个单词,采用前后指针实现,这里的注意事项标注在了代码中。

 public class Solution {
public String ReverseSentence(String str) {
if (str == null || str.length() <= 1) {
return str;
}
char[] c = str.toCharArray();
reverse(c, 0, c.length - 1);
System.out.println(new String(c));
int start = 0, end = 0;
while (start < c.length) {
if (c[start] == ' ') {
start++;
end++;
} else if (end == c.length || c[end] == ' ') { // 这里的else if的判断一定要注意顺序不能颠倒,否则会发生索引越界的异常。
end--;
reverse(c, start, end);
start = end + 1;
end = start;
} else {
end++;
}
}
return new String(c);
}
public void reverse(char[] c, int start, int end) {
int left = start, right = end;
while (left < right) {
char temp = c[left];
c[left] = c[right];
c[right] = temp;
left++;
right--;
}
}
}

Rotate String

Given a string and an offset, rotate string by offset. (rotate from left to right)

Example
Given "abcdefg". offset=0 => "abcdefg"
offset=1 => "gabcdef"
offset=2 => "fgabcde"
offset=3 => "efgabcd"

题目

思路:三步翻转法

 public class Solution {
/*
* @param str: An array of char
* @param offset: An integer
* @return: nothing
*/
public void rotateString(char[] str, int offset) {
// write your code here
if (str == null || str.length <= 1
|| offset < 0 || offset % str.length == 0) {
return;
}
offset = offset % str.length;
reverse(str, 0, str.length - 1);
reverse(str, 0, offset - 1);
reverse(str, offset, str.length - 1);
}
public void reverse(char[] str, int start, int end) {
int left = start, right = end;
while (left < right) {
char temp = str[left];
str[left] = str[right];
str[right] = temp;
left++;
right--;
}
}
}

Search for a Range -- 排序数组中找给定值的开始位置和结束位置

思路:二次二分,第一次二分找给定值在排序数组中第一次出现的位置,第二次二分找给定值在排序数组中最后一次出现的位置。

 public class Solution {
/*
* @param A: an integer sorted array
* @param target: an integer to be inserted
* @return: a list of length 2, [index1, index2]
*/
public int[] searchRange(int[] A, int target) {
// write your code here
if (A == null || A.length == 0) {
return new int[] {-1, -1};
}
int start = 0, end = A.length - 1;
while (start + 1 < end) {
int mid = start + (end - start) / 2;
if (A[mid] == target) {
end = mid;
} else if (A[mid] < target) {
start = mid;
} else {
end = mid;
}
}
int[] result = new int[2];
if (A[start] == target) {
result[0] = start;
} else if (A[end] == target) {
result[0] = end;
} else {
return new int[] {-1, -1};
}
start = 0;
end = A.length - 1;
while (start + 1 < end) {
int mid = start + (end - start) / 2;
if (A[mid] == target) {
start = mid;
} else if (A[mid] < target) {
start = mid;
} else {
end = mid;
}
}
if (A[end] == target) {
result[1] = end;
} else if (A[start] == target) {
result[1] = start;
}
// else {
// return new int[] {-1, -1};
// }
return result;
}
}

Product of Array Exclude Itself

Given an integers array A.

Define B[i] = A[0] * ... * A[i-1] * A[i+1] * ... * A[n-1], calculate B WITHOUT divide operation.

Have you met this question in a real interview? Yes
Example
For A = [1, 2, 3], return [6, 3, 2].

题目

思路:某一个位置的乘积值是有该位置左边所有元素的乘积和右边所有元素的乘积的乘积,所以可以建立一个left数组和right数组,left[i]表示前i个元素的乘积之和,right[i]表示后i个元素的乘积之和。

 public class Solution {
/*
* @param nums: Given an integers array A
* @return: A long long array B and B[i]= A[0] * ... * A[i-1] * A[i+1] * ... * A[n-1]
*/
public List<Long> productExcludeItself(List<Integer> nums) {
// write your code here
List<Long> result = new ArrayList<>();
if (nums == null || nums.size() == 0) {
return result;
}
long[] left = new long[nums.size() + 1];
left[0] = 1;
long[] right = new long[nums.size() + 1];
right[0] = 1;
for (int i = 1; i <= nums.size(); i++) {
left[i] = left[i - 1] * nums.get(i - 1);
right[i] = right[i - 1] * nums.get(nums.size() - i);
}
for (int i = 0; i < nums.size(); i++) {
result.add(left[i] * right[nums.size() - i - 1]);
}
return result;
}
}

序列化和反序列化二叉树

Design an algorithm and write code to serialize and deserialize a binary tree. Writing the tree to a file is called 'serialization' and reading back from the file to reconstruct the exact same binary tree is 'deserialization'.

 Notice

There is no limit of how you deserialize or serialize a binary tree, LintCode will take your output of serialize as the input of deserialize, it won't check the result of serialize.

Have you met this question in a real interview? Yes
Example
An example of testdata: Binary tree {3,9,20,#,#,15,7}, denote the following structure: 3
/ \
9 20
/ \
15 7
Our data serialization use bfs traversal. This is just for when you got wrong answer and want to debug the input. You can use other method to do serializaiton and deserialization.

题目

思路:bfs。反序列化的时候,要设置一个标记为isLeftChild来判断当前节点是否为根节点的左儿子,以及建一个list用来存放非空节点,用index标注当前根节点。

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/**
* This method will be invoked first, you should design your own algorithm
* to serialize a binary tree which denote by a root node to a string which
* can be easily deserialized by your own "deserialize" method later.
*/
public String serialize(TreeNode root) {
// write your code here
if (root == null) {
return "{}";
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
StringBuilder sb = new StringBuilder();
sb.append("{").append(root.val).append(",");
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node.left != null) {
queue.offer(node.left);
sb.append(node.left.val).append(",");
} else {
sb.append("#").append(",");
}
if (node.right != null) {
queue.offer(node.right);
sb.append(node.right.val).append(",");
} else {
sb.append("#").append(",");
}
}
while (sb.charAt(sb.length() - 1) == ',' || sb.charAt(sb.length() - 1) == '#') {
sb.deleteCharAt(sb.length() - 1);
}
sb.append("}");
return sb.toString();
} /**
* This method will be invoked second, the argument data is what exactly
* you serialized at method "serialize", that means the data is not given by
* system, it's given by your own serialize method. So the format of data is
* designed by yourself, and deserialize it here as you serialize it in
* "serialize" method.
*/
public TreeNode deserialize(String data) {
// write your code here
if (data.equals("{}")) {
return null;
}
String[] str = data.substring(1, data.length() - 1).split(",");
boolean isLeftChild = true;
List<TreeNode> list = new ArrayList<>();
list.add(new TreeNode(Integer.parseInt(str[0])));
int index = 0;
for (int i = 1; i < str.length; i++) {
if (!str[i].equals("#")) {
TreeNode node = new TreeNode(Integer.parseInt(str[i]));
if (isLeftChild) {
list.get(index).left = node;
} else {
list.get(index).right = node;
}
list.add(node);
}
if (!isLeftChild) {
index++;
}
isLeftChild = !isLeftChild;
}
return list.get(0);
}
}

数据流中位数 -- 返回A[(n - 1) / 2]的中位数

思路:大根堆和小根堆,小根堆size为0或者比小根堆堆顶元素小的数加入小根堆,否则加入大根堆。加入之后,进行一次调整使得小根堆的size总是等于大根堆的size或者小根堆的size等于大根堆的size + 1

 public class Solution {
/*
* @param nums: A list of integers
* @return: the median of numbers
*/
public int[] medianII(int[] nums) {
// write your code here
if (nums == null || nums.length == 0) {
return new int[0];
}
int[] result = new int[nums.length];
PriorityQueue<Integer> maxHeap
= new PriorityQueue<Integer>(new Comparator<Integer>() {
public int compare(Integer a, Integer b) {
return b - a;
}
});
PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>();
for (int i = 0; i < nums.length; i++) {
if (maxHeap.size() == 0 || nums[i] < maxHeap.peek()) {
maxHeap.offer(nums[i]);
} else {
minHeap.offer(nums[i]);
}
if (maxHeap.size() - minHeap.size() > 1) {
minHeap.offer(maxHeap.poll());
} else if (minHeap.size() > maxHeap.size()) {
maxHeap.offer(minHeap.poll());
}
result[i] = maxHeap.peek();
}
return result;
}
}

滑动窗口中位数

Given an array of n integer, and a moving window(size k), move the window at each iteration from the start of the array, find the median of the element inside the window at each moving. (If there are even numbers in the array, return the N/2-th number after sorting the element in the window. )

Have you met this question in a real interview? Yes
Example
For array [1,2,7,8,5], moving window size k = 3. return [2,7,7] At first the window is at the start of the array like this [ | 1,2,7 | ,8,5] , return the median 2; then the window move one step forward. [1, | 2,7,8 | ,5], return the median 7; then the window move one step forward again. [1,2, | 7,8,5 | ], return the median 7;

题目

思路:跟数据流中位数思路一样,只是只有当当前位置 >= k - 1时才开始每次都将中位数加入到结果集中,并且每次都要删除滑动窗口最前面的一个数,删除该数之后要重新调整大根堆和小根堆的size关系。

 public class Solution {
/*
* @param nums: A list of integers
* @param k: An integer
* @return: The median of the element inside the window at each moving
*/
public List<Integer> medianSlidingWindow(int[] nums, int k) {
// write your code here
List<Integer> result = new ArrayList<>();
if (nums == null || nums.length == 0 || k < 1 || k > nums.length) {
return result;
}
PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new Comparator<Integer>() {
public int compare(Integer a, Integer b) {
return b - a;
}
});
PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>();
for (int i = 0; i < nums.length; i++) {
if (maxHeap.size() == 0 || nums[i] < maxHeap.peek()) {
maxHeap.offer(nums[i]);
} else {
minHeap.offer(nums[i]);
}
if (maxHeap.size() > minHeap.size() + 1) {
minHeap.offer(maxHeap.poll());
} else if (minHeap.size() > maxHeap.size()) {
maxHeap.offer(minHeap.poll());
}
if (i >= k - 1) {
result.add(maxHeap.peek());
int num = nums[i - k + 1];
if (num <= maxHeap.peek()) {
maxHeap.remove(num);
} else {
minHeap.remove(num);
}
if (maxHeap.size() > minHeap.size() + 1) {
minHeap.offer(maxHeap.poll());
} else if (minHeap.size() > maxHeap.size()) {
maxHeap.offer(minHeap.poll());
}
}
}
return result;
}
}

滑动窗口最大值

思路:双端队列,存储可能的最大值。加入队列时,删除队列中比这个数小的元素;删除前面的元素时,如果前面的元素等于队列首元素则删除队列首元素。

时间复杂度是O(n),因为每个数只可能被操作最多两次,一次是加入队列的时候,一次是因为有别的更大数在后面,所以被扔掉,或者因为出了窗口而被扔掉

空间复杂度O(k),由于队列里只有窗口内的数,所以他们其实就是窗口内第一大,第二大,第三大...的数。

 public class Solution {
/*
* @param nums: A list of integers
* @param k: An integer
* @return: The maximum number inside the window at each moving
*/
public ArrayList<Integer> maxSlidingWindow(int[] nums, int k) {
// write your code here
ArrayList<Integer> result = new ArrayList<>();
if (nums == null || nums.length == 0 || k < 1 || k > nums.length) {
return result;
}
Deque<Integer> deque = new ArrayDeque<Integer>();
for (int i = 0; i < k - 1; i++) {
inQueue(deque, nums[i]);
}
for (int i = k - 1; i < nums.length; i++) {
inQueue(deque, nums[i]);
result.add(deque.peekFirst());
outQueue(deque, nums[i - k + 1]);
}
return result;
}
public void inQueue(Deque<Integer> deque, int num) {
while (!deque.isEmpty() && num > deque.peekLast()) {
deque.pollLast();
}
deque.offerLast(num);
}
public void outQueue(Deque<Integer> deque, int num) {
if (num == deque.peekFirst()) {
deque.pollFirst();
}
}
};

Ugly Number I -- 判断一个数是不是丑数(丑数是指素因子只有2,3,5,特别的,1也是丑数)

思路:不断除以素因子2,3,5,看最后的结果是否等于1。注意,0要事先特殊处理,否则会死循环。

 public class Solution {
/*
* @param num: An integer
* @return: true if num is an ugly number or false
*/
public boolean isUgly(int num) {
// write your code here
if (num == 0) {
return false;
}
while (num % 2 == 0) {
num = num / 2;
}
while (num % 3 == 0) {
num = num / 3;
}
while (num % 5 == 0) {
num = num / 5;
}
return num == 1;
}
}

Ugly Number II -- 求第n个丑数

思路I:小根堆 + Set,循环n次就可以得到第n个丑数。时间复杂度O(nlgn),空间复杂度O(n)。注意当前数 * 2 、3、5可能会超出int范围,所以小根堆和Set中存储的类型为Long而不是Integer

 public class Solution {
/*
* @param n: An integer
* @return: the nth prime number as description.
*/
public int nthUglyNumber(int n) {
// write your code here
if (n < 1) {
return 0;
}
PriorityQueue<Long> pq = new PriorityQueue<Long>();
Set<Long> set = new HashSet<>();
pq.offer((long) 1);
set.add((long) 1);
long num = 1;
for (int i = 0; i < n; i++) {
num = pq.poll();
if (!set.contains(num * 2)) {
pq.offer(num * 2);
set.add(num * 2);
}
if (!set.contains(num * 3)) {
pq.offer(num * 3);
set.add(num * 3);
}
if (!set.contains(num * 5)) {
pq.offer(num * 5);
set.add(num * 5);
}
}
return (int) num;
}
}

思路II:p2,p3,p5分别记录乘以2,3,5之后恰好大于当前丑数的位置,然后每次生成下一个丑数时取p2 * 2,p3 * 3和p5 * 5的最小值。

 public class Solution {
/*
* @param n: An integer
* @return: the nth prime number as description.
*/
public int nthUglyNumber(int n) {
// write your code here
if (n < 1) {
return 0;
}
int p2 = 0, p3 = 0, p5 = 0;
List<Integer> ugly = new ArrayList<>();
ugly.add(1);
for (int i = 1; i < n; i++) {
while (ugly.get(p2) * 2 <= ugly.get(i - 1)) {
p2++;
}
while (ugly.get(p3) * 3 <= ugly.get(i - 1)) {
p3++;
}
while (ugly.get(p5) * 5 <= ugly.get(i - 1)) {
p5++;
}
ugly.add(Math.min(Math.min(ugly.get(p2) * 2, ugly.get(p3) * 3),
ugly.get(p5) * 5));
}
return ugly.get(ugly.size() - 1);
}
}

最近公共祖先 -- Lowest Common Ancestor(LCA)

思路:分治。在root为根的二叉树中找A,B的LCA: 如果找到了就返回这个LCA;如果只碰到A,就返回A;如果只碰到B,就返回B;如果都没有,就返回null

 /**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/ public class Solution {
/*
* @param root: The root of the binary search tree.
* @param A: A TreeNode in a Binary.
* @param B: A TreeNode in a Binary.
* @return: Return the least common ancestor(LCA) of the two nodes.
*/
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode A, TreeNode B) {
// write your code here
if (root == null || A == root || B == root) {
return root;
}
TreeNode left = lowestCommonAncestor(root.left, A, B);
TreeNode right = lowestCommonAncestor(root.right, A, B);
if (left != null && right != null) {
return root;
}
if (left != null) {
return left;
}
if (right != null) {
return right;
}
return null;
}
}

Digit Counts

Count the number of k's between 0 and n. k can be 0 - 9.

题目

思路I:暴力法,从0 - n对每一个数字计算k出现的次数,然后求和,注意n == 0 && k == 0的特殊情况处理

 public class Solution {
/*
* @param : An integer
* @param : An integer
* @return: An integer denote the count of digit k in 1..n
*/
public int digitCounts(int k, int n) {
// write your code here
if (k < 0 || k > 9 || n < 0) {
return 0;
}
int num = 0;
for (int i = 0; i <= n; i++) {
num += count(i, k);
}
return num;
}
public int count(int n, int k) {
if (n == 0 && k == 0) {
return 1;
}
int count = 0;
while (n != 0) {
if (n % 10 == k) {
count++;
}
n = n / 10;
}
return count;
}
};

思路II:

当某一位的数字小于i时,那么该位出现i的次数为:更高位数字x当前位数

当某一位的数字等于i时,那么该位出现i的次数为:更高位数字x当前位数+低位数字+1

当某一位的数字大于i时,那么该位出现i的次数为:(更高位数字+1)x当前位数

比如计算0到11间出现的0的次数,会把1,2,3,4…视为01,02,03,04… 从而得出错误的结果。所以0是需要单独考虑的。

注意n == 0 && k == 0的特殊情况处理

 public class Solution {
/*
* @param : An integer
* @param : An integer
* @return: An integer denote the count of digit k in 1..n
*/
public int digitCounts(int k, int n) {
// write your code here
if (k < 0 || k > 9 || n < 0) {
return 0;
}
if (n == 0 && k == 0) {
return 1;
}
int factor = 1;
int low = 0, cur = 0, high = 0;
int count = 0;
while (n / factor != 0) {
low = n % factor;
cur = (n / factor) % 10;
high = n / (factor * 10);
if (cur < k) {
count += high * factor;
} else if (cur == k) {
count += high * factor + low + 1;
} else {
count += (high + 1) * factor;
}
if (k == 0 && factor != 1) {
count -= factor;
}
factor *= 10;
}
return count;
}
};

String to Integer II -- 将字符串转化成整数

Implement function atoi to convert a string to an integer.

If no valid conversion could be performed, a zero value is returned.

If the correct value is out of the range of representable values, INT_MAX (2147483647) or INT_MIN (-2147483648) is returned.

Have you met this question in a real interview? Yes
Example
"10" => 10
"-1" => -1
"123123123123123" => 2147483647
"1.0" => 1

题目

思路:用一个flag标记是整数还是负数,需要处理很多特殊情况:空格(先trim)、判断第一个字符是不是'-'号,移动相应的start指针、累加过程中累加和sum可能会超出int范围,所以累加和sum定义为long、累加和 > Integer.MAX_VALUE + 1和累加和 == Integer.MIN_VALUE + 1这两种情况的特殊处理,因为int的范围是-2147483648 - 2147483647,所以要分> Integer.MAX_VALUE + 1和== Integer.MIN_VALUE + 1这两种情况。

 public class Solution {
/*
* @param str: A string
* @return: An integer
*/
public int atoi(String str) {
// write your code here
if (str == null || str.length() == 0) {
return 0;
}
str = str.trim();
if (str.length() == 0) {
return 0;
}
boolean flag = true;
int start = 0;
if (str.charAt(0) == '-') {
flag = false;
start++;
} else if (str.charAt(0) == '+') {
start++;
}
long sum = 0;
while (start < str.length()
&& str.charAt(start) >= '0' && str.charAt(start) <= '9') {
sum = sum * 10 + (str.charAt(start) - '0');
if (sum > (long) Integer.MAX_VALUE + 1) {
if (flag) {
return Integer.MAX_VALUE;
} else {
return Integer.MIN_VALUE;
}
}
if (sum == (long) Integer.MAX_VALUE + 1) {
if (flag) {
return Integer.MAX_VALUE;
}
}
start++;
}
if (!flag) {
sum = -sum;
}
return (int) sum;
}
}

Regular Expression Matching -- 正则表达式匹配

Implement regular expression matching with support for '.' and '*'.

'.' Matches any single character.
'*' Matches zero or more of the preceding element. The matching should cover the entire input string (not partial). The function prototype should be:
bool isMatch(const char *s, const char *p) Some examples:
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "a*") → true
isMatch("aa", ".*") → true
isMatch("ab", ".*") → true
isMatch("aab", "c*a*b") → true

题目

思路:

f[i][j]: if s[0..i-1] matches p[0..j-1]

if p[j - 1] != '*'

  f[i][j] = f[i - 1][j - 1] && s[i - 1] == p[j - 1]

if p[j - 1] == '*', denote p[j - 2] with x

  f[i][j] is true iff any of the following is true

    1) "x*" repeats 0 time and matches empty: f[i][j - 2]

    2) "x*" repeats >= 1 times and matches "x*x": s[i - 1] == x && f[i - 1][j]

 class Solution {
public boolean isMatch(String s, String p) {
if (s == null && p == null) {
return true;
}
if (s == null || p == null) {
return false;
}
int n = s.length();
int m = p.length();
if (m == 0) {
if (n == 0) {
return true;
} else {
return false;
}
}
boolean[][] dp = new boolean[n + 1][m + 1];
dp[0][0] = true;
for (int i = 1; i <= n; i++) {
dp[i][0] = false;
}
for (int j = 1; j <= m; j++) {
dp[0][j] = j > 1 && p.charAt(j - 1) == '*' && dp[0][j - 2];
}
for (int i = 1; i <= n; i++) {
dp[i][1] = i == 1 && (s.charAt(0) == p.charAt(0) || p.charAt(0) == '.');
}
for (int i = 1; i <= n; i++) {
for (int j = 2; j <= m; j++) {
if (p.charAt(j - 1) != '*') {
dp[i][j] = dp[i - 1][j - 1] && (s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '.');
} else {
dp[i][j] = dp[i][j - 2] ||
((s.charAt(i - 1) == p.charAt(j - 2) || p.charAt(j - 2) == '.') && dp[i - 1][j]);
}
}
}
return dp[n][m];
}
}

Symmetric Tree

判断一个二叉树是不是对称的二叉树

 /**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) {
return true;
}
return compare(root, root);
}
public boolean compare(TreeNode node1, TreeNode node2) {
if (node1 == null && node2 == null) {
return true;
}
if (node1 == null || node2 == null) {
return false;
}
if (node1.val != node2.val) {
return false;
}
return compare(node1.left, node2.right) && compare(node1.right, node2.left);
}
}

剑指offer ------ 刷题总结的更多相关文章

  1. 剑指offer刷题(Tree)

    开篇 二刷剑指offer了,本来用Tyora记的笔记,发现字数到四万了就变得好卡o(╥﹏╥)o,刚好开始写博客,就转过来吧,记下来子自己看.不废话,开刷... JZ26. 树的子结构 输入两棵二叉树A ...

  2. 剑指offer刷题

    1.面试题43. 1-n整数中1出现的次数 输入一个整数 n ,求1-n这n个整数的十进制表示中1出现的次数. 例如,输入12,1-12这些整数中包含1 的数字有1.10.11和12,1一共出现了5次 ...

  3. 牛客网剑指offer刷题总结

    二维数组中的查找: 题目描述:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. 两 ...

  4. LeetCode剑指Offer刷题总结(一)

    LeetCode过程中值得反思的细节 以下题号均指LeetCode剑指offer题库中的题号 本文章将每周定期更新,当内容达到10题左右时将会开下一节. 二维数组越界问题04 public stati ...

  5. 剑指offer刷题记录

    目录 二维数组中的查找 替换空格 从尾到头打印链表 反转链表 重建二叉树 用两个栈实现队列 旋转数组的最小数字 斐波拉切数列 跳台阶 变态跳台阶 矩形覆盖 二进制中1的个数 数值的整次方 链表中倒数第 ...

  6. 剑指offer刷题笔记

    删除链表中重复的结点:较难 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针. 例如,链表1->2->3->3->4->4- ...

  7. 剑指offer刷题总结

    ★ 二维数组的查找 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否 ...

  8. 剑指offer刷题(算法类_2)

    排序 035-数组中的逆序对(归并排序) 题目描述 题解 代码 复杂度 029-最小的K个数(堆排序) 题目描述 题解 代码 复杂度 029-最小的K个数(快速排序) 题目描述 题解 代码 复杂度 位 ...

  9. 剑指offer刷题(算法类_1)

    斐波那契数列 007-斐波拉契数列 题目描述 题解 代码 复杂度 008-跳台阶 题目描述 题解 代码 复杂度 009-变态跳台阶 题目描述 题解 代码 复杂度 010-矩形覆盖 题目描述 题解 代码 ...

随机推荐

  1. VS2013全攻略(安装,技巧,快捷键,插件)!

    工欲善其事,必先利其器.VS2013全攻略(安装,技巧,快捷键,插件)! 之前一篇<c++的性能, c#的产能?!鱼和熊掌可以兼得,.NET NATIVE初窥>承蒙大家喜爱和编辑推荐,在此 ...

  2. 一个ERP系统的磕磕碰碰

    历时5个月的开发,终于可以交付使用了.在这期间系统从需求分析.系统架构设计.开发.测试.运行.迭代.交付,整个忐忑波荡的一路走来,终于有时间静下心来写一篇总结跟各位博友一起探讨.学习了.如果对大家有帮 ...

  3. Shards

    跟我一起云计算(5)——Shards   什么是sharding Sharding的基本思想就要把一个数据库切分成多个部分放到不同的数据库 (server)上,从而缓解单一数据库的性能问题.不太严格的 ...

  4. jquery中each用法

    通用遍历方法,可用于遍历对象和数组.$().each(),回调函数拥有两个参数: 第一个为对象的成员或数组的索引,第二个为对应变量或内容.如需退出each循环可使回调函数返回false 现有如下两个s ...

  5. mmDeferred

    前端异步解决方案——mmDeferred Deferred是前端解决异步操作的一种编程范式,后来出现的Promise规范更是让其普适性大大提高.不过Promise规范也存在分岐.现在最流行的是Prom ...

  6. Go语言Web框架gwk介绍4

    Go语言Web框架gwk介绍 (四)   事件 gwk支持事件系统,但并没有硬编码有哪些事件,而是采用了比较松散的定义方式. 订阅事件有两种方式: 调用On函数或者OnFunc函数 func On(m ...

  7. 分享Mvc3+NInject+EF+LigerUI权限系统

    分享Mvc3+NInject+EF+LigerUI权限系统   前段时间时不时看到有园友的分享权限系统,于是本人突发奇想,也想写一个玩玩,就利用晚上时间,陆陆续续花了一周多样子,写了如今这个权限系统, ...

  8. HDU 4062 Partition

    Partition Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Sub ...

  9. TOGAF架构内容框架之架构制品(下)

    TOGAF架构内容框架之架构制品(下) 4.2.31 数据生命周期图(Data Lifecycle Diagram) 数据生命周期图是在业务流程的约束之下对业务数据在其整个生命周期(从概念阶段到最终退 ...

  10. Supervisor 管理后台守护进程

    Supervisor 管理后台守护进程 参考原文如下: http://codinn.com/people/brant/notes/110948/ 做了一些注释 +++++++++++引用开始+++++ ...