剑指 Offer 题解 - 40~49.md 14.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 30 31 32 33 34 35 36 37 38
<!-- GFM-TOC -->
* [40. 最小的 K 个数](#40-最小的-k-个数)
    * [解题思路](#解题思路)
        * [快速选择](#快速选择)
        * [大小为 K 的最小堆](#大小为-k-的最小堆)
* [41.1 数据流中的中位数](#411-数据流中的中位数)
    * [题目描述](#题目描述)
    * [解题思路](#解题思路)
* [41.2 字符流中第一个不重复的字符](#412-字符流中第一个不重复的字符)
    * [题目描述](#题目描述)
    * [解题思路](#解题思路)
* [42. 连续子数组的最大和](#42-连续子数组的最大和)
    * [题目描述](#题目描述)
    * [解题思路](#解题思路)
* [43. 从 1 到 n 整数中 1 出现的次数](#43-从-1-到-n-整数中-1-出现的次数)
    * [解题思路](#解题思路)
* [44. 数字序列中的某一位数字](#44-数字序列中的某一位数字)
    * [题目描述](#题目描述)
    * [解题思路](#解题思路)
* [45. 把数组排成最小的数](#45-把数组排成最小的数)
    * [题目描述](#题目描述)
    * [解题思路](#解题思路)
* [46. 把数字翻译成字符串](#46-把数字翻译成字符串)
    * [题目描述](#题目描述)
    * [解题思路](#解题思路)
* [47. 礼物的最大价值](#47-礼物的最大价值)
    * [题目描述](#题目描述)
    * [解题思路](#解题思路)
* [48. 最长不含重复字符的子字符串](#48-最长不含重复字符的子字符串)
    * [题目描述](#题目描述)
    * [解题思路](#解题思路)
* [49. 丑数](#49-丑数)
    * [题目描述](#题目描述)
    * [解题思路](#解题思路)
<!-- GFM-TOC -->


# 40. 最小的 K 个数
C
CyC2018 已提交
39 40 41

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

C
CyC2018 已提交
42
## 解题思路
C
CyC2018 已提交
43

C
CyC2018 已提交
44
### 快速选择
C
CyC2018 已提交
45

C
CyC2018 已提交
46 47
- 复杂度:O(N) + O(1)
- 只有当允许修改数组元素时才可以使用
C
CyC2018 已提交
48

C
CyC2018 已提交
49
快速排序的 partition() 方法,会返回一个整数 j 使得 a[l..j-1] 小于等于 a[j],且 a[j+1..h] 大于等于 a[j],此时 a[j] 就是数组的第 j 大元素。可以利用这个特性找出数组的第 K 个元素,这种找第 K 个元素的算法称为快速选择算法。
C
CyC2018 已提交
50 51

```java
C
CyC2018 已提交
52 53 54 55 56 57 58 59 60
public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) {
    ArrayList<Integer> ret = new ArrayList<>();
    if (k > nums.length || k <= 0)
        return ret;
    findKthSmallest(nums, k - 1);
    /* findKthSmallest 会改变数组,使得前 k 个数都是最小的 k 个数 */
    for (int i = 0; i < k; i++)
        ret.add(nums[i]);
    return ret;
C
CyC2018 已提交
61 62
}

C
CyC2018 已提交
63 64 65 66 67 68 69 70 71 72 73
public void findKthSmallest(int[] nums, int k) {
    int l = 0, h = nums.length - 1;
    while (l < h) {
        int j = partition(nums, l, h);
        if (j == k)
            break;
        if (j > k)
            h = j - 1;
        else
            l = j + 1;
    }
C
CyC2018 已提交
74 75
}

C
CyC2018 已提交
76 77 78 79 80 81 82 83 84 85 86 87
private int partition(int[] nums, int l, int h) {
    int p = nums[l];     /* 切分元素 */
    int i = l, j = h + 1;
    while (true) {
        while (i != h && nums[++i] < p) ;
        while (j != l && nums[--j] > p) ;
        if (i >= j)
            break;
        swap(nums, i, j);
    }
    swap(nums, l, j);
    return j;
C
CyC2018 已提交
88 89
}

C
CyC2018 已提交
90 91 92 93
private void swap(int[] nums, int i, int j) {
    int t = nums[i];
    nums[i] = nums[j];
    nums[j] = t;
C
CyC2018 已提交
94 95 96
}
```

C
CyC2018 已提交
97
### 大小为 K 的最小堆
C
CyC2018 已提交
98

C
CyC2018 已提交
99 100
- 复杂度:O(NlogK) + O(K)
- 特别适合处理海量数据
C
CyC2018 已提交
101 102 103

应该使用大顶堆来维护最小堆,而不能直接创建一个小顶堆并设置一个大小,企图让小顶堆中的元素都是最小元素。

C
CyC2018 已提交
104
维护一个大小为 K 的最小堆过程如下:在添加一个元素之后,如果大顶堆的大小大于 K,那么需要将大顶堆的堆顶元素去除。
C
CyC2018 已提交
105 106

```java
C
CyC2018 已提交
107 108 109 110 111 112 113 114 115 116
public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) {
    if (k > nums.length || k <= 0)
        return new ArrayList<>();
    PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1);
    for (int num : nums) {
        maxHeap.add(num);
        if (maxHeap.size() > k)
            maxHeap.poll();
    }
    return new ArrayList<>(maxHeap);
C
CyC2018 已提交
117 118 119
}
```

C
CyC2018 已提交
120
# 41.1 数据流中的中位数
C
CyC2018 已提交
121 122 123

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

C
CyC2018 已提交
124
## 题目描述
C
CyC2018 已提交
125 126 127

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

C
CyC2018 已提交
128
## 解题思路
C
CyC2018 已提交
129 130

```java
C
CyC2018 已提交
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
/* 大顶堆,存储左半边元素 */
private PriorityQueue<Integer> left = new PriorityQueue<>((o1, o2) -> o2 - o1);
/* 小顶堆,存储右半边元素,并且右半边元素都大于左半边 */
private PriorityQueue<Integer> right = new PriorityQueue<>();
/* 当前数据流读入的元素个数 */
private int N = 0;

public void Insert(Integer val) {
    /* 插入要保证两个堆存于平衡状态 */
    if (N % 2 == 0) {
        /* N 为偶数的情况下插入到右半边。
         * 因为右半边元素都要大于左半边,但是新插入的元素不一定比左半边元素来的大,
         * 因此需要先将元素插入左半边,然后利用左半边为大顶堆的特点,取出堆顶元素即为最大元素,此时插入右半边 */
        left.add(val);
        right.add(left.poll());
    } else {
        right.add(val);
        left.add(right.poll());
    }
    N++;
C
CyC2018 已提交
151 152
}

C
CyC2018 已提交
153 154 155 156 157
public Double GetMedian() {
    if (N % 2 == 0)
        return (left.peek() + right.peek()) / 2.0;
    else
        return (double) right.peek();
C
CyC2018 已提交
158 159 160
}
```

C
CyC2018 已提交
161
# 41.2 字符流中第一个不重复的字符
C
CyC2018 已提交
162 163 164

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

C
CyC2018 已提交
165
## 题目描述
C
CyC2018 已提交
166

C
CyC2018 已提交
167
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符 "go" 时,第一个只出现一次的字符是 "g"。当从该字符流中读出前六个字符“google" 时,第一个只出现一次的字符是 "l"。
C
CyC2018 已提交
168

C
CyC2018 已提交
169
## 解题思路
C
CyC2018 已提交
170 171

```java
C
CyC2018 已提交
172 173 174 175 176 177 178 179
private int[] cnts = new int[256];
private Queue<Character> queue = new LinkedList<>();

public void Insert(char ch) {
    cnts[ch]++;
    queue.add(ch);
    while (!queue.isEmpty() && cnts[queue.peek()] > 1)
        queue.poll();
C
CyC2018 已提交
180 181
}

C
CyC2018 已提交
182 183
public char FirstAppearingOnce() {
    return queue.isEmpty() ? '#' : queue.peek();
C
CyC2018 已提交
184 185 186
}
```

C
CyC2018 已提交
187
# 42. 连续子数组的最大和
C
CyC2018 已提交
188 189 190

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

C
CyC2018 已提交
191
## 题目描述
C
CyC2018 已提交
192

C
CyC2018 已提交
193
{6, -3, -2, 7, -15, 1, 2, 2},连续子数组的最大和为 8(从第 0 个开始,到第 3 个为止)。
C
CyC2018 已提交
194

C
CyC2018 已提交
195
## 解题思路
C
CyC2018 已提交
196 197

```java
C
CyC2018 已提交
198 199 200 201 202 203 204 205 206 207
public int FindGreatestSumOfSubArray(int[] nums) {
    if (nums == null || nums.length == 0)
        return 0;
    int greatestSum = Integer.MIN_VALUE;
    int sum = 0;
    for (int val : nums) {
        sum = sum <= 0 ? val : sum + val;
        greatestSum = Math.max(greatestSum, sum);
    }
    return greatestSum;
C
CyC2018 已提交
208 209 210
}
```

C
CyC2018 已提交
211
# 43. 从 1 到 n 整数中 1 出现的次数
C
CyC2018 已提交
212 213 214

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

C
CyC2018 已提交
215
## 解题思路
C
CyC2018 已提交
216 217

```java
C
CyC2018 已提交
218 219 220 221 222 223 224
public int NumberOf1Between1AndN_Solution(int n) {
    int cnt = 0;
    for (int m = 1; m <= n; m *= 10) {
        int a = n / m, b = n % m;
        cnt += (a + 8) / 10 * m + (a % 10 == 1 ? b + 1 : 0);
    }
    return cnt;
C
CyC2018 已提交
225 226 227
}
```

C
CyC2018 已提交
228
> [Leetcode : 233. Number of Digit One](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython)
C
CyC2018 已提交
229

C
CyC2018 已提交
230
# 44. 数字序列中的某一位数字
C
CyC2018 已提交
231

C
CyC2018 已提交
232
## 题目描述
C
CyC2018 已提交
233

C
CyC2018 已提交
234
数字以 0123456789101112131415... 的格式序列化到一个字符串中,求这个字符串的第 index 位。
C
CyC2018 已提交
235

C
CyC2018 已提交
236
## 解题思路
C
CyC2018 已提交
237 238

```java
C
CyC2018 已提交
239 240 241 242 243 244 245 246 247 248 249 250
public int getDigitAtIndex(int index) {
    if (index < 0)
        return -1;
    int place = 1;  // 1 表示个位,2 表示 十位...
    while (true) {
        int amount = getAmountOfPlace(place);
        int totalAmount = amount * place;
        if (index < totalAmount)
            return getDigitAtIndex(index, place);
        index -= totalAmount;
        place++;
    }
C
CyC2018 已提交
251 252 253
}

/**
C
CyC2018 已提交
254 255 256 257 258 259 260
 * place 位数的数字组成的字符串长度
 * 10, 90, 900, ...
 */
private int getAmountOfPlace(int place) {
    if (place == 1)
        return 10;
    return (int) Math.pow(10, place - 1) * 9;
C
CyC2018 已提交
261 262 263
}

/**
C
CyC2018 已提交
264 265 266 267 268 269 270
 * place 位数的起始数字
 * 0, 10, 100, ...
 */
private int getBeginNumberOfPlace(int place) {
    if (place == 1)
        return 0;
    return (int) Math.pow(10, place - 1);
C
CyC2018 已提交
271 272 273
}

/**
C
CyC2018 已提交
274 275 276 277 278 279 280 281
 * 在 place 位数组成的字符串中,第 index 个数
 */
private int getDigitAtIndex(int index, int place) {
    int beginNumber = getBeginNumberOfPlace(place);
    int shiftNumber = index / place;
    String number = (beginNumber + shiftNumber) + "";
    int count = index % place;
    return number.charAt(count) - '0';
C
CyC2018 已提交
282 283 284
}
```

C
CyC2018 已提交
285
# 45. 把数组排成最小的数
C
CyC2018 已提交
286 287 288

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

C
CyC2018 已提交
289
## 题目描述
C
CyC2018 已提交
290

C
CyC2018 已提交
291
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组 {3,32,321},则打印出这三个数字能排成的最小数字为 321323。
C
CyC2018 已提交
292

C
CyC2018 已提交
293
## 解题思路
C
CyC2018 已提交
294

C
CyC2018 已提交
295
可以看成是一个排序问题,在比较两个字符串 S1 和 S2 的大小时,应该比较的是 S1+S2 和 S2+S1 的大小,如果 S1+S2 < S2+S1,那么应该把 S1 排在前面,否则应该把 S2 排在前面。
C
CyC2018 已提交
296 297

```java
C
CyC2018 已提交
298 299 300 301 302 303 304 305 306 307 308 309
public String PrintMinNumber(int[] numbers) {
    if (numbers == null || numbers.length == 0)
        return "";
    int n = numbers.length;
    String[] nums = new String[n];
    for (int i = 0; i < n; i++)
        nums[i] = numbers[i] + "";
    Arrays.sort(nums, (s1, s2) -> (s1 + s2).compareTo(s2 + s1));
    String ret = "";
    for (String str : nums)
        ret += str;
    return ret;
C
CyC2018 已提交
310 311 312
}
```

C
CyC2018 已提交
313
# 46. 把数字翻译成字符串
C
CyC2018 已提交
314 315 316

[Leetcode](https://leetcode.com/problems/decode-ways/description/)

C
CyC2018 已提交
317
## 题目描述
C
CyC2018 已提交
318

C
CyC2018 已提交
319
给定一个数字,按照如下规则翻译成字符串:1 翻译成“a”,2 翻译成“b”... 26 翻译成“z”。一个数字有多种翻译可能,例如 12258 一共有 5 种,分别是 abbeh,lbeh,aveh,abyh,lyh。实现一个函数,用来计算一个数字有多少种不同的翻译方法。
C
CyC2018 已提交
320

C
CyC2018 已提交
321
## 解题思路
C
CyC2018 已提交
322 323

```java
C
CyC2018 已提交
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
public int numDecodings(String s) {
    if (s == null || s.length() == 0)
        return 0;
    int n = s.length();
    int[] dp = new int[n + 1];
    dp[0] = 1;
    dp[1] = s.charAt(0) == '0' ? 0 : 1;
    for (int i = 2; i <= n; i++) {
        int one = Integer.valueOf(s.substring(i - 1, i));
        if (one != 0)
            dp[i] += dp[i - 1];
        if (s.charAt(i - 2) == '0')
            continue;
        int two = Integer.valueOf(s.substring(i - 2, i));
        if (two <= 26)
            dp[i] += dp[i - 2];
    }
    return dp[n];
C
CyC2018 已提交
342 343 344
}
```

C
CyC2018 已提交
345
# 47. 礼物的最大价值
C
CyC2018 已提交
346 347 348

[NowCoder](https://www.nowcoder.com/questionTerminal/72a99e28381a407991f2c96d8cb238ab)

C
CyC2018 已提交
349
## 题目描述
C
CyC2018 已提交
350

C
CyC2018 已提交
351
在一个 m\*n 的棋盘的每一个格都放有一个礼物,每个礼物都有一定价值(大于 0)。从左上角开始拿礼物,每次向右或向下移动一格,直到右下角结束。给定一个棋盘,求拿到礼物的最大价值。例如,对于如下棋盘
C
CyC2018 已提交
352 353

```
C
CyC2018 已提交
354 355 356 357
1    10   3    8
12   2    9    6
5    7    4    11
3    7    16   5
C
CyC2018 已提交
358 359
```

C
CyC2018 已提交
360
礼物的最大价值为 1+12+5+7+7+16+5=53。
C
CyC2018 已提交
361

C
CyC2018 已提交
362
## 解题思路
C
CyC2018 已提交
363 364 365 366

应该用动态规划求解,而不是深度优先搜索,深度优先搜索过于复杂,不是最优解。

```java
C
CyC2018 已提交
367 368 369 370 371 372 373 374 375 376 377
public int getMost(int[][] values) {
    if (values == null || values.length == 0 || values[0].length == 0)
        return 0;
    int n = values[0].length;
    int[] dp = new int[n];
    for (int[] value : values) {
        dp[0] += value[0];
        for (int i = 1; i < n; i++)
            dp[i] = Math.max(dp[i], dp[i - 1]) + value[i];
    }
    return dp[n - 1];
C
CyC2018 已提交
378 379 380
}
```

C
CyC2018 已提交
381
# 48. 最长不含重复字符的子字符串
C
CyC2018 已提交
382

C
CyC2018 已提交
383
## 题目描述
C
CyC2018 已提交
384

C
CyC2018 已提交
385
输入一个字符串(只包含 a\~z 的字符),求其最长不含重复字符的子字符串的长度。例如对于 arabcacfr,最长不含重复字符的子字符串为 acfr,长度为 4。
C
CyC2018 已提交
386

C
CyC2018 已提交
387
## 解题思路
C
CyC2018 已提交
388 389

```java
C
CyC2018 已提交
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407
public int longestSubStringWithoutDuplication(String str) {
    int curLen = 0;
    int maxLen = 0;
    int[] preIndexs = new int[26];
    Arrays.fill(preIndexs, -1);
    for (int curI = 0; curI < str.length(); curI++) {
        int c = str.charAt(curI) - 'a';
        int preI = preIndexs[c];
        if (preI == -1 || curI - preI > curLen) {
            curLen++;
        } else {
            maxLen = Math.max(maxLen, curLen);
            curLen = curI - preI;
        }
        preIndexs[c] = curI;
    }
    maxLen = Math.max(maxLen, curLen);
    return maxLen;
C
CyC2018 已提交
408 409 410
}
```

C
CyC2018 已提交
411
# 49. 丑数
C
CyC2018 已提交
412 413 414

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

C
CyC2018 已提交
415
## 题目描述
C
CyC2018 已提交
416

C
CyC2018 已提交
417
把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。例如 6、8 都是丑数,但 14 不是,因为它包含因子 7。习惯上我们把 1 当做是第一个丑数。求按从小到大的顺序的第 N 个丑数。
C
CyC2018 已提交
418

C
CyC2018 已提交
419
## 解题思路
C
CyC2018 已提交
420 421

```java
C
CyC2018 已提交
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
public int GetUglyNumber_Solution(int N) {
    if (N <= 6)
        return N;
    int i2 = 0, i3 = 0, i5 = 0;
    int[] dp = new int[N];
    dp[0] = 1;
    for (int i = 1; i < N; i++) {
        int next2 = dp[i2] * 2, next3 = dp[i3] * 3, next5 = dp[i5] * 5;
        dp[i] = Math.min(next2, Math.min(next3, next5));
        if (dp[i] == next2)
            i2++;
        if (dp[i] == next3)
            i3++;
        if (dp[i] == next5)
            i5++;
    }
    return dp[N - 1];
C
CyC2018 已提交
439 440
}
```
C
CyC2018 已提交
441 442 443 444 445 446 447




<div align="center">欢迎关注公众号,获取最新文章!</div>


C
CyC2018 已提交
448
<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>