前沿:撰写博客的目的是为了再刷时回顾和进一步完善,其次才是以教为学,所以如果有些博客写的较简陋,是为了保持进度不得已而为之,还请大家多多见谅。
预:看到题目后的思路和实现的代码。
见:参考答案展示。
感思:对比答案后的思考,与之前做过的题目是否有关联。
行:
(1)对于没做出来的题目,阅读答案后重新做一遍;
(2)下次做题可以尝试改善的方向;
(3)有助于理解的相关的题目
优先级:做题进度>学习&总结>默写回顾>做题数量
数组相关题目链接
数组基础知识
为什么使用数组?
对于同一类型数据能够一次性创建足够大的空间存储在一起,像一个班的成绩能够通过一个数组全部存储。
数组的定义:存储一堆相同数据类型在连续的地址中
- 数组参考定义:存放在连续地址上相同类型数据的集合,并且其下标从0开始。
- 连续地址存储→下标获取效率高→任意插入和删除/覆盖时效率低→链表插入删除效率高
如何使用数组?数据类型[] 名称 = new 数据类型[];数组下标从0开始
704. 二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例:
输入: nums = [-1,0,3,5,9,12], target = 9 输出: 4 解释: 9 出现在 nums中并且下标为 4
- 你可以假设
nums
中的所有元素是不重复的。n
将在[1, 10000]
之间。nums
的每个元素都将在[-9999, 9999]
之间。
预
1.阅读题目看到有序+元素不重复→二分查找
2.撰写大体思路+特殊情况
大体思路:双指针left,right,middle,while判断在哪边就往哪边偏
特殊情况:nums.length = 0,return -1;1,if判断;
class Solution {
public int search(int[] nums, int target) {
// 双指针left,right,middle,while判断在哪边就往哪边偏
// 特殊情况:nums.length = 0,return -1;1,if;
if(nums.length == 0){
return -1;
}else if(nums.length == 1){
if(nums[0] == target){
return 0;
}else{
return -1;
}
}
int left = 0;
int right = nums.length-1;
int middle;
// 左闭右闭
while(right >= left){ //是否要添加等号?
middle = (right+left)/2;
// System.out.println(middle);
if(nums[middle] < target){
left = middle+1;
}else if(nums[middle] > target){
right = middle-1;
}else{
return middle;
}
// cout << left << " " << right << endl;
}
return -1;
}
}
见
阅读答案,实现另一种思路:左闭右开
class Solution {
public int search(int[] nums, int target) {
if(nums.length == 0){
return -1;
}else if(nums.length == 1){
if(nums[0] == target){
return 0;
}else{
return -1;
}
}
int left = 0;
// 左闭右开
int right = nums.length;
int middle;
while(right > left){
middle = left+((right-left)>>1);
// middle = (right+left)/2+1;
if(nums[middle] == target){
return middle;
}else if(nums[middle] < target){
left = middle+1;
}else{
right = middle;
}
}
return -1;
}
}
感思
发现自己考虑的特殊情况不能真正起到作用,但我认为有意识地思考特殊情况是必要的。
相比参考答案,将target与两端值比大小,能更快速地排除极端的情况。
// 参考答案
if(target < nums[0] || target > nums[nums.length - 1]){
return -1;
}
左闭右开总结:
小结:
1.右开表示右边是取不到的值
right = nums.length→while (left < right)→right = middle(instead of middle+1)
2.画图>>脑子想
出错:在实现左闭右开时,脑子已经有点疲倦了,此时撰写判断条件后的right和left赋值写反了。
习惯性用脑子想判断关系,但实际上对于我来说是很容易出错的;每次遇到需要思考一下的情况,就尽量在草稿纸上写下来判断,遇到区间赋值时,则采用画区间显示化的方式。
行
有空再刷:
35.搜索插入位置
34. 在排序数组中查找元素的第一个和最后一个位置
27. 移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。提示:
0 <= nums.length <= 100
0 <= nums[i] <= 50
预
发现二刷反而出现逻辑问题没有写出来。
1.阅读题目,不要忽视提示部分,因为其会给相关范围。
2.先撰写大体思路+特殊情况
两边双指针法:假设left和right,left值等于val时,先判断right值是否等于val,等于val,right-1,直到不等于val时再往内移动一格,并交换值。
特殊情况:nums.length = 0,return 0;1,if判断;
class Solution {
public int removeElement(int[] nums, int val) {
// 假设left和right,left值等于val时,先判断right值是否等于val,等于val直接-1,直到不等于val时再往内移动一格,并交换值。
// 特殊情况:nums.length = 0,return 0;1,if判断;
if(nums.length == 0){
return 0;
}
if(nums.length == 1){
if(nums[0] == val){
return 0;
}else{
return 1;
}
}
// 两边双指针法
int left = 0;
int right = nums.length-1;
int temp;
while(right >= left){
if(nums[left] == val){
while(right > 0 && nums[right] == val){
right--;
}
if(right > left){
temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}else{
return left;
}
left++;
right--;
}else{
left++;
}
}
if(right == left && nums[right] == nums[left]){
return left;
}
return left;
}
}
见:阅读答案后,实现另一种版本:快慢指针
class Solution {
public int removeElement(int[] nums, int val) {
if(nums.length == 0){
return 0;
}
if(nums.length == 1){
if(nums[0] == val){
return 0;
}else{
return 1;
}
}
// 快慢双指针法
int slow = 0;
for(int fast = 0;fast < nums.length;fast++){
if(nums[fast] != val){
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
}
感思
移除元素:数组内存地址连续不能单独移除某元素,只能覆盖
双指针法(快慢指针法):
通过双指针法在一个for循环下完成两个for循环的工作
左右双针法实现过程中的两次报错:
1. 未考虑两边迭代时,可能出现空指针的情况
while(nums[right] == val) ,加上right > 0
2.有时会少输出一个
while(right >left):需要考虑相等的情况,否则可能出现相等时又不等于的情况时,新数组则会漏掉一个值。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/binary-search
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
二刷总结
- 懒的习惯是顽固的
- 二刷时认为题目简单,实现后出现细节问题,总想着脑子想一下修改就能出答案,结果改bug改到头晕都没写出来。
- 回顾一刷时这篇文章就有提到“画图 > 脑子想”,但二刷时又忘记了,所以还是要对所有题目都保持谨慎,出现逻辑问题,则一定要写/画出来寻找问题在哪,而不是偷懒只用脑子想。
- “大体思路+特殊情况”的思考逻辑也是被忘得一干二净,想着简单就只思考了大体思路,发现a出来也就没有去思考是否有特殊情况需要判断了。
- 要抓住主要问题,而不要困在某个细节实现中。
- 当思路可行,但实现总是有问题时,要思考我这题的关键是什么?
- 像移动元素,若以左指针作为更新指标,则要以为主要判断依据,而不是先考虑右指针的情况。
- 治标不治本快,实际上却是事倍功半→不能总想着以解决没有通过示例来完善,不然多个示例有问题,每次都得再考虑。
三刷总结
- 题目都暗示只需保证前面的数组正确和返回新长度即可→快慢指针直接覆盖
- 思维是顽固的三刷移除元素每次解题思路都选择了更困难的方式,不专注直接没不出来。
做题记录