CS-Notes/docs/notes/剑指 Offer 题解 - 40~49.md

404 lines
12 KiB
Java
Raw Normal View History

2019-03-27 20:57:37 +08:00
# 40. 最小的 K 个数
2019-03-08 21:29:22 +08:00
2019-10-17 02:15:15 +08:00
[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&from=cyc_github)
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 解题思路
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
### 快速选择
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
- 复杂度O(N) + O(1)
- 只有当允许修改数组元素时才可以使用
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
快速排序的 partition() 方法会返回一个整数 j 使得 a[l..j-1] 小于等于 a[j] a[j+1..h] 大于等于 a[j]此时 a[j] 就是数组的第 j 大元素可以利用这个特性找出数组的第 K 个元素这种找第 K 个元素的算法称为快速选择算法
2019-03-08 21:29:22 +08:00
```java
2019-03-27 20:57:37 +08:00
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;
2019-03-08 21:29:22 +08:00
}
2019-03-27 20:57:37 +08:00
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;
}
2019-03-08 21:29:22 +08:00
}
2019-03-27 20:57:37 +08:00
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;
2019-03-08 21:29:22 +08:00
}
2019-03-27 20:57:37 +08:00
private void swap(int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
2019-03-08 21:29:22 +08:00
}
```
2019-03-27 20:57:37 +08:00
### 大小为 K 的最小堆
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
- 复杂度O(NlogK) + O(K)
- 特别适合处理海量数据
2019-03-08 21:29:22 +08:00
应该使用大顶堆来维护最小堆而不能直接创建一个小顶堆并设置一个大小企图让小顶堆中的元素都是最小元素
2019-03-27 20:57:37 +08:00
维护一个大小为 K 的最小堆过程如下在添加一个元素之后如果大顶堆的大小大于 K那么需要将大顶堆的堆顶元素去除
2019-03-08 21:29:22 +08:00
```java
2019-03-27 20:57:37 +08:00
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);
2019-03-08 21:29:22 +08:00
}
```
2019-03-27 20:57:37 +08:00
# 41.1 数据流中的中位数
2019-03-08 21:29:22 +08:00
2019-10-17 02:15:15 +08:00
[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&from=cyc_github)
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 题目描述
2019-03-08 21:29:22 +08:00
如何得到一个数据流中的中位数如果从数据流中读出奇数个数值那么中位数就是所有数值排序之后位于中间的数值如果从数据流中读出偶数个数值那么中位数就是所有数值排序之后中间两个数的平均值
2019-03-27 20:57:37 +08:00
## 解题思路
2019-03-08 21:29:22 +08:00
```java
2019-03-27 20:57:37 +08:00
/* 大顶堆,存储左半边元素 */
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++;
2019-03-08 21:29:22 +08:00
}
2019-03-27 20:57:37 +08:00
public Double GetMedian() {
if (N % 2 == 0)
return (left.peek() + right.peek()) / 2.0;
else
return (double) right.peek();
2019-03-08 21:29:22 +08:00
}
```
2019-03-27 20:57:37 +08:00
# 41.2 字符流中第一个不重复的字符
2019-03-08 21:29:22 +08:00
2019-10-17 02:15:15 +08:00
[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&from=cyc_github)
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 题目描述
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
请实现一个函数用来找出字符流中第一个只出现一次的字符例如当从字符流中只读出前两个字符 "go" 第一个只出现一次的字符是 "g"当从该字符流中读出前六个字符google" 时,第一个只出现一次的字符是 "l"
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 解题思路
2019-03-08 21:29:22 +08:00
```java
2019-03-27 20:57:37 +08:00
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();
2019-03-08 21:29:22 +08:00
}
2019-03-27 20:57:37 +08:00
public char FirstAppearingOnce() {
return queue.isEmpty() ? '#' : queue.peek();
2019-03-08 21:29:22 +08:00
}
```
2019-03-27 20:57:37 +08:00
# 42. 连续子数组的最大和
2019-03-08 21:29:22 +08:00
2019-10-17 02:15:15 +08:00
[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&from=cyc_github)
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 题目描述
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
{6, -3, -2, 7, -15, 1, 2, 2}连续子数组的最大和为 8从第 0 个开始到第 3 个为止
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 解题思路
2019-03-08 21:29:22 +08:00
```java
2019-03-27 20:57:37 +08:00
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;
2019-03-08 21:29:22 +08:00
}
```
2019-03-27 20:57:37 +08:00
# 43. 1 n 整数中 1 出现的次数
2019-03-08 21:29:22 +08:00
2019-10-17 02:15:15 +08:00
[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&from=cyc_github)
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 解题思路
2019-03-08 21:29:22 +08:00
```java
2019-03-27 20:57:37 +08:00
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;
2019-03-08 21:29:22 +08:00
}
```
2019-03-27 20:57:37 +08:00
> [Leetcode : 233. Number of Digit One](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython)
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
# 44. 数字序列中的某一位数字
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 题目描述
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
数字以 0123456789101112131415... 的格式序列化到一个字符串中求这个字符串的第 index
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 解题思路
2019-03-08 21:29:22 +08:00
```java
2019-03-27 20:57:37 +08:00
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++;
}
2019-03-08 21:29:22 +08:00
}
/**
2019-03-27 20:57:37 +08:00
* place 位数的数字组成的字符串长度
* 10, 90, 900, ...
*/
private int getAmountOfPlace(int place) {
if (place == 1)
return 10;
return (int) Math.pow(10, place - 1) * 9;
2019-03-08 21:29:22 +08:00
}
/**
2019-03-27 20:57:37 +08:00
* place 位数的起始数字
* 0, 10, 100, ...
*/
private int getBeginNumberOfPlace(int place) {
if (place == 1)
return 0;
return (int) Math.pow(10, place - 1);
2019-03-08 21:29:22 +08:00
}
/**
2019-03-27 20:57:37 +08:00
* 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';
2019-03-08 21:29:22 +08:00
}
```
2019-03-27 20:57:37 +08:00
# 45. 把数组排成最小的数
2019-03-08 21:29:22 +08:00
2019-10-17 02:15:15 +08:00
[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&from=cyc_github)
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 题目描述
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
输入一个正整数数组把数组里所有数字拼接起来排成一个数打印能拼接出的所有数字中最小的一个例如输入数组 {332321}则打印出这三个数字能排成的最小数字为 321323
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 解题思路
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
可以看成是一个排序问题在比较两个字符串 S1 S2 的大小时应该比较的是 S1+S2 S2+S1 的大小如果 S1+S2 < S2+S1那么应该把 S1 排在前面否则应该把 S2 排在前面
2019-03-08 21:29:22 +08:00
```java
2019-03-27 20:57:37 +08:00
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;
2019-03-08 21:29:22 +08:00
}
```
2019-03-27 20:57:37 +08:00
# 46. 把数字翻译成字符串
2019-03-08 21:29:22 +08:00
[Leetcode](https://leetcode.com/problems/decode-ways/description/)
2019-03-27 20:57:37 +08:00
## 题目描述
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
给定一个数字按照如下规则翻译成字符串1 翻译成a2 翻译成b... 26 翻译成z一个数字有多种翻译可能例如 12258 一共有 5 分别是 abbehlbehavehabyhlyh实现一个函数用来计算一个数字有多少种不同的翻译方法
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 解题思路
2019-03-08 21:29:22 +08:00
```java
2019-03-27 20:57:37 +08:00
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];
2019-03-08 21:29:22 +08:00
}
```
2019-03-27 20:57:37 +08:00
# 47. 礼物的最大价值
2019-03-08 21:29:22 +08:00
[NowCoder](https://www.nowcoder.com/questionTerminal/72a99e28381a407991f2c96d8cb238ab)
2019-03-27 20:57:37 +08:00
## 题目描述
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
在一个 m\*n 的棋盘的每一个格都放有一个礼物每个礼物都有一定价值大于 0从左上角开始拿礼物每次向右或向下移动一格直到右下角结束给定一个棋盘求拿到礼物的最大价值例如对于如下棋盘
2019-03-08 21:29:22 +08:00
```
2019-03-27 20:57:37 +08:00
1 10 3 8
12 2 9 6
5 7 4 11
3 7 16 5
2019-03-08 21:29:22 +08:00
```
2019-03-27 20:57:37 +08:00
礼物的最大价值为 1+12+5+7+7+16+5=53
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 解题思路
2019-03-08 21:29:22 +08:00
应该用动态规划求解而不是深度优先搜索深度优先搜索过于复杂不是最优解
```java
2019-03-27 20:57:37 +08:00
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];
2019-03-08 21:29:22 +08:00
}
```
2019-03-27 20:57:37 +08:00
# 48. 最长不含重复字符的子字符串
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 题目描述
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
输入一个字符串只包含 a\~z 的字符求其最长不含重复字符的子字符串的长度例如对于 arabcacfr最长不含重复字符的子字符串为 acfr长度为 4
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 解题思路
2019-03-08 21:29:22 +08:00
```java
2019-03-27 20:57:37 +08:00
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;
2019-03-08 21:29:22 +08:00
}
```
2019-03-27 20:57:37 +08:00
# 49. 丑数
2019-03-08 21:29:22 +08:00
2019-10-17 02:15:15 +08:00
[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&from=cyc_github)
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 题目描述
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
把只包含因子 23 5 的数称作丑数Ugly Number例如 68 都是丑数 14 不是因为它包含因子 7习惯上我们把 1 当做是第一个丑数求按从小到大的顺序的第 N 个丑数
2019-03-08 21:29:22 +08:00
2019-03-27 20:57:37 +08:00
## 解题思路
2019-03-08 21:29:22 +08:00
```java
2019-03-27 20:57:37 +08:00
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];
2019-03-08 21:29:22 +08:00
}
```