CS-Notes/notes/剑指 Offer 题解 - 30~39.md
2019-06-09 19:32:01 +08:00

476 lines
15 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- GFM-TOC -->
* [30. 包含 min 函数的栈](#30-包含-min-函数的栈)
* [31. 栈的压入弹出序列](#31-栈的压入弹出序列)
* [32.1 从上往下打印二叉树](#321-从上往下打印二叉树)
* [32.2 把二叉树打印成多行](#322-把二叉树打印成多行)
* [32.3 按之字形顺序打印二叉树](#323-按之字形顺序打印二叉树)
* [33. 二叉搜索树的后序遍历序列](#33-二叉搜索树的后序遍历序列)
* [34. 二叉树中和为某一值的路径](#34-二叉树中和为某一值的路径)
* [35. 复杂链表的复制](#35-复杂链表的复制)
* [36. 二叉搜索树与双向链表](#36-二叉搜索树与双向链表)
* [37. 序列化二叉树](#37-序列化二叉树)
* [38. 字符串的排列](#38-字符串的排列)
* [39. 数组中出现次数超过一半的数字](#39-数组中出现次数超过一半的数字)
<!-- GFM-TOC -->
# 30. 包含 min 函数的栈
[NowCoder](https://www.nowcoder.com/practice/4c776177d2c04c2494f2555c9fcc1e49?tpId=13&tqId=11173&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
## 题目描述
定义栈的数据结构请在该类型中实现一个能够得到栈最小元素的 min 函数
## 解题思路
```java
private Stack<Integer> dataStack = new Stack<>();
private Stack<Integer> minStack = new Stack<>();
public void push(int node) {
dataStack.push(node);
minStack.push(minStack.isEmpty() ? node : Math.min(minStack.peek(), node));
}
public void pop() {
dataStack.pop();
minStack.pop();
}
public int top() {
return dataStack.peek();
}
public int min() {
return minStack.peek();
}
```
# 31. 栈的压入弹出序列
[NowCoder](https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&tqId=11174&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
## 题目描述
输入两个整数序列第一个序列表示栈的压入顺序请判断第二个序列是否为该栈的弹出顺序假设压入栈的所有数字均不相等
例如序列 1,2,3,4,5 是某栈的压入顺序序列 4,5,3,2,1 是该压栈序列对应的一个弹出序列 4,3,5,1,2 就不可能是该压栈序列的弹出序列
## 解题思路
使用一个栈来模拟压入弹出操作
```java
public boolean IsPopOrder(int[] pushSequence, int[] popSequence) {
int n = pushSequence.length;
Stack<Integer> stack = new Stack<>();
for (int pushIndex = 0, popIndex = 0; pushIndex < n; pushIndex++) {
stack.push(pushSequence[pushIndex]);
while (popIndex < n && !stack.isEmpty()
&& stack.peek() == popSequence[popIndex]) {
stack.pop();
popIndex++;
}
}
return stack.isEmpty();
}
```
# 32.1 从上往下打印二叉树
[NowCoder](https://www.nowcoder.com/practice/7fe2212963db4790b57431d9ed259701?tpId=13&tqId=11175&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
## 题目描述
从上往下打印出二叉树的每个节点同层节点从左至右打印
例如以下二叉树层次遍历的结果为1,2,3,4,5,6,7
<div align="center"> <img src="pics/d5e838cf-d8a2-49af-90df-1b2a714ee676.jpg" width="250"/> </div><br>
## 解题思路
使用队列来进行层次遍历
不需要使用两个队列分别存储当前层的节点和下一层的节点因为在开始遍历一层的节点时当前队列中的节点数就是当前层的节点数只要控制遍历这么多节点数就能保证这次遍历的都是当前层的节点
```java
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
ArrayList<Integer> ret = new ArrayList<>();
queue.add(root);
while (!queue.isEmpty()) {
int cnt = queue.size();
while (cnt-- > 0) {
TreeNode t = queue.poll();
if (t == null)
continue;
ret.add(t.val);
queue.add(t.left);
queue.add(t.right);
}
}
return ret;
}
```
# 32.2 把二叉树打印成多行
[NowCoder](https://www.nowcoder.com/practice/445c44d982d04483b04a54f298796288?tpId=13&tqId=11213&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
## 题目描述
和上题几乎一样
## 解题思路
```java
ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.add(pRoot);
while (!queue.isEmpty()) {
ArrayList<Integer> list = new ArrayList<>();
int cnt = queue.size();
while (cnt-- > 0) {
TreeNode node = queue.poll();
if (node == null)
continue;
list.add(node.val);
queue.add(node.left);
queue.add(node.right);
}
if (list.size() != 0)
ret.add(list);
}
return ret;
}
```
# 32.3 按之字形顺序打印二叉树
[NowCoder](https://www.nowcoder.com/practice/91b69814117f4e8097390d107d2efbe0?tpId=13&tqId=11212&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
## 题目描述
请实现一个函数按照之字形打印二叉树即第一行按照从左到右的顺序打印第二层按照从右至左的顺序打印第三行按照从左到右的顺序打印其他行以此类推
## 解题思路
```java
public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.add(pRoot);
boolean reverse = false;
while (!queue.isEmpty()) {
ArrayList<Integer> list = new ArrayList<>();
int cnt = queue.size();
while (cnt-- > 0) {
TreeNode node = queue.poll();
if (node == null)
continue;
list.add(node.val);
queue.add(node.left);
queue.add(node.right);
}
if (reverse)
Collections.reverse(list);
reverse = !reverse;
if (list.size() != 0)
ret.add(list);
}
return ret;
}
```
# 33. 二叉搜索树的后序遍历序列
[NowCoder](https://www.nowcoder.com/practice/a861533d45854474ac791d90e447bafd?tpId=13&tqId=11176&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
## 题目描述
输入一个整数数组判断该数组是不是某二叉搜索树的后序遍历的结果假设输入的数组的任意两个数字都互不相同
例如下图是后序遍历序列 1,3,2 所对应的二叉搜索树
<div align="center"> <img src="pics/13454fa1-23a8-4578-9663-2b13a6af564a.jpg" width="150"/> </div><br>
## 解题思路
```java
public boolean VerifySquenceOfBST(int[] sequence) {
if (sequence == null || sequence.length == 0)
return false;
return verify(sequence, 0, sequence.length - 1);
}
private boolean verify(int[] sequence, int first, int last) {
if (last - first <= 1)
return true;
int rootVal = sequence[last];
int cutIndex = first;
while (cutIndex < last && sequence[cutIndex] <= rootVal)
cutIndex++;
for (int i = cutIndex; i < last; i++)
if (sequence[i] < rootVal)
return false;
return verify(sequence, first, cutIndex - 1) && verify(sequence, cutIndex, last - 1);
}
```
# 34. 二叉树中和为某一值的路径
[NowCoder](https://www.nowcoder.com/practice/b736e784e3e34731af99065031301bca?tpId=13&tqId=11177&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
## 题目描述
输入一颗二叉树和一个整数打印出二叉树中结点值的和为输入整数的所有路径路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径
下图的二叉树有两条和为 22 的路径10, 5, 7 10, 12
<div align="center"> <img src="pics/ed77b0e6-38d9-4a34-844f-724f3ffa2c12.jpg" width="200"/> </div><br>
## 解题思路
```java
private ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
backtracking(root, target, new ArrayList<>());
return ret;
}
private void backtracking(TreeNode node, int target, ArrayList<Integer> path) {
if (node == null)
return;
path.add(node.val);
target -= node.val;
if (target == 0 && node.left == null && node.right == null) {
ret.add(new ArrayList<>(path));
} else {
backtracking(node.left, target, path);
backtracking(node.right, target, path);
}
path.remove(path.size() - 1);
}
```
# 35. 复杂链表的复制
[NowCoder](https://www.nowcoder.com/practice/f836b2c43afc4b35ad6adc41ec941dba?tpId=13&tqId=11178&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
## 题目描述
输入一个复杂链表每个节点中有节点值以及两个指针一个指向下一个节点另一个特殊指针指向任意一个节点返回结果为复制后复杂链表的 head
```java
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
```
<div align="center"> <img src="pics/66a01953-5303-43b1-8646-0c77b825e980.png" width="300"/> </div><br>
## 解题思路
第一步在每个节点的后面插入复制的节点
<div align="center"> <img src="pics/dfd5d3f8-673c-486b-8ecf-d2082107b67b.png" width="600"/> </div><br>
第二步对复制节点的 random 链接进行赋值
<div align="center"> <img src="pics/cafbfeb8-7dfe-4c0a-a3c9-750eeb824068.png" width="600"/> </div><br>
第三步拆分
<div align="center"> <img src="pics/e151b5df-5390-4365-b66e-b130cd253c12.png" width="600"/> </div><br>
```java
public RandomListNode Clone(RandomListNode pHead) {
if (pHead == null)
return null;
// 插入新节点
RandomListNode cur = pHead;
while (cur != null) {
RandomListNode clone = new RandomListNode(cur.label);
clone.next = cur.next;
cur.next = clone;
cur = clone.next;
}
// 建立 random 链接
cur = pHead;
while (cur != null) {
RandomListNode clone = cur.next;
if (cur.random != null)
clone.random = cur.random.next;
cur = clone.next;
}
// 拆分
cur = pHead;
RandomListNode pCloneHead = pHead.next;
while (cur.next != null) {
RandomListNode next = cur.next;
cur.next = next.next;
cur = next;
}
return pCloneHead;
}
```
# 36. 二叉搜索树与双向链表
[NowCoder](https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId=13&tqId=11179&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
## 题目描述
输入一棵二叉搜索树将该二叉搜索树转换成一个排序的双向链表要求不能创建任何新的结点只能调整树中结点指针的指向
<div align="center"> <img src="pics/05a08f2e-9914-4a77-92ef-aebeaecf4f66.jpg" width="400"/> </div><br>
## 解题思路
```java
private TreeNode pre = null;
private TreeNode head = null;
public TreeNode Convert(TreeNode root) {
inOrder(root);
return head;
}
private void inOrder(TreeNode node) {
if (node == null)
return;
inOrder(node.left);
node.left = pre;
if (pre != null)
pre.right = node;
pre = node;
if (head == null)
head = node;
inOrder(node.right);
}
```
# 37. 序列化二叉树
[NowCoder](https://www.nowcoder.com/practice/cf7e25aa97c04cc1a68c8f040e71fb84?tpId=13&tqId=11214&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
## 题目描述
请实现两个函数分别用来序列化和反序列化二叉树
## 解题思路
```java
private String deserializeStr;
public String Serialize(TreeNode root) {
if (root == null)
return "#";
return root.val + " " + Serialize(root.left) + " " + Serialize(root.right);
}
public TreeNode Deserialize(String str) {
deserializeStr = str;
return Deserialize();
}
private TreeNode Deserialize() {
if (deserializeStr.length() == 0)
return null;
int index = deserializeStr.indexOf(" ");
String node = index == -1 ? deserializeStr : deserializeStr.substring(0, index);
deserializeStr = index == -1 ? "" : deserializeStr.substring(index + 1);
if (node.equals("#"))
return null;
int val = Integer.valueOf(node);
TreeNode t = new TreeNode(val);
t.left = Deserialize();
t.right = Deserialize();
return t;
}
```
# 38. 字符串的排列
[NowCoder](https://www.nowcoder.com/practice/fe6b651b66ae47d7acce78ffdd9a96c7?tpId=13&tqId=11180&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
## 题目描述
输入一个字符串按字典序打印出该字符串中字符的所有排列例如输入字符串 abc则打印出由字符 a, b, c 所能排列出来的所有字符串 abc, acb, bac, bca, cab cba
## 解题思路
```java
private ArrayList<String> ret = new ArrayList<>();
public ArrayList<String> Permutation(String str) {
if (str.length() == 0)
return ret;
char[] chars = str.toCharArray();
Arrays.sort(chars);
backtracking(chars, new boolean[chars.length], new StringBuilder());
return ret;
}
private void backtracking(char[] chars, boolean[] hasUsed, StringBuilder s) {
if (s.length() == chars.length) {
ret.add(s.toString());
return;
}
for (int i = 0; i < chars.length; i++) {
if (hasUsed[i])
continue;
if (i != 0 && chars[i] == chars[i - 1] && !hasUsed[i - 1]) /* 保证不重复 */
continue;
hasUsed[i] = true;
s.append(chars[i]);
backtracking(chars, hasUsed, s);
s.deleteCharAt(s.length() - 1);
hasUsed[i] = false;
}
}
```
# 39. 数组中出现次数超过一半的数字
[NowCoder](https://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163?tpId=13&tqId=11181&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
## 解题思路
多数投票问题可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题使得时间复杂度为 O(N)
使用 cnt 来统计一个元素出现的次数当遍历到的元素和统计元素相等时 cnt++否则令 cnt--如果前面查找了 i 个元素 cnt == 0说明前 i 个元素没有 majority或者有 majority但是出现的次数少于 i / 2 因为如果多于 i / 2 的话 cnt 就一定不会为 0 此时剩下的 n - i 个元素中majority 的数目依然多于 (n - i) / 2因此继续查找就能找出 majority
```java
public int MoreThanHalfNum_Solution(int[] nums) {
int majority = nums[0];
for (int i = 1, cnt = 1; i < nums.length; i++) {
cnt = nums[i] == majority ? cnt + 1 : cnt - 1;
if (cnt == 0) {
majority = nums[i];
cnt = 1;
}
}
int cnt = 0;
for (int val : nums)
if (val == majority)
cnt++;
return cnt > nums.length / 2 ? majority : 0;
}
```
<img width="580px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/other/公众号海报1.png"></img>