1.无需开会的工作日
题目描述:输入工作日多少天(days)输入二维数组表示,第几天到第几天开会(距离[[1,3],[2,4]]),第一天第三天连续开会,第二天到第四天连续开会。
最终输出总工作日内有几天不开会的日子
举例 输入 4 [[1,3],[2,4]]
输出 0
分析:合并连续重复区间即可。重复区间定义 nums[i][1] >=nums[i+1][0]
然后 days - (nums[i][1] - nums[i][0] + 1)即可
反思:这道题思路比较简单,我做的时候有几个点导致了耗时较长
1.使用一个list保存合并后的不连续空间&&数组需要及时更新为最新的连续区间
list的更新我在一开始就没想清楚,导致list更新存在问题
list的更新该是怎样的呢?
a.当存在连续重复区间时,需要先合并区间,更新nums[i]为最新的连续区间
b.更新list的最后一个元素,如果最后一个元素区间和当前元素区间一致,则不必更新
2.数组遍历的问题
数组遍历由于要和下一个元素进行比较,因此最终在遍历时将最后一个元素单独处理,如果最后一个元素时独立区间,则更新到list当中,如果不是,则不更新,这里通过一个boolean变量控制,具体看代码即可。
3.区间的时间计算异常
区间的start应该是两个区间的最小值
区间的end应该是两个区间的最大值
总结:
这道题比较简单,在list的更新这部分确实耗费了较多时间,以及对于问题的抽象能力做的也比较差,后续需要注意对于数据结构的更新维护的处理,以及快速将问题抽象出来,并细化每一个小步骤的实现细节。
2.两数之和
**题目描述:**数组中,存在唯一一个结果等于两个元素相加,输出这两个元素的index
例如:输入 9 [1,3,6] 输出[1,2]
分析:最简单的就是暴力遍历,但是时间复杂度就成了O(n)2,因此需要使用如下数据结构存储数据值HashMap<Integer, ArrayList> map
key存放数组元素值, value存放数组元素下标值(这样的话即便元素重复也不影响)
遍历数组,计算当前位置的target,如果map.get值不为空,则判断一下list.size是否大于1,如果大于1,返回list的所有值,如果为1,则返回当前元素下标和list的元素即可
反思:这题一开始忽略了多个相同值的元素的场景处理,因此一开始设计key value都存int值,导致出错,后续在考虑到上述场景后,对于map的设计思考占用了一点时间。
3.两数相加
题目描述:输入两个单向链表,从左向右加,输出最终的链表即可
分析:这题也比较简单,关注进位即可,进位只可能会为0和1,及时更新进位即可
反思:这道题在我这里的问题点如下
1.链表遍历过程中,对于l1 l2其中一个不为空的情况我的处理一开始不太好
2.哨兵节点的处理做的不好,正确的哨兵节点应该是
创建res = node = new ListNode
后续更新时仅更新node.next
最后返回 res.next即可。
4.三个线程循环打印
题目描述:三个线程循环按序打印0-99
分析:这题主要考研对于锁的理解和使用,wait和notify函数正确使用即可
反思:
1.锁的使用不能直接使用this,因为在runnable中 this实际上是当前的runnable对象
2.锁的代码块应该在循环内部,而非外部,否则会出现死锁
5.二分查找
**题目描述:**有序数组,二分查找指定元素
输入nums有序数组,target目标值
**分析:**左右指针 left和right分别指向数组左右两端,定义中间元素mid = (left + right) / 2;
如果nums[mid] < target, 递归调用 left = mid+ 1,(右半侧) 如果nums[mid] > target ,递归调用 right = mid - 1;当 left > right return false;
反思:
这题做了好多次了,主要是临界条件和递归设置注意点
临界条件:nums[mid] == target return true;left > right return false;
递归设置,见前文分析
6.归并排序
题目描述:
归排
分析
1.思路:
a.拆:将数组递归拆分为左右数组,直至原子化,
b.合:将左右数组进行比较,左数组是有序数组,右数组是无序数组,遍历比较两个数组(一定可以遍历完其中某个数组,因为左有序,右无序)
接着将左右数组未遍历部分填充至temp数组对应部分
最后将temp数组copy给原数组即可
2.时间复杂度
O(nlogn)
3.空间复杂度O(n)
反思:
1.在进行拆数组的时候递归终止条件设置的有问题 left >= right,而不是left > right,当left == right的时候,此时已经是原子区间了,理应停止递归
7、搜索二维矩阵
描述:二维数组,行内元素递增,第二行第一个元素始终大于前一行左后一个元素,请输出target是否存在于二维数组中
思路:
先是将每行看做一维数组,进行二分查找,在mid行 如果target < nums[mid][0] 则向左遍历
如果target > nums[mid][nums[mid].length-1] 则向右遍历
如果在这之间则对行进行二分查找即可
反思:这题比较简单 主要是前期二分查找写的判断条件错了,浪费了时间
换水问题II
描述:参考leetcode描述
思路:理解题意后就很好做,这个确实没啥好讲的
反思:无
三数之和
思路:双指针,先对数组排序 然后遍历数组,定义左右指针,三个数相加 >0 右指针左移 三数相加 < 0 左指针右移, 等于0,则记录结果,同时左右移动双指针,并进行去重,跳过左右指针所有相等的元素,当然 第一个数的去重也是必要的
反思:1、这题一开始想复杂了 认为是三指针 所以浪费了很长时间
2、去重 需要考虑清楚怎么做,去重分为两部分 一部分是第一个数的去重 第二部分是找到答案后后两个数的去重
有效括号
思路:使用栈保存括号 新来括号如果和栈顶括号是一对 则移除栈顶括号 否则 加入新来括号
最后判断stack是否为空即可
39.组合总和
思路:回溯+剪枝,1、先画出状态树 2、构造参数,参数构造要义:a.返回结果是什么 b.回溯阶段中间状态如何描述 3、写出终止条件 4、回溯 5、恢复现场 6、剪枝
这题按照这个流程来做的话, 参数内容,List path 描述当前递归路径中的有效元素,List<List> res,int index 描述当前递归层遍历从哪里开始(避免再次遍历已经搜寻过得前面元素点),int number 描述当前元素减去的剩余值
要点:1、res中需要过滤掉重复结果值 2、第一层遍历中 对于已经遍历过的元素 跳过
3、添加 回溯 移除 剪枝 这个可以理解为一个原子操作,需要一起执行(比如if语句内 add 回溯 语句外然后remove 这样是不合理的)
反思:这个题做了很久主要是自己对于回溯的思想重构了 因此很拧巴 以后做题前 严格按照上述的流程去做,应该会好一些,还有就是对于回溯中的中间状态参数的理解和处理做的不太好
单词拆分
思路:动态规划 dp[n]表示当前位置元素在字典中能否是否有效(能被拆成x个单词)
递推公式:dp[n] = (dp[i] + s(n-i)) || (dp[j] + s(n-j))
当前位置字符串有效 = 当前位置前所有有效字符串 + n-j(剩余字符串是否有效)
反思:这题浪费时间较多的地方主要是stringbuilder 的delete和append操作不熟练导致,这点需要加强 特别强调 append(char[], int offset, int len) 第二个参数是,从当前哪个位置开始 len是代表截取多少个元素,这个很重要 delete(0, sb.length())其实就相当于clear操作
螺旋矩阵:
思路:分为四个动作,右下左上,在某个动作区间分为两种情况,继续沿着这个方向,还是掉头进入另一个方向,判断条件为(是否到达外部极值端以及继续沿着该方向的部分是否使用过,未到达极值端或者继续该方向的元素未曾使用,则继续沿着该方向 否则 掉头)
注意事项:使用递归实现,但是递归完成后记得return 否则会加入多余元素
搜索二维矩阵
思路:根据矩阵特性,可以从左下或者右上进行,遍历,始终保证左右子节点的有序性
特殊处理一维特性类型数组 还有就是边界条件设置注意(这次就是这个吃了大亏了!!!)
最大子数组和
思路:动态规划,dp[n]表示当前位置的最大和
dp[0] = nums[0]
dp[n] = dp[n-1] > 0 ? dp[n-1] + nums[n] : nums[n];
return max.dp[n]
合并区间
思路:对二维数组进行排序,campatar.compartingInt(这里是个lamda表达式 a->a[0])
对a数组的第0号位置元素比较,也就是行第一个元素比较
剩下的就比较简单了
创建一个Node 缓存所有有效结果值
进行比较 如果重叠 则计算新的重叠区间即可 然后写入nodeList
轮转数组
思路:使用temp缓存 剩下的就不用再讲了
除自身以外数组的乘积
思路:准备两个数组 一个从左往右遍历 缓存该位置元素左边所有元素 另一个从右往左遍历 缓存该位置元素右边所有元素乘积 最后进行遍历 左右数组相乘即可
over!
缺失的第一个正数
思路:遍历当前数组,如果当前数组的取值范围内的所有正数都出现了,那么return nums.length+1 否则 反馈当前数组中第一个没被标记的元素位置
注意:我对于list遍历与在遍历中修改不是很熟悉
找到字符串中的所有字符异位词
思路:由于输入的所有的都是小写字母,因此可以使用count[]数组缓存所有小写字符出现的频率,然后与模板pCount通过Arrays.equals判断是否相同
先初始化判断第一个窗口,然后再通过滑动窗口将字符串s的字符进行滑动,每滑动一次判断一次,如果和pCount相等,将左侧写入res即可
两两交换链表中的节点
思路:关键是前驱节点的使用(很重要),每次交换 前驱节点的next指向交换后的位置,前驱节点本身指向first位置
这个写起来比较简单,就是理解前驱节点的用法和思想很重要
k个一组翻转链表
思路:和两两交换链表中的节点一样,这次不同的是使用list缓存node,然后进行交换需要注意prev节点 prev.next节点的运用(prev始终指向本轮循环排序后的最后一个元素 prev.next始终指向本轮排序后的头节点)
二叉树的中序遍历
思路:
递归遍历即可,根节点执行add操作
前序:根左右 中序:左根右 后序:左右根
二叉树的最大深度
思路:层序遍历,构建两个队列queue和temp,queue存储当前层级节点,temp存储下一层级节点,完成一层遍历则计数器加一,然后将下一层级数据放到当前层级queue当中并clear下一层级
queue的方法有哪些:
offer == add 这都是添加元素函数 添加在队列尾部
remove == poll 这都是移除队列首部的元素
翻转二叉树:
思路:将根节点的左右节点交换即可 然后递归遍历所有的节点
对称二叉树
思路:1、构造队列,比较队列首位元素是否一致
2、递归,比较两个左右节点是否一致,当都是空的时候 return true(代表不可递归了) 当有一个为空 则return false 接着就是比较值(递归是个很好的解法 就是这里递归返回值的判断设计确实比较高明!)