03-19 | 《剑指offer第2版》Java题解总结(上)_科技小猎豹的博客-CSDN博客 |
20-39 | 《剑指offer第2版》Java题解总结(中)_科技小猎豹的博客-CSDN博客 |
40-68 |
40-68
40. 最小的 k 个数
4种解法秒杀TopK(快排/堆/二叉搜索树/计数排序)力扣
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (k >= arr.length) return arr;
return quickSort(arr, k, 0, arr.length - 1);
}
private int[] quickSort(int[] arr, int k, int l, int r) {
int i = l, j = r;
while (i < j) {
while (i < j && arr[j] >= arr[l]) j--;
while (i < j && arr[i] <= arr[l]) i++;
swap(arr, i, j);
}
swap(arr, i, l);
if (i > k) return quickSort(arr, k, l, i - 1);
if (i < k) return quickSort(arr, k, i + 1, r);
return Arrays.copyOf(arr, k);
}
private void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
41.数据流中的中位数困难
与主站 295 题相同:https://leetcode-cn.com/problems/find-median-from-data-stream/
42.连续子数组的最大和dp,空间1
public int maxSubArray(int[] nums) {
int pre = 0, maxAns = nums[0];
for (int x : nums) {
pre = Math.max(pre + x, x);
maxAns = Math.max(maxAns, pre);
}
return maxAns;
}
43.整数中出现1的次数(LCR162数字1的个数)
困难与主站 233 题相同:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
44.数字序列中某一位的数字(LCR163找到第K位数字)
与主站 400 题相同:https://leetcode-cn.com/problems/nth-digit/
public int findKthNumber(int k) {
int digit = 1;
long start = 1;
long count = 9;
while (k > count) { // 1.
k -= count;
start *= 10;
digit += 1;
count = digit * start * 9;
}
long num = start + (k - 1) / digit; // 2.
return Long.toString(num).charAt((k - 1) % digit) - '0'; // 3.
}
45.把数组排列成最小的数(LCR164)
具体做法:
step 1:优先判断空数组的特殊情况。
step 2:将数组中的数字元素转换成字符串类型。
step 3:重载排序比较为字符串类型的x + y < y + x,然后进行排序。
step 4:将排序结果再按照字符串拼接成一个整体。import java.util.*;
public class Solution {
public String PrintMinNumber(int [] numbers) {
//空数组的情况
if(numbers == null || numbers.length == 0)
return "";
String[] nums = new String[numbers.length];
//将数字转成字符
for(int i = 0; i < numbers.length; i++)
nums[i] = numbers[i] + "";
//按照重载排序
Arrays.sort(nums, new Comparator<String>() {
public int compare(String s1, String s2) {
return (s1 + s2).compareTo(s2 + s1);
}
});
StringBuilder res = new StringBuilder();
//字符串叠加
for(int i = 0; i < nums.length; i++)
res.append(nums[i]);
return res.toString();
}
}
46.把数字翻译成字符串(165)中等
状态定义: 设动态规划列表 dp,dp[i] 代表以x i为结尾的数字的翻译方案数量。
转移方程: 若x_i 和 xi组成的两位数字可被整体翻译,
则 dp[i]=dp[i−1]+dp[i−2],否则 dp[i]=dp[i−1]。
47.礼物的最大价值166
dp
48.最长不含重复字符的子字符串167
与主站 3 题相同:https://leetcode.cn/problems/longest-substring-without-repeating-characters/
滑动窗口/动规 + 哈希表
49.丑数168
与主站 264 题相同:https://leetcode-cn.com/problems/ugly-number-ii/
存优先队列。O(N)
int a = 0, b = 0, c = 0;
int[] res = new int[n];
res[0] = 1;
for(int i = 1; i < n; i++) {
int n2 = res[a] * 2, n3 = res[b] * 3, n5 = res[c] * 5;
res[i] = Math.min(Math.min(n2, n3), n5);
if (res[i] == n2) a++;
if (res[i] == n3) b++;
if (res[i] == n5) c++;
}
return res[n - 1];
50.第一个只出现一次的字符169
哈希表。简化为true标记。
51.数组中的逆序对困难170
52.两个链表的第一个公共节点171
双指针
53.在排序数组中查找数字172、173
与主站 34 题相同(仅返回值不同):https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/
O(logN)
53 - II. 0 ~ n-1 中缺失的数字
排序数组中的搜索问题,首先想到 二分查找 解决.减少冗余。
54.二叉搜索树的第K大的节点174
测试用例
功能测试(各种形态不同的二叉搜索树)
边界值测试(输入k为0、1、二叉搜索树的节点数、二叉树搜索树的节点数+1)
特殊输入测试(指向二叉搜索树根节点的指针为空指针)
题目考点
考察应聘者的只是迁移能力,利用中序遍历解题。
考察应聘者对二叉搜索树和中序遍历的特点的理解。
解题思路
按照中序遍历的顺序遍历一个二叉搜索树。倒序,找到第k提前结束。
55.二叉树的深度175,- II平衡二叉树176
与主站 110 题相同:https://leetcode-cn.com/problems/balanced-binary-tree/
题目二:对二叉树做后序遍历,从底至顶返回子树深度,若判定某子树不是平衡树则 “剪枝” ,直接向上返回。
56.- I. 数组中数字出现的次数177
. - 力扣(LeetCode)异或位运算
56 - II. 数组中数字出现的次数 II178
统计所有数字的各二进制位中 111 的出现次数,并对 333 求余,结果则为只出现一次的数字。
有限状态自动机
class Solution {
public int trainingPlan(int[] actions) {
int ones = 0, twos = 0;
for(int action : actions){
ones = ones ^ action & ~twos;
twos = twos ^ action & ~ones;
}
return ones;
}
}
57.和为 s 的两个数字179
解题思路:
利用 HashMap 可以通过遍历数组找到数字组合,时间和空间复杂度均为 O(N) ;
注意本题的 nums是 排序数组 ,因此可使用 双指针法 将空间复杂度降低至 O(1) 。提醒一下,判断条件最好不要用相加后的结果,应该用target - nums[i] 跟 nums[j]比较,这样保证不会溢出。虽然这题中不会出错。同样的例子还有二分查找,(left + right) / 2 可以用left + ((rigth - left) >> 1))代替
Krahets
@Little boy 哈喽,感谢指出,非常细节! 感觉严格说来 target - nums[i] 也可能越界,因为 nums[i] 可能是负数,因此我觉得用 long 去比可能是最安全~
57 - II. 和为 s 的连续正数序列180
滑动窗口的重要性质是:窗口的左边界和右边界永远只能向右移动,而不能向左移动。这是为了保证滑动窗口的时间复杂度是 O(n)。
toArray(new T[0])能够运行是因为java做了优化,能动态生成对应大小的数组,是官方建议的书写方式,能避免某些并发问题并且效率更高
力扣求和公式 / 滑动窗口
58.翻转字符串181
分割+倒序
58 - II. 左旋转字符串182
字符串拼接substring( ,)
59.- I. 滑动窗口的最大值183困难
与主站 239 题相同:https://leetcode.cn/problems/sliding-window-maximum/
算法流程:
初始化: 双端队列 deque ,结果列表res ,数组长度 n ;
滑动窗口: 左边界范围 i∈[1−limit,n−limit],右边界范围 j∈[0,n−1];
若 i>0 且 队首元素 deque[0] = 被删除元素 heights[i - 1] :则队首元素出队;
删除 deque内所有 <heights[j] 的元素,以保持 deque 递减;
将 heights[j]添加至 deque 尾部;
若已形成窗口(即 i≥0i \geq 0i≥0 ):将窗口最大值(即队首元素 deque[0] )添加至列表 res;
返回值: 返回结果列表 res
59 - II. 队列的最大值184
空间换时间,双头队列递减。
class Checkout {
Queue<Integer> queue;
Deque<Integer> deque;
public Checkout() {
queue = new LinkedList<>();
deque = new LinkedList<>();
}
public int get_max() {
return deque.isEmpty() ? -1 : deque.peekFirst();
}
public void add(int value) {
queue.offer(value);
while(!deque.isEmpty() && deque.peekLast() < value)
deque.pollLast();
deque.offerLast(value);
}
public int remove() {
if(queue.isEmpty()) return -1;
if(queue.peek().equals(deque.peekFirst()))
deque.pollFirst();
return queue.poll();
}
}
60.n个骰子的点数185
考察应聘者的数学建模能力。对递归和循环的性能的理解。
使得n-1点数概率数组和1点数概率数组元素两两相乘,并将乘积结果加到n点数概率数组上。
运算完成后就得到了最终的n点数概率数组。基本思路如上,然后我们可以根据动态规划的套路:
1.构造dp数组:tmp[]为n个骰子的点数概率数组,pre[]为n-1个骰子的点数概率数组,一个骰子的点数概率数组显然是6个六分之一,不需要另设数组。
2.初始化dp数组:pre[]={1/6d,1/6d,1/6d,1/6d,1/6d,1/6d}
3.构造状态转移方程:tmp[x+y]+=pre[x]*num[y];
Java代码如下:public double[] twoSum(int n) {
double pre[]={1/6d,1/6d,1/6d,1/6d,1/6d,1/6d};
for(int i=2;i<=n;i++){
double tmp[]=new double[5*i+1];
for(int j=0;j<pre.length;j++)
for(int x=0;x<6;x++)
tmp[j+x]+=pre[j]/6;
pre=tmp;
}
return pre;
}
61.顺子186
62. 圆圈中最后剩下的数字187
迭代,避免递归使用栈空间。
class Solution {
public int iceBreakingGame(int num, int target) {
int f = 0;
for (int i = 2; i != num + 1; ++i) {
f = (target + f) % i;
}
return f;
}
}
复杂度分析
时间复杂度:O(num),需要求解的函数值有 num 个。
空间复杂度:O(1),只使用常数个变量。
63.股票的最大利润188
与主站 121 题相同:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
class Solution {
public int maxProfit(int[] prices) {
int cost = Integer.MAX_VALUE, profit = 0;
for(int price : prices) {
cost = Math.min(cost, price);
profit = Math.max(profit, price - cost);
}
return profit;
}
}
64.求1+2+...+n189
解题思路
反正肯定是用递归,然后我们需要的就是怎么把返回条件确定好。条件与 && 具有短路原则,即在第一个条件语句为 false 的情况下不会去执行第二个条件语句。利用这一特性,将递归的返回条件取非然后作为 && 的第一个条件语句,递归的主体转换为第二个条件语句,那么当递归的返回条件为 true 的情况下就不会执行递归的主体部分,递归返回。
class Solution {
public int sumNums(int n) {
boolean x = n > 1 && (n += sumNums(n - 1)) > 0;
return n;
}
}
65.不用加减乘除做加法190
解题思路---计算机二进制位运算
1.利用位异或运算不考虑进位地加两个数
2.利用位与运算以及左移运算得到进制代表的值
3.将上面1,2的结果相加(即重复1,2运算,直到进位为0)
66.构建乘积数组191
【双向遍历】具体做法:
step 1:初始化数组B,第一个元素为1.
step 2:从左到右遍历数组A,将数组B的前一个数与数组A的前一个数相乘就得到了下三角中数组B的当前数。
step 3:再从右到左遍历数组A,用一个数字记录从右到左上三角中的累乘,每次只会乘上一个数,同时给数组B对应部分也乘上该累乘。整体思路,结果集中任何一个元素 = 其左边所有元素的乘积 * 其右边所有元素的乘积。一轮循环构建左边的乘积并保存在结果集中,二轮循环 构建右边乘积的过程,乘以左边的乘积,并将最终结果保存
class Solution {
public int[] constructArr(int[] a) {
int len = a.length;
if(len == 0) return new int[0];
int[] b = new int[len];
b[0] = 1;
int tmp = 1;
for(int i = 1; i < len; i++) {
b[i] = b[i - 1] * a[i - 1];
}
for(int i = len - 2; i >= 0; i--) {
tmp *= a[i + 1];
b[i] *= tmp;
}
return b;
}
}
67.把字符串转换成整数192
与主站 8 题相同:https://leetcode-cn.com/problems/string-to-integer-atoi/
不使用库函数的字符串转整数(数字越界处理)。
68.I二叉搜索树的最近公共祖先193
与主站 235 题相同:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/
迭代一层层排除O(N)。
68 - II. 二叉树的最近公共祖先194
与主站 236 题相同:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/
递归O(N),四种情况终止条件、递推分类。。