动态规划
111. 爬楼梯
思路
类似斐波那契数列
注意
考虑第 0 阶的特殊情况
272. 爬楼梯 II
思路
类似上题,只是从两个变量变成了三个变量,注意特判下第 0、1、2 三阶台阶
630. 骑士的最短路径II
思路
骑士只能从左往右移动,所以从 (0, 0) 点出发沿着方向坐标先更新最左列数组,逐渐向右更新,直到 (n - 1, m - 1) 的位置
注意
对上一个点值和坐标是否越界的判断注意书写的正确
116. 跳跃游戏
思路
当前位置 i 是否可达由其前面的 0 到 i - 1 位置决定的,如果 j 位置可达且 j + A[j] >= i 则代表 i 可达,最后判断 A.length - 1 位置是否可达
117. 跳跃游戏 II
思路
思路和上题相似,但题目要求求出到达最小长度,所以将布尔数组换成整数数组,Integer.MAX_VALUE
代表不可达,其余数值代表从起始位置到当前位置的最小长度
错误
细节错误,忘记给 steps[0] 赋值 0
254. Drop Eggs
思路
哈希表与堆
128. 哈希函数
思路
背下来就好了
注意
- 应将 ans 设为 long 类型,否则当 key 很大时会溢出
- 要记得取模
129. 重哈希
思路
新的 hash 表容量是原来2倍,遍历原 hash 表,hash 表中元素模上 newSize 得到
newIndex,然后在 newIndex 对应位置的链表的结尾插入值
错误
while (hashTable[i] != null)
不能写成 if (hashTable[i] == null) { continue; }
如果写成 if 形式,当前 hash[i] 对应的位置如果是一个链表,那只会执行第一个结点的rehash,然后开始执行 hash[i + 1],hash[i] 位置的链表第一个结点后面的所有结点都会被跳过
544. 前K大数
思路
用 PriorityQueue 实现一个大小为 k 的最小堆,堆中元素不足 k 不断加入,堆中元素等于 k 时把堆顶最小值抛出
606. 第K大的元素 II
思路
如果 N 和 K 在一个数量级上,采用 quickSelect 来解题,如果 N >> K,采用 PriorityQueue 来解题
错误
最小堆要保持堆内元素个数在 k 个,这样可以保证执行 minHeap.peek() 时得到的是第 k 大的元素
所以有两种写法
for (int num : nums) {
q.add(num);
if (q.size() > k) {
q.poll();
}
}
return q.peek();
for (int num : nums) {
if (q.size() == k) {
q.poll();
}
q.add(num);
}
return q.peek();
先往堆内添加元素就大于 k 时再抛出这种写法是对的,后往堆内添加元素就在等于 k 时就抛出这种写法是错的,因为最后一个抛出的元素可能比最后一个加入的元素更符合要求
612. K个最近的点
思路
定义最大堆,元素一个接一个加入堆,堆中元素数量大于 k 时抛出
错误
- Comparator 不会写
- 往堆里添加元素写成了等于 k 时抛出,然后加入元素这种形式
- 未考虑堆中元素数目不足 k 的情况,即在往 results 中添加元素时,循环的结束条件应是
while (!heap.isEmpty())
,如果用for (int i = 0; i < k; i++)
当元素数目不足 k 个时会出现空指针越界错误
545. 前K大数 II
思路
数据是动态的,用 PriorityQueue 来做
错误
- 构造方法只是用来初始化赋值的,构造方法中的变量,其他方法仍旧不能读取
- 迭代器写的不熟,应该写
while (it.hasNext())
而不是if (it.hasNext())
613. 优秀成绩
思路
分数可能重复,但 id 不会重复,每个学生有多个分数,以 id 为键,学生所有的分数构成的 PriorityQueue 为值组成 HashMap,每个学生 id 对应一个 PriorityQueue
错误
Map.Entry使用不熟练
575. 表达式展开
思路
- 用递归,对于当前层递归首先分四部分考虑左括号、右括号、数字、以及其他,递归的好处是可以体现由大化小的能力,坏处是当嵌套太深时会栈溢出
然后每一部分又要分别考虑此部分是在方括号内还是方括号外,括号内的话仅仅加入字符串记录下来即可,将字符串传入下一层递归处理,括号外则需要进行处理
错误
右括号处理完后要使number = 0;
subString = "";
- 用栈
从最左边的右括号开始展开
错误
a. stack中的元素类型应为Object
类型,如果设为String
类型在执行Integer count = (Integer) stack.pop();
会出现String
无法转化为Integer
的错误
b. 先有stack.push(Integer.valueOf(number));
然后执行Integer count = (Integer) stack.pop();
才不会报错;先将number
转化为Integer
类型
两根指针
607. 两数之和-数据结构设计
思路
需要考虑到 3 + 3 = 6 时两个 3 到底是不是同一个数
注意
- map 和 list 的初值用构造器赋值
- list 中每个元素只添加一次
587. 两数之和 - 不同组成
思路
先排序,两根相向型指针,注意去重,num[left] + nums[right] < target 时
left++,nums[left] + nums[right] > target 时 right--
533. 两数和的最接近值
思路
先排序,两根相向型指针,不用考虑去重,num[left] + nums[right] < target 时 left++,nums[left] + nums[right] > target 时 right--,同时要注意 differ 的更新
443. 两数之和 II
思路
当找到第一个和大于 target 的 left 和 right 后,所有 left 到 right 之间的数全部都符合条件
610. 两数和 - 差等于目标值
思路
需要新建 Pair 类(index 和 num),按照 num 大小排序后还能得到原来的下标,找到满足调条件的两个数,同时注意 index 的大小关系
错误
细节错误还是很多
- target 为负数没注意
- 对 j 的越界判断
- Comparator 的书写
521.去除重复元素
思路
- 先对数组排序,用两根指针,len 指向当前最后一个不重复的数,i 遍历整个数组
- 用 HashSet 去重,用
for (int num : arrays)
遍历 Set 集合 arrays
604. 滑动窗口内数的和
思路
添一个删一个维持当前 sum[i] 为 k 个元素之和
625. 数组划分II
思路
- 快速切分两次,每次两根指针
- 快速切分一次,每次三根指针
bugfree
链表与数组
599. 向循环有序链表插入节点
思路
考虑四种情况:
- 链表为空
- 链表只有一个结点
- 链表有多个结点,但结点值全部相同
- 链表有多个结点,结点值不同
错误
链表有多个值不同的结点时要考虑到比如往30->50->2->2->3->5->7->9->11->20
总插入结点 2 这种在部分值相等结点中插入同样值的结点
prev.val < current.val && prev.val <= x && current.val >= x
判断条件要加上等于
165. 合并两个排序链表
思路
当模板背下来
注意
&&
别写成 ||
102. 带环链表
思路
- 笨方法用 hash 表,遍历链表将表内未出现的元素加入表中,如果当前元素在表内出现过说明有环
- 快慢指针,慢的一步,快的两步
bugfree
98. 链表排序
思路
归并排序
先找中点,按中点位置分成左右两部分,分别排序,然后合并
错误
findMedian的判断条件写错是while (fast.next != null && fast.next.next ! = null)
不是while(slow != null && fast != null)
快指针跑到终点,慢指针跑到一半距离
注意
先sortList(median.next)
,然后median.next = null
,最后sortList(head)
快速排序
a. 先找中点,按中点值大小切分成小于、等于、大于三部分,分别排序连接到一起
错误
总是忘记写head = head.next
if (head == null || head.next == null) { return head; }
如果写return null
在 head 只有一个结点时会报错
35. 翻转链表
思路
两根指针 prev 和 head,一前一后逐渐一个一个的改变指向,head 为空时,prev 正好指向原链表最后一个结点,即为翻转后链表头结点
错误
不要用 dummyNode 结点,用了等于给原链表增加了一个新的结点
450.K组翻转链表
思路
定义 reverseK 函数实现每 k 个结点翻转一次,外层调用while (curr != null) { curr = reverseK(curr, k); }
来实现整个链表的 k 组翻转;每 k 个结点翻转的实现实际上就是在链表翻转的基础上修改代码,先 for 循环 k 次找到第 k 个结点, 需要判断结点是否为空,nkPlus = nk.next,用 prev 和 current 两根指针不断翻转链表,直到 current 指向 nkPlus 为止
错误
reverseK 中链表翻转的判断条件写错,是while (current != nkPlus)
5. 第k大元素
思路
quickSelect 模板
错误
- 当
left == right
执行 swap 操作时,要记得left++
和right--
- 每一轮切分完,执行递归的判断条件记不清楚
if (start + k - 1 <= right) {
return quickSelect(nums, start, right, k);
} else if (start + k - 1 >= left) {
return quickSelect(nums, left, end, k - (left - start));
} else {
return pivot; // 也可以写成 return nums[right + 1]
}
486. 合并k个排序数组
思路
将各行的第一个元素加入 PriorityQueue 构成的最小堆,将抛出元素的值加入 results,然后将该元素所在行的下一元素加入最小堆
错误
- Comparator 写的不够熟
- 要对每一行进行越界和是否为空集判断
-
int index = 0;
定义时要定义在 while 外面,否则一直都是在对 results[0] 反复赋值
471. 最高频的K个单词
思路
统计单词出现次数,定义一个按词频(词频相同时按词典序排列)的堆,堆中元素不足 k 个时一直加入,达到 k 个时要和堆顶元素比较,将更符合条件的元素留在堆顶
错误
在往堆内添加单词时忘记去重,每个单词应该只加入一次,这时如果用 for (int i = 0; i < words.length; i++)
就会输出多个一样的单词,应该用代码 for (String word : hash.keySet())
494. 双队列实现栈
思路
将 queue1 中元素除最后一个全部加入 queue2,,,
返回最后一个元素同时将其加入 queue2,然后交换 queue1 和 queue2,即为 top() 操作;
将最后一个元素删除,然后交换 queue1 和 queue2 即为 pop() 操作;
621. 最大子序列
思路
用 queue 来存储最小的前缀和的下标,连续序列长度为 k1 ~ k2,则从当前 i 开始,连续序列的第一个下标的值应在 i-k2 ~ i-k1 之间,所以要对 queue 中元素进行更新,当 i 增加时,如果 i-k1 大于queue 中第一个元素则删除 queue 的第一个元素;
另一点是我们要的前缀和是最小,则在往queue 中加入新的下标时,该下标对应的前缀和应比 queue 中的最后一个小,因为对于当前 i 如果连续序列在满足要求的长度范围时,我们要使和最大则需找到最小的前缀和,那这样往队列里添加更大的前缀和根本不起作用,因为当 sum[queue.getLast() - 1] < sum[queue.getLast()]
时 sum[i] - sum[queue.getLast() - 1] > sum[i] - sum[queue.getLast()]
, 这样往 queue 中加入后面的下标明显不起任何作用,所以我们要让 queue 中下标按对应 sum 的大小降序排列
错误
- Queue 是用 LinkedList 实现,但LinkedList 的好多功能 Queue 接口并不具备,比如本题用的 getFirst() 和 getLast() 功能,所以本题队列声明应该这样写
LinkedList
queue = new LinkedList<>();
而不是
Queue
queue = new LinkedList<>(); - 在 queue.removeLast() 时要用 while 循环,用 if 会报错
Depth First Search
211. 字符串置换
思路
- 两个字符转换成字母数组,分别排序(Java 默认的排序是快排),for 循环一一对比
错误
要注意先判断字符串长度是否相等,不相等的情况比如 A 为空串 B 为 'a',如果未判断长度是否相等,就对比会导致跳过 for 循环return true
- 建立一个整数数组,统计字符串中字母出现的次数,对于字符串 A,字母每出现一次加一,对于字符串B,字母每出现一次减一,最后如果整数数组中元素值都为 0,说明 A 和 B 可以相互转换
190.下一个排列II
思路
首先应该先理解 字典序,从右往左找到第一个小于它右边相邻数的数,该数即为需要交换的位置 Index1,该数右边位置必然都是降序排列,从最右边开始寻找第一个大于该数的数的位置 Index2,交换两数,此时得到新的序列必然排在原序列之后,此时要明确从 Index1 往后的所有数全是降序排列,此时序列即为以Index1及其左边的所有字母开头的序列中权重最大的,从 Index1 + 1 翻转,则得到以 Index1 及其左边的所有字母开头的序列中权重最小的序列,即为原序列下一个排序
错误
边界书写不仔细,数组越界
52.下一个排列
思路
和上题思路一样,只是多了需要返回数组
注意
找到 index 和 biggerIndex 后记得 break
51. 上一个排列
思路
和下一个排列过程正好反过来,从左往右寻找,第一个值大于其右边相邻位置值的 index,然后从 index + 1 开始往右寻找第一个值小于 nums[index] 的位置,交换两个位置的值,然后从 index + 1 开始反转,即得到上一个排序
错误
忘记了 ArrayList 读取用 ArrayList.get(),ArrayList 修改用 ArrayList.set()
10.字符串的不同排列
思路
将当前字符串转化为字符数组按字母从小到大排序,再转化为字符串,此时字符串为字典序中权值最小的值,不断得到下一个排序加入 results,直到得到最后一个排序为止
注意
最后一个排序时 return null 作为结束标志
错误
- String.valueOf(); 在进行字符串转化时有个坑
- 两次寻找下标的操作不要忘记 break
Breadth First Search
433. 岛屿的个数
思路
- for循环 + bst + inBound
for 循环遍历布尔数组中每一个点,对于值为 true (不是 1 )的点进行 bst,将 bst 中找到的所有相连的值为 true 的点布尔值全部置为 false,每 bst 一次 count 加1,inBound 函数用于检查坐标是否越界以及当前坐标对应的布尔值是否为 false
错误
a. 数组是布尔数组,里面的 0 和 1 代表的是 false 和 true,不是数字,不是数字,不是数字,重要事情加粗说三遍
b. 本题不是分层遍历,所以不需要在 while (!queue.isEmpty())
后面加 for (int i = 0; i < size; i++);
- 并查集
还不会
69. 二叉树的层次遍历
思路
典型 bfs 分层遍历模板,要写熟
242. 将二叉树按照层级转化为链表
思路
二叉树的分层遍历,只不过新建一个新的 dummy 结点来记录链表
618.搜索图中结点
思路
图的最短路径
错误
应在队列抛出结点的同时判断结点是否是要找的结点,如果放到for 循环中对邻接表中的点进行判断则当图只有一个点时会报错
598. 僵尸矩阵
思路
for 循环统计人数并将僵尸的坐标全部加入队列,然后就是典型的 bfs 分层遍历,层数即天数,需要说明的是矩阵中的所有的僵尸位置就是 bfs 第一层
错误
- for 循环矩阵对矩阵中每一个僵尸位置进行了 bfs,比如从位置 A 开始 bfs,周围所有 People 全部变成了 Zombie,已经计算好了 days,但退出当前 bfs 后,因为周围原本 People 的位置都变成了 Zombie,for 循环会继续进入新出现的 Zombie 位置继续 bfs,虽然周围的点都已经是 Zombie,但 days 仍旧在增加,days本来不应该增加却增加了,这样就会出现错误
- 必须要在 people 大小为 0 时退出 bfs 并
return days
,否则也会出现上述错误
611. 骑士的最短路线
思路
典型的 bfs 分层遍历
错误
布尔数组里面的 0 和 1 代表的是 true 和 false,布尔数组里面的 0 和 1 代表的是 true 和 false,布尔数组里面的 0 和 1 代表的是 true 和 false,重要事情加粗再说三遍
531.六度问题
思路
经典的 bfs 分层遍历模板
bugfree
605.序列重构
思路
127.拓扑排序
思路
统计每个结点入度,把度为 0 的结点加入队列,然后对队列抛出的结点加入结果同时对该结点相邻结点进行 bfs,遍历到的结点的度减一,如果度等于 0,则加入队列
Binary Tree & Divide Conquer
597. 具有最大平均数的子树
思路
用分治法 + ResultType,定义ResultType来记录当前结点的sum 和 size
错误
细节错误,再写一遍
596. 最小子树
思路
- 全局变量(minSum, minRoot) + 分治
bugfree - ResultType + 分治
思路
定义ResultType(node, minSum, sum) + 分治
错误
没搞清 sum 实际上是用于计算叶子结点的 minSum 的,以及当前result 结点的 minSum、node 更新的判断条件是if (leftResult.minSum <= result.minSum)
和if (rightResult.minSum <= result.minSum)
480. 二叉树的所有路径
思路
- 分治,将当前结点加到左子树的每条路径和右子树的每条路径上
错误
root 为空以及 root 为叶子结点时处理写错 - 遍历
两种方法还是手写不太熟练
97. 二叉树的最大深度
思路
- 分治,计算左子树高度,计算右子树高度,两者最大值 + 1即为二叉树高度
bugfree
- 遍历
方法不会写
遍历要加全局变量
93. 平衡二叉树
思路
分治 + ResultType,根据平衡二叉树定义,左右子树有一个不平衡则二叉树不平衡,左右子树高度差大于一则二叉树不平衡
错误
判断左右子树是否平衡时,|| 写成了 &&分治不加 ResultType
不会写
66. 二叉树的前序遍历
- 分治 + 递归
- 遍历 + 递归
- 非递归 + 栈(几乎必考)
思路
先将当前结点加入栈,当栈非空时抛出结点 node,结点值加入results,当 node.right 不为空时加入栈, node.left 不为空时加入栈,注意加入顺序是先加右儿子再左儿子
67. 二叉树的中序遍历
- 分治 + 递归
- 遍历 + 递归
- 非递归 + 栈 (几乎必考)
思路
从根结点开始一直向左查找,在向左查找过程中将查找过的结点全部加入栈,直到找到最左边的结点,此时该结点值为二叉树的中序遍历的起始结点,结点值加入 results ,判断该结点右儿子是否存在,不存在则说明是该结点是二叉树最左的叶子结点,若存在则对以右儿子为根结点的二叉树执行重复操作,右儿子为根结点的二叉树的最左结点是这个二叉树中序遍历的第二结点,依次类推,理解中序遍历非递归做法的关键是理解不断递归寻找最左结点的过程
68.二叉树的后序遍历
- 分治 + 递归
- 遍历 + 递归
- 非递归 + 栈(几乎必考)
思路
在链接中写的很详细了,比较难记,要多看几遍,背下来
小结:
二叉树的前、中、后序三种遍历递归方式的两种解法除了需要按照遍历顺序调整下左儿子、当前结点、右儿子的代码顺序,其它几乎一样;重点在于非递归写法,死记硬背也要记下来
453. 将二叉树拆成链表
思路
88. 最近公共祖先
思路
首先搞清最近公共祖先定义,结点自己本身也是自己的祖先;
分治法,分别在左子树和右子树中寻找两个结点的公共祖先,左子树和右子树都存在则返回 root,右子树为空则 return left
,左子树为空则 return right
错误
异常情况写错,题目已经说明两个结点在树中存在,需要考虑下 root == null || root == A || root == B
情况
如果不存在 return null
474. 最近公共祖先 II
思路
因为有 parent 指针,比较好做,从当前结点 A 和 B 向上查找,找到从当前结点 A 和 B 到根结点的完整路线记录在 ArrayList 中,然后去从两个数组的最后一个元素 (根结点) 开始一一对比,找到最后一个相同的元素即为最近公共祖先
错误
while (indexA >= 0 && indexB >= 0) {
if (pathA.get(indexA) != pathB.get(indexB)) {
CA = pathA.get(indexA + 1);
}
indexA--;
indexB--;
}
这么写当二叉树是那种单线情况,比如 {1,#,2,#,3,#,4,#,5}
寻找 3 和 5 的LCA 时,CA 会一直保持在 null 不更新,正确写法如下:
while (indexA >= 0 && indexB >= 0) {
if (pathA.get(indexA) != pathB.get(indexB)) {
break;
}
lowestAncestor = pathA.get(indexA);
indexA--;
indexB--;
}
578. 最近公共祖先 III
思路
总体思路和 88. 最近公共祖先 相似,不同之处在于本题两个结点 A 和 B 不一定在树中出现,所以需要额外定义 ResultType 来记录 A 和 B 结点是否存在
错误
- 思路有点问题,确定完 A 和 B 结点的存在性后,确定在以 root 为根结点的二叉树中 A 和 B的公共祖先,分 5 种情形:
- A 和 B 中一个是 root 结点
- A 和 B 分别在 root 的左右子树
- A 和 B 同时在 root 的左子树
- A 和 B 同时在 root 的右子树
- A 和 B 不在 root 为根结点的二叉树中
- root 为空时,不是return true, return false
if (root == null) {
return new ResultType(false, false, root);
}
95. 验证二叉查找树
思路
- 分治法 + ResultType(以当前结点为根结点的子树是否平衡,子树最大值和最小值)
- 遍历 + 全局变量
不会
155. 二叉树的最小深度
思路
- 分治
错误
忘记考虑二叉树只有一个分支的特殊情形,比如{1,#,2,3}
- 遍历
思路
一边遍历一边记录当前深度,遍历到叶子结点后和全局变量进行比较,如果当前深度小于全局最小深度则更新它
错误
全局变量初始值不要写0,否则没办法更新,应该写成private int depth = Integer.MAX_VALUE;
595. 二叉树最长连续序列
思路
- 分治 + ResultType(两个变量 1. 从当前根结点出发的最长连续序列长度; 2. 当前根结点构成的二叉树的所有一子树的各个连续序列中最长的那个)
- 分治 + 遍历(全局变量)
614.二叉树的最长连续子序列 II
思路
分治 + ResultType(up 和 down)
从根结点出发向左寻找最大的 up 或 down,向右寻找最大的 up 或 down,只会出现两种情形:
- 左右两边一个为 up 另一个为 down,此时 max_length = up + down + 1;
- 左右两边同时为 down 或者 同时为 up,此时 max_length = up + down + 1; 其中 up 或 down 有一个值为 0 未更新
错误
细节上 && 写成 || 的小错误
619. 二叉树的最长连续子序列III
思路
做法同上题类似,只是把二叉树换成了多叉树,所以要把 if 条件句换成 for (MultiTreeNode node : root.children)
+ if 条件句
378.将二叉查找树转换成双链表
思路
分治 + ResultType(当前结点为根结点的二叉树转换成双链表的头尾结点)
因为是二叉查找树,分别将左子树和右子树转换成双链表,然后按顺序和当前结点连起来,然后返回链表头结点
注意
- 要考虑左右子树为空的情形
- 链表是双向链表,所以在连接时要记得连完 next 指针要反过来连 prev
448. 二叉树查找树中序后继
思路
理解中序遍历寻找下一个结点的 3 种情况是解题关键
475. 二叉树的最大路径和 II
思路
分治
错误
审题不仔细,路径可以在二叉树任意结点结束,所以合并代码要这么写 int sum = Math.max(0, Math.max(left, right)) + root.val;
Binary Search 除了414和617全部完成
459. 排序数组中最接近元素
思路
在数组中二分寻找 index (A[index] >= target)
,因为 target
不一定在数组中,可能小于 A[0]
,可能大于 A[A.length - 1]
,也可能不是整数在A[start]
和 A[end]
之间,所以要对 3 种情况做特判
458. 目标最后位置
bugfree
28. 搜索二维矩阵
思路
- 先对行进行二分,再对列进行二分查找
- 由于本题的矩阵特性,可将二维数组转换成一维数组来进行二分查找
错误
没记住对于mid在二维数组中坐标为x = mid / row, y = mid % col
585. 山脉序列中的最大值
思路
比较 nums[mid] 和 nums[mid + 1] 大小,来判断当前 mid 是在上升段还是下降段
错误
- 不熟时比较容易同时写
if (nums[mid] > nums[mid - 1]) start = mid;
和if (nums[mid] < nums[mid + 1]) end = mid;
这种在处于峰值时会死循环的错误 - 忘记要求返回的是值而不是下标
447. 在大数组中查找
思路
仍旧是寻找排序数组中目标数的第一个位置,多了用倍增方法来比较确定右边界下标的操作
注意
读取操作为reader.get(index)
159. 寻找旋转排序数组中的最小值
思路
首先需要明确 数组中没有重复元素 不然没办法用二分法来做,以 A[A.length - 1] 为 target,将旋转排序数组分为两段,判断 mid 出现在哪个段,不断二分缩小范围最后锁定在两个分割点上
bugfree
160. 寻找旋转排序数组中的最小值 II
思路
因为数组中可能 存在重复元素,所以当是特殊情况比如数组是 111101111 的情况下,用二分法其实是没办法判断到底在哪一段,此时只能用 for 循环遍历的方法来求 min,需要注意的是 min 初值设定可以设为 nums[0]
63. 搜索旋转排序数组 II
思路
同样是只能用 for 循环遍历,原因同上
75. 寻找峰值
思路
需要考虑四种情况:谷底、峰顶、上升段、下降段
但用if (A[mid] < A[mid + 1])
做判断条件后可把谷底并入上升段,峰顶并入下降段,和这个 585. 山脉序列中的最大值 思路类似
bugfree
74. 第一个错误的代码版本
思路
寻找排序数组 target 出现的第一个位置的变形题
bugfree
62. 搜索旋转排序数组
思路
- 笨方法是二分法两次,第一次找到旋转排序数组最小值下标
index,下标 0 到 下标 index - 1 和下标 index 到下标 end 分别是段从小到大排序的数组,判断好 target 在哪一段,直接用二分法
查找
错误
忘记 index - 1 在 index 为 0 时会越界,要加上index > 0
做判断条件 - 二分法一次
错误
忘记没找到结果时应返回 -1
462. 目标出现总和
思路
在升序数组找到目标数最后一次出现的 lastIndex,和第一次出现的 firstIndex,count = lastIndex - firstIndex + 1
bugfree
14. 二分查找
思路
二分法模板,找到第一个位置
bugfree
460. 在排序数组中找最接近的K个数
思路
先寻找最接近 target 的下标 index, 以 index 和 index - 1 为起始点在不越界的情况下,按照题目要求(将最接近 target 和 接近程度相同时的较小数排在前面)将两边元素不断加入大小为 k 的数组
错误
思路不清晰,往结果中加入元素写不太清楚
414. 两个整数相除
61. 搜索区间
思路
找到第一个和最后一个位置,放到 results 数组中,老套的二分法模板
bugfree
38. 搜索二维矩阵 II
思路
矩阵满足行从小到大顺序,列从小到大顺序,且无重复元素,从左下角或者右上角开始执行,一次排除一行或者一列
错误
写得不够熟
600. 包裹黑色像素点的最小矩形
思路
从给定点出发分别用二分法向上下找到行的边界,向左右找到列的边界
area = (row2 - row1 + 1) * (col2 - col1 + 1);
如何寻找边界需要明确:
寻找行边界时,要对每一行判断是否存在黑色像素点
寻找列边界时,要对每一列判断是否存在黑色像素点
错误
数组是字符二维数组,所以数组中的 0 和 1 是字符,要加单引号,如果不加会在检查行列是否包含黑色像素时报错
当然本题思路仍旧不够清晰
457. 经典二分查找问题
思路
二分法寻找第一个位置或最后一个位置的模板都可以
bugfree
141. x的平方根
思路
如果 x < 1
, return 0
,否则从 0 到 x 进行二分
注意各个变量需要定义为 long,否则 mid * mid 会溢出
错误
因为取整是向下取,所以需要 return (int)start;
586. 对x开根II
思路
首先要判断 x 是否大于 1,若 x 小于 1 则 end = 1,若 x 大于 1 则 end = x;
其次二分法退出条件是 end - start > eps
其中 eps 的精度要看要求
错误
小错误
617.最大平均值子数组
437. 书籍复印
思路
对每个人工作量进行二分,计算在当前工作量下所需要的人数,在人数满足小于等于 k 的情况下,使每个人的工作量尽可能地小
错误
在书写 countCopier 函数时容易忽略下面错误
错误写法
int sum = 0;
int copier = 1;
for (int i = 0; i < pages.length; i++) {
sum += pages[i];
if (sum > limit) {
copier++;
sum = 0;
}
}
return copier;
正确写法
int sum = pages[0];
int copier = 1;
for (int i = 1; i < pages.length; i++) {
if (sum + pages[i] > limit) {
copier++;
sum = 0;
}
sum += pages[i];
}
return copier;
错误在于 当 sum + pages[i] > limit
时, pages[i]
的工作量本该计给下一个人,而错误写法把 page[i]
计给上一个人了
183.木材加工
思路
对木头长度进行二分,计算当前长度 length 下可以切割的木头个数,在保证当前木头个数大于等于 k 的情况下,使得每段木头长度 length 尽可能长;计算木头个数 count += L[i] / length;
;本题和上题思路类似
bugefree
strStr(全部完成)
13.strStr
思路
常规的两层循环一一对比
错误
异常情况写错,只需考虑 source 或 target 为空情况,return -1;
594.strStr II
思路
m = target.length()
,求出 target 的哈希码,求出每 m 位的哈希码,如果出现某 m 位的哈希码和 target 的哈希码相等,则需要调用 substring 函数和 target 字母逐位比较判断是否相等
错误
- hashcode 计算 写的不够熟练
- power 的计算不能忘记取模
17. Subsets
思路
递归 + 回溯,每一次递归只往list中加一个元素,定义startIndex
来标记每一次递归的添加元素位置
错误
- 异常情况中输入是空集情况应该返回
[[]]
; - 输出子集有顺序要求,先排序
18. Subsets II
思路
比上题多了一个去重的过程,用选代表的方式增加一个判别条件
if (i != 0 && nums[i - 1] == nums[i] && i > startIndex)
错误
进入下一轮递归是help (nums, i + 1, list, results)
而不是help(nums, startIndex, list, results)
15. Permutations
思路
排列问题和组合问题区别:不需要startIndex
错误
- 去除重复添加操作
if (list.contains(nums[i])) {continue;}
要写在添加操作list.add(nums[i])
的前面 - 输入为
[]
时,输出为[[]]
16. Permutations II
思路
本题的去重条件if (visited[i] == 1 || (i != 0 && nums[i - 1] == nums[i] && visited[i - 1] == 0))
中visited[i] == 1
实际上就相当于全排列的判断条件if (list.contains(nums[i]))
, 而后面的(i != 0 && nums[i - 1] == nums[i] && visited[i - 1] == 0)
实际上是在除下列情况:比如数组 [1, 2, 2] 第一个 2 用 A 表示,第二个 2 用 B 表示,我们在 [1, A, B] 和 [1, B, A] 两个相同排序中选取典型的[1, A, B]做代表,所以当某一次递归出现 [1, B] 开头的排序,下一个递归中只有 A 未被添加,但我们通过判断条件(i != 0 && nums[i - 1] == nums[i] && visited[i - 1] == 0)
continue 掉 A
错误
去重不会