剑指 Offer 题解 - 3~9.md 12.2 KB
Newer Older
C
CyC2018 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
<!-- GFM-TOC -->
* [3. 数组中重复的数字](#3-数组中重复的数字)
    * [题目描述](#题目描述)
    * [解题思路](#解题思路)
* [4. 二维数组中的查找](#4-二维数组中的查找)
    * [题目描述](#题目描述)
    * [解题思路](#解题思路)
* [5. 替换空格](#5-替换空格)
    * [题目描述](#题目描述)
    * [解题思路](#解题思路)
* [6. 从尾到头打印链表](#6-从尾到头打印链表)
    * [题目描述](#题目描述)
    * [解题思路](#解题思路)
        * [使用递归](#使用递归)
        * [使用头插法](#使用头插法)
        * [使用栈](#使用栈)
* [7. 重建二叉树](#7-重建二叉树)
    * [题目描述](#题目描述)
    * [解题思路](#解题思路)
* [8. 二叉树的下一个结点](#8-二叉树的下一个结点)
    * [题目描述](#题目描述)
    * [解题思路](#解题思路)
* [9. 用两个栈实现队列](#9-用两个栈实现队列)
    * [题目描述](#题目描述)
    * [解题思路](#解题思路)
<!-- GFM-TOC -->


# 3. 数组中重复的数字
C
CyC2018 已提交
30 31 32

[NowCoder](https://www.nowcoder.com/practice/623a5ac0ea5b4e5f95552655361ae0a8?tpId=13&tqId=11203&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)

C
CyC2018 已提交
33
## 题目描述
C
CyC2018 已提交
34

C
CyC2018 已提交
35
在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的,也不知道每个数字重复几次。请找出数组中任意一个重复的数字。
C
CyC2018 已提交
36 37 38

```html
Input:
C
CyC2018 已提交
39
{2, 3, 1, 0, 2, 5}
C
CyC2018 已提交
40 41 42 43 44

Output:
2
```

C
CyC2018 已提交
45
## 解题思路
C
CyC2018 已提交
46

C
CyC2018 已提交
47
要求是时间复杂度 O(N),空间复杂度 O(1)。因此不能使用排序的方法,也不能使用额外的标记数组。
C
CyC2018 已提交
48

C
CyC2018 已提交
49
对于这种数组元素在 [0, n-1] 范围内的问题,可以将值为 i 的元素调整到第 i 个位置上进行求解。
C
CyC2018 已提交
50

C
CyC2018 已提交
51
以 (2, 3, 1, 0, 2, 5) 为例,遍历到位置 4 时,该位置上的数为 2,但是第 2 个位置上已经有一个 2 的值了,因此可以知道 2 重复:
C
CyC2018 已提交
52

C
CyC2018 已提交
53
<div align="center"> <img src="pics/_u6570_u7EC4_u4E2D_u91CD_u590D_1548260392361.gif" width="250px"> </div><br>
C
CyC2018 已提交
54 55

```java
C
CyC2018 已提交
56 57 58 59 60 61 62 63 64 65 66 67 68
public boolean duplicate(int[] nums, int length, int[] duplication) {
    if (nums == null || length <= 0)
        return false;
    for (int i = 0; i < length; i++) {
        while (nums[i] != i) {
            if (nums[i] == nums[nums[i]]) {
                duplication[0] = nums[i];
                return true;
            }
            swap(nums, i, nums[i]);
        }
    }
    return false;
C
CyC2018 已提交
69 70
}

C
CyC2018 已提交
71 72 73 74
private void swap(int[] nums, int i, int j) {
    int t = nums[i];
    nums[i] = nums[j];
    nums[j] = t;
C
CyC2018 已提交
75 76 77
}
```

C
CyC2018 已提交
78
# 4. 二维数组中的查找
C
CyC2018 已提交
79 80 81

[NowCoder](https://www.nowcoder.com/practice/abc3fe2ce8e146608e868a70efebf62e?tpId=13&tqId=11154&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)

C
CyC2018 已提交
82
## 题目描述
C
CyC2018 已提交
83 84 85 86

给定一个二维数组,其每一行从左到右递增排序,从上到下也是递增排序。给定一个数,判断这个数是否在该二维数组中。

```html
C
CyC2018 已提交
87
Consider the following matrix:
C
CyC2018 已提交
88
[
C
CyC2018 已提交
89 90 91 92 93
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
C
CyC2018 已提交
94 95
]

C
CyC2018 已提交
96 97
Given target = 5, return true.
Given target = 20, return false.
C
CyC2018 已提交
98 99
```

C
CyC2018 已提交
100
## 解题思路
C
CyC2018 已提交
101

C
CyC2018 已提交
102
要求时间复杂度 O(M + N),空间复杂度 O(1)。
C
CyC2018 已提交
103

C
CyC2018 已提交
104
该二维数组中的一个数,它左边的数都比它小,下边的数都比它大。因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来缩小查找区间,当前元素的查找区间为左下角的所有元素。
C
CyC2018 已提交
105

C
CyC2018 已提交
106
<div align="center"> <img src="pics/_u4E8C_u7EF4_u6570_u7EC4_u4E2D_.gif"/> </div><br>
C
CyC2018 已提交
107 108

```java
C
CyC2018 已提交
109 110 111 112 113 114 115 116 117 118 119 120 121 122
public boolean Find(int target, int[][] matrix) {
    if (matrix == null || matrix.length == 0 || matrix[0].length == 0)
        return false;
    int rows = matrix.length, cols = matrix[0].length;
    int r = 0, c = cols - 1; // 从右上角开始
    while (r <= rows - 1 && c >= 0) {
        if (target == matrix[r][c])
            return true;
        else if (target > matrix[r][c])
            r++;
        else
            c--;
    }
    return false;
C
CyC2018 已提交
123 124 125
}
```

C
CyC2018 已提交
126
# 5. 替换空格
C
CyC2018 已提交
127 128 129

[NowCoder](https://www.nowcoder.com/practice/4060ac7e3e404ad1a894ef3e17650423?tpId=13&tqId=11155&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)

C
CyC2018 已提交
130
## 题目描述
C
CyC2018 已提交
131 132


C
CyC2018 已提交
133
将一个字符串中的空格替换成 "%20"。
C
CyC2018 已提交
134 135 136

```text
Input:
C
CyC2018 已提交
137
"A B"
C
CyC2018 已提交
138 139 140 141 142

Output:
"A%20B"
```

C
CyC2018 已提交
143
## 解题思路
C
CyC2018 已提交
144 145 146

在字符串尾部填充任意字符,使得字符串的长度等于替换之后的长度。因为一个空格要替换成三个字符(%20),因此当遍历到一个空格时,需要在尾部填充两个任意字符。

C
CyC2018 已提交
147
令 P1 指向字符串原来的末尾位置,P2 指向字符串现在的末尾位置。P1 和 P2 从后向前遍历,当 P1 遍历到一个空格时,就需要令 P2 指向的位置依次填充 02%(注意是逆序的),否则就填充上 P1 指向字符的值。
C
CyC2018 已提交
148

C
CyC2018 已提交
149
从后向前遍是为了在改变 P2 所指向的内容时,不会影响到 P1 遍历原来字符串的内容。
C
CyC2018 已提交
150

C
CyC2018 已提交
151
<div align="center"> <img src="pics/_u66FF_u6362_u7A7A_u683C.gif"/> </div><br>
C
CyC2018 已提交
152 153

```java
C
CyC2018 已提交
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
public String replaceSpace(StringBuffer str) {
    int P1 = str.length() - 1;
    for (int i = 0; i <= P1; i++)
        if (str.charAt(i) == ' ')
            str.append("  ");

    int P2 = str.length() - 1;
    while (P1 >= 0 && P2 > P1) {
        char c = str.charAt(P1--);
        if (c == ' ') {
            str.setCharAt(P2--, '0');
            str.setCharAt(P2--, '2');
            str.setCharAt(P2--, '%');
        } else {
            str.setCharAt(P2--, c);
        }
    }
    return str.toString();
C
CyC2018 已提交
172 173 174
}
```

C
CyC2018 已提交
175
# 6. 从尾到头打印链表
C
CyC2018 已提交
176 177 178

[NowCoder](https://www.nowcoder.com/practice/d0267f7f55b3412ba93bd35cfa8e8035?tpId=13&tqId=11156&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)

C
CyC2018 已提交
179
## 题目描述
C
CyC2018 已提交
180 181 182

从尾到头反过来打印出每个结点的值。

C
CyC2018 已提交
183
<div align="center"> <img src="pics/_u4ECE_u5C3E_u5230_u5934_u6253_1548293972480.gif" width="250px"> </div><br>
C
CyC2018 已提交
184

C
CyC2018 已提交
185
## 解题思路
C
CyC2018 已提交
186

C
CyC2018 已提交
187
### 使用递归
C
CyC2018 已提交
188

C
CyC2018 已提交
189
<div align="center"> <img src="pics/_u4ECE_u5C3E_u5230_u5934_u6253_1548296249372.gif" width="200px"> </div><br>
C
CyC2018 已提交
190 191 192


```java
C
CyC2018 已提交
193 194 195 196 197 198 199
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
    ArrayList<Integer> ret = new ArrayList<>();
    if (listNode != null) {
        ret.addAll(printListFromTailToHead(listNode.next));
        ret.add(listNode.val);
    }
    return ret;
C
CyC2018 已提交
200 201 202
}
```

C
CyC2018 已提交
203
### 使用头插法
C
CyC2018 已提交
204 205 206 207 208

利用链表头插法为逆序的特点。

头结点和第一个节点的区别:

C
CyC2018 已提交
209 210
- 头结点是在头插法中使用的一个额外节点,这个节点不存储值;
- 第一个节点就是链表的第一个真正存储值的节点。
C
CyC2018 已提交
211 212


C
CyC2018 已提交
213
<div align="center"> <img src="pics/_u4ECE_u5C3E_u5230_u5934_u6253_1548295232667.gif"/> </div><br>
C
CyC2018 已提交
214 215

```java
C
CyC2018 已提交
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
    // 头插法构建逆序链表
    ListNode head = new ListNode(-1);
    while (listNode != null) {
        ListNode memo = listNode.next;
        listNode.next = head.next;
        head.next = listNode;
        listNode = memo;
    }
    // 构建 ArrayList
    ArrayList<Integer> ret = new ArrayList<>();
    head = head.next;
    while (head != null) {
        ret.add(head.val);
        head = head.next;
    }
    return ret;
C
CyC2018 已提交
233 234 235
}
```

C
CyC2018 已提交
236
### 使用栈
C
CyC2018 已提交
237

C
CyC2018 已提交
238
<div align="center"> <img src="pics/_u4ECE_u5C3E_u5230_u5934_u6253_1548503461113.gif" width="500px"> </div><br>
C
CyC2018 已提交
239 240

```java
C
CyC2018 已提交
241 242 243 244 245 246 247 248 249 250
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
    Stack<Integer> stack = new Stack<>();
    while (listNode != null) {
        stack.add(listNode.val);
        listNode = listNode.next;
    }
    ArrayList<Integer> ret = new ArrayList<>();
    while (!stack.isEmpty())
        ret.add(stack.pop());
    return ret;
C
CyC2018 已提交
251 252 253
}
```

C
CyC2018 已提交
254
# 7. 重建二叉树
C
CyC2018 已提交
255 256 257

[NowCoder](https://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6?tpId=13&tqId=11157&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)

C
CyC2018 已提交
258
## 题目描述
C
CyC2018 已提交
259 260 261 262

根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

```html
C
CyC2018 已提交
263 264
preorder = [3,9,20,15,7]
inorder =  [9,3,15,20,7]
C
CyC2018 已提交
265 266
```

C
CyC2018 已提交
267
<div align="center"> <img src="pics/_u91CD_u5EFA_u4E8C_u53C9_u6811-1.gif" width="200"/> </div><br>
C
CyC2018 已提交
268

C
CyC2018 已提交
269
## 解题思路
C
CyC2018 已提交
270 271 272

前序遍历的第一个值为根节点的值,使用这个值将中序遍历结果分成两部分,左部分为树的左子树中序遍历结果,右部分为树的右子树中序遍历的结果。

C
CyC2018 已提交
273
<div align="center"> <img src="pics/_u91CD_u5EFA_u4E8C_u53C9_u6811-21548502782193.gif"/> </div><br>
C
CyC2018 已提交
274 275

```java
C
CyC2018 已提交
276 277
// 缓存中序遍历数组每个值对应的索引
private Map<Integer, Integer> indexForInOrders = new HashMap<>();
C
CyC2018 已提交
278

C
CyC2018 已提交
279 280 281 282
public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
    for (int i = 0; i < in.length; i++)
        indexForInOrders.put(in[i], i);
    return reConstructBinaryTree(pre, 0, pre.length - 1, 0);
C
CyC2018 已提交
283 284
}

C
CyC2018 已提交
285 286 287 288 289 290 291 292 293
private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int inL) {
    if (preL > preR)
        return null;
    TreeNode root = new TreeNode(pre[preL]);
    int inIndex = indexForInOrders.get(root.val);
    int leftTreeSize = inIndex - inL;
    root.left = reConstructBinaryTree(pre, preL + 1, preL + leftTreeSize, inL);
    root.right = reConstructBinaryTree(pre, preL + leftTreeSize + 1, preR, inL + leftTreeSize + 1);
    return root;
C
CyC2018 已提交
294 295 296
}
```

C
CyC2018 已提交
297
# 8. 二叉树的下一个结点
C
CyC2018 已提交
298 299 300

[NowCoder](https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&tqId=11210&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)

C
CyC2018 已提交
301
## 题目描述
C
CyC2018 已提交
302 303 304 305

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

```java
C
CyC2018 已提交
306
public class TreeLinkNode {
C
CyC2018 已提交
307

C
CyC2018 已提交
308 309 310 311
    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode next = null;
C
CyC2018 已提交
312

C
CyC2018 已提交
313 314 315
    TreeLinkNode(int val) {
        this.val = val;
    }
C
CyC2018 已提交
316 317 318
}
```

C
CyC2018 已提交
319
## 解题思路
C
CyC2018 已提交
320

C
CyC2018 已提交
321
① 如果一个节点的右子树不为空,那么该节点的下一个节点是右子树的最左节点;
C
CyC2018 已提交
322

C
CyC2018 已提交
323
<div align="center"> <img src="pics/_u4E8C_u53C9_u6811_u7684_u4E0B_.gif" width="250"/> </div><br>
C
CyC2018 已提交
324

C
CyC2018 已提交
325
② 否则,向上找第一个左链接指向的树包含该节点的祖先节点。
C
CyC2018 已提交
326

C
CyC2018 已提交
327
<div align="center"> <img src="pics/_u4E8C_u53C9_u6811_u7684_u4E0B_1548504426508.gif" width="250"/> </div><br>
C
CyC2018 已提交
328 329

```java
C
CyC2018 已提交
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
public TreeLinkNode GetNext(TreeLinkNode pNode) {
    if (pNode.right != null) {
        TreeLinkNode node = pNode.right;
        while (node.left != null)
            node = node.left;
        return node;
    } else {
        while (pNode.next != null) {
            TreeLinkNode parent = pNode.next;
            if (parent.left == pNode)
                return parent;
            pNode = pNode.next;
        }
    }
    return null;
C
CyC2018 已提交
345 346 347
}
```

C
CyC2018 已提交
348
# 9. 用两个栈实现队列
C
CyC2018 已提交
349 350 351

[NowCoder](https://www.nowcoder.com/practice/54275ddae22f475981afa2244dd448c6?tpId=13&tqId=11158&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)

C
CyC2018 已提交
352
## 题目描述
C
CyC2018 已提交
353

C
CyC2018 已提交
354
用两个栈来实现一个队列,完成队列的 Push 和 Pop 操作。
C
CyC2018 已提交
355

C
CyC2018 已提交
356
## 解题思路
C
CyC2018 已提交
357

C
CyC2018 已提交
358
in 栈用来处理入栈(push)操作,out 栈用来处理出栈(pop)操作。一个元素进入 in 栈之后,出栈的顺序被反转。当元素要出栈时,需要先进入 out 栈,此时元素出栈顺序再一次被反转,因此出栈顺序就和最开始入栈顺序是相同的,先进入的元素先退出,这就是队列的顺序。
C
CyC2018 已提交
359

C
CyC2018 已提交
360
<div align="center"> <img src="pics/_u7528_u4E24_u4E2A_u6808_u5B9E_.gif" width="500"/> </div><br>
C
CyC2018 已提交
361 362 363


```java
C
CyC2018 已提交
364 365
Stack<Integer> in = new Stack<Integer>();
Stack<Integer> out = new Stack<Integer>();
C
CyC2018 已提交
366

C
CyC2018 已提交
367 368
public void push(int node) {
    in.push(node);
C
CyC2018 已提交
369 370
}

C
CyC2018 已提交
371 372 373 374
public int pop() throws Exception {
    if (out.isEmpty())
        while (!in.isEmpty())
            out.push(in.pop());
C
CyC2018 已提交
375

C
CyC2018 已提交
376 377
    if (out.isEmpty())
        throw new Exception("queue is empty");
C
CyC2018 已提交
378

C
CyC2018 已提交
379
    return out.pop();
C
CyC2018 已提交
380 381 382
}
```

C
CyC2018 已提交
383
</br></br><div align="center">欢迎关注公众号,获取最新文章!</div></br>
C
CyC2018 已提交
384
<div align="center"><img width="180px" src="https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg"></img></div>