目录
DAY1
1:两数之和
思路:哈希表
用哈希表存放数组nums里的元素和它对应的索引值,每遍历一个元素就去哈希表李查找是否有满足与它相加和为target的值,若有的话就返回两个key对应的value(索引);若没有的话将其添加至哈希表中继续遍历。
class Solution {
public int[] twoSum(int[] nums, int target) {
HashMap<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
if(map.containsKey(target - nums[i]))
return new int[]{map.get(target - nums[i]), i}; //get是获取key的value值,即nums[i]对应的索引值
map.put(nums[i], i);
}
return new int[0];
}
}
2:两数相加
思路:如果两个链表长短不一,给短的前面补0。新链表节点 = l1.val + l2.val + 进位。然后得到的和求出进位和进位之后剩下的数,将个位数放进新链表中。
如果最后一位加完之后还有进位,则需要为它单独创建一个val为1的节点。
tips:新建链表的话一定要设置一个pre指向新链表的头节点,这样防止在创建的时候头节点移动导致最后无法输出整个链表。
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode pre = new ListNode(0); //指向新列表的头节点
ListNode cur = pre;
int c = 0; //进位
while(l1 != null || l2 != null){
//如果两个链表长短不一,短的可以补0
int a = l1 == null ? 0 : l1.val;
int b = l2 == null ? 0 : l2.val;
int sum = a + b + c;
c = sum / 10;
sum %= 10; //进位后留下个位数
cur.next = new ListNode(sum);
//相加的两个链表和新链表都要移动
cur = cur.next;
if(l1 != null) l1 = l1.next;
if(l2 != null) l2 = l2.next;
}
if(c == 1) cur.next = new ListNode(c); //如果相加到最后还有进位,则需要新建一个val为1的节点
return pre.next;
}
}
3:无重复字符的最长子串
同剑指offer48:最长不含重复字符的子字符串
https://blog.youkuaiyun.com/weixin_45876739/article/details/125970441?spm=1001.2014.3001.5502
- 定义一个 map 数据结构存储 (k, v),其中 key 值为字符,value 值为字符位置 +1,加 1 表示从字符位置后一个才开始不重复
- 我们定义不重复子串的开始位置为 start,结束位置为 end
- 随着 end 不断遍历向后,会遇到与 [start, end] 区间内字符相同的情况,此时将字符作为 key 值,获取其 value 值,并更新 start,此时 [start, end] 区间内不存在重复字符
- 无论是否更新 start,都会更新其 map 数据结构和结果 ans。
作者:画手大鹏
链接:https://leetcode.cn/leetbook/read/illustrate-lcof/e2wq47/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
DAY2
5:最长回文子串
思路:中心扩展算法。回文串有两种形式:奇数和偶数,所以需要分别以i,i开始和i,i+1开始,判断是哪种(哪个长度长就是哪个),每循环一次更新start和end的位置,这里需要注意:
- 对于奇数串移动start和end位置时,len/2和(len-1)/2的结果是一样
- 对于偶数串,i 指向的是相同字符前面的,所以start 的位置是(len-1)/2,end位置是len/2
class Solution {
public String longestPalindrome(String s) {
if(s == null || s.length() < 1) return "";
int start = 0, end = 0;
for(int i = 0; i < s.length(); i++){
int len1 = expandAroundCenter(s, i, i); //回文串形如aba,起始是单个字符
int len2 = expandAroundCenter(s, i, i + 1); //回文串形如dbbd,起始是两个相同字符
int len = Math.max(len1, len2);
if(len > end - start){
//改变start和end位置需要格外注意
start = i - (len - 1) / 2; //除法向下取整
end = i + len / 2;
//当起始是一个字符时len/2的结果是相同的,
//当是两个字符的时候i是前面的,所以start需要i减去(len-1)/2
}
}
return s.substring(start, end + 1);
}
int expandAroundCenter(String s, int left, int right){
while(left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)){
left--;
right++;
}
return right - left - 1; //返回子串长度
}
}
DAY3
11:盛最多水的容器(太秒了 多看这道题)
思路:双指针。如果向内移动短板,水槽的短板 min(h[i], h[j])可能变大,所以水面积可能变大;如果向内移动长板,水槽的短板 min(h[i], h[j])可能变小或不变,面积一定变小。所以循环过程中只移动短板。res保存最大值。
class Solution {
public int maxArea(int[] height) {
int i = 0, j = height.length - 1;
int res = 0;
while(i < j){
//移动短板
res = height[i] < height[j] ?
Math.max(res, (j - i) * height[i++]) :
Math.max(res, (j - i) * height[j--]); //这一步简直是神来之笔
}
return res;
}
}
15:三数之和
思路:排序,双指针。排序后k从头开始遍历到nums[k]=0,大于0之后和一定不为0。对我而言本题中最难的是跳过重复的数字。
- sum>0,移动指针 j ,减小sum值
- sum<0,移动指针 i ,增大sum值
- 等于的时候将三个数以数组形式存入res中,跳过重复的元素
while(i<j&&nums[i]==nums[++i]) 等价于 i++; while(i<j&&nums[i-1]==nums[i]) i++
将i++之后要判断这个数和他前面的是不是一样的,如果是一样的 那么我们i继续后移一位,然后再判断,一直到不同为止。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums); //先排序
List<List<Integer>> res = new ArrayList<>();
for(int k = 0; k < nums.length - 2; k++){ //i,j在k的右面,所以是-2
if(nums[k] > 0) break;
//已经将 nums[k - 1] 的所有组合加入到结果中,本次双指针搜索只会得到重复组合
if( k > 0 && nums[k] == nums[k - 1]) continue;
int i = k + 1, j = nums.length - 1;
while(i < j){
int sum = nums[i] + nums[j] + nums[k];
if(sum < 0) while(i < j && nums[i] == nums[++i]); //跳过重复的数字
else if(sum > 0) while(i < j && nums[j] == nums[--j]);
else{
res.add(new ArrayList<Integer>(Arrays.asList(nums[k], nums[i], nums[j])));
while(i < j && nums[i] == nums[++i]);
while(i < j && nums[j] == nums[--j]);
}
}
}
return res;
}
}
DAY4
53:最大子数组和
思路:动态规划。我们要判断的是当以nums[ i ]结尾的时候连续最大和数组。而当nums[ i - 1 ]小于等于0的时候,那么答案就是nums[ i ]这个数。(这里之际二用nums动态存储了前面的最大和,所以可以直接比较)。
其实这里用nums[ i ]小于0的话对nums[ i + 1 ]产生负反馈更好理解,此时就从nums[ i + 1 ]开始作为结果数组的首元素继续判断。
同剑指offer42
class Solution {
public int maxSubArray(int[] nums) {
int res = nums[0];
for(int i = 1; i < nums.length; i++){
nums[i] += Math.max(nums[i - 1], 0); //判断i-1是正反馈还是负反馈
res = Math.max(res, nums[i]); //如果是负反馈,那么从i开始重新为和最大数组的首元素
}
return res;
}
}
20:有效的括号
思路:栈。遇到左括号就在栈里放入该对应的右括号,如果按顺序遍历到的括号与pop出的元素相等那就是正确的。
class Solution {
public boolean isValid(String s) {
if(s.isEmpty()) return true;
Stack<Character> stack = new Stack<>();
for(char c : s.toCharArray()){
if(c == '(') stack.push(')');
else if(c == '{') stack.push('}');
else if(c == '[') stack.push(']');
else if(stack.empty() || c != stack.pop()) return false; //pop出的就是该有的闭括号
}
if(stack.empty()) return true;
return false;
}
}
70:爬楼梯
思路:动态规划。斐波那契函数。
同剑指offer10:青蛙跳台阶
class Solution {
public int climbStairs(int n) {
if(n == 1 || n == 2) return n;
int a = 0, b = 0, f = 1;
for(int i = 0; i < n; i++){
a = b;
b = f;
f = a + b;
}
return f;
}
}
DAY5
21:合并两个有序链表
同剑指offer25:https://blog.youkuaiyun.com/weixin_45876739/article/details/125698816
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode head = new ListNode(0), cur = head;
while(list1 != null && list2 != null){
if(list1.val <= list2.val){
cur.next = list1;
list1 = list1.next;
}else{
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
cur.next = list1 != null ? list1 : list2;
return head.next;
}
}
DAY6
22:括号生成
思路:回溯算法(参考题解:笨猪爆破组)。首先下一个添加的是左括号还是右括号需要分情况判断
- 当剩余的左括号>0时可以一直选左括号
- 当剩余左括号<右括号时才可以选右括号,不然就是非法的
一共有n对,所以最后生成的每组长度应该是n*2
回溯过程
class Solution {
List<String> res = new ArrayList<>();
public List<String> generateParenthesis(int n) {
dfs(n, n, "", n * 2);
return res;
}
public void dfs(int left, int right, String str, int len){
//left和right分别表示剩余左右括号的个数
if(len == str.length()){
res.add(str);
return;
}
if(left > 0) dfs(left - 1, right, str+"(", len);
if(right > left) dfs(left, right - 1, str+")", len); //右括号剩余的多才能选它
}
}
DAY7
215:数组中的第k个最大元素
思路:大顶堆和小顶堆,但不知道为什么我的这么慢。
//大顶堆 56ms
class Solution {
public int findKthLargest(int[] nums, int k) {
//大顶堆
PriorityQueue<Integer> heap = new PriorityQueue<>((x, y) -> (y - x));
for(int n :nums)
heap.offer(n);
for(int i = 0; i < k - 1; i++) //弹出前k-1个数字,最后顶部就是第k个最大元素
heap.poll();
return heap.peek();
}
}
//小顶堆 39ms
class Solution {
public int findKthLargest(int[] nums, int k) {
//小顶堆,堆顶是最小元素
PriorityQueue<Integer> heap = new PriorityQueue<>();
int i = 0;
for(; i < k; i++)
heap.add(nums[i]);
for(; i < nums.length; i++)
if(heap.peek() < nums[i]){
heap.poll();
heap.add(nums[i]);
}
return heap.peek();
}
}
DAY8
206:反转链表
思路:pre指向原链表中的cur的前一个节点,暂存cur.next,然后将cur的next指向pre,再将pre移动到cur,cur移动到刚刚暂存的next里即可。
最后cur指向了null,所以需要输出的是pre。
class Solution {
public ListNode reverseList(ListNode head) {
ListNode cur = head, pre = null;
while(cur != null){
ListNode temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
}
45:跳跃游戏-Ⅱ
思路:贪心算法。每走到一个元素,判断它可以移动到的范围,设置一个边界。maxPosition存放在边界范围内可以跳出的最大位置(也就是在比较上一个数的跳跃范围内,可以跳到下一个更远的位置最大是多少)。
class Solution {
public int jump(int[] nums) {
int end = 0, maxPosition = 0, steps = 0;
for(int i = 0; i < nums.length - 1; i++){ //注意这里边界,最后一个不用判断
maxPosition = Math.max(maxPosition, i + nums[i]); //边界就是索引值+该索引对应的值
if(i == end){ //走到边界就更新
end = maxPosition;
steps++;
}
}
return steps;
}
}
DAY9
104:二叉树的深度
思路:就一个简单的递归,然后计数。
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) return 0;
else{
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return Math.max(left, right) + 1;
}
}
}
121:买股票的最佳时机
思路:动态规划。同剑指offer63.
注意:minprices要保证第一步将第一个数无论大小都要存为min,所以应该设置为Integer.MAX_VALUE。
还有if里的语句顺序不能颠倒(弱智错误,颠倒了压根儿没存min)
class Solution {
public int maxProfit(int[] prices) {
int minprices = Integer.MAX_VALUE, maxprofit = 0;
for(int i = 0; i < prices.length; i++){
if(prices[i] < minprices) minprices = prices[i];
else if(prices[i] - minprices > maxprofit)
maxprofit = prices[i] - minprices;
}
return maxprofit;
}
}
136:只出现一次的数字
思路:异或。这两个字,深深敲击我的心。只能说,妙到家了。
class Solution {
public int singleNumber(int[] nums) {
//异或!!!
int res = nums[0]; //如果数组中只有一个数字直接输出即可
for(int i = 1; i < nums.length; i++){
res = res ^ nums[i];
}
return res;
}
}
DAY10
94:二叉树的中序遍历
思路一:递归,很简单。
思路二:迭代,用栈模拟中序遍历(先进后出)。先遍历左子树,将每个遍历过的节点放入栈中,当没有左孩子的时候弹出本身,然后遍历右子树。好理解但是不好写,多看看。
//思路一
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<Integer>();
dfs(list, root);
return list;
}
void dfs(List<Integer> list, TreeNode root){
if(root == null) return;
dfs(list, root.left);
list.add(root.val);
dfs(list, root.right);
}
}
//思路二
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
while(stack.size() > 0 || root != null){ //注意循环条件
if(root != null){ //判断节点是否有左孩子,一直往左下走
stack.add(root);
root = root.left;
}else{
TreeNode temp = stack.pop();
res.add(temp.val); //最后输出的是数组
root = temp.right; //左遍历完输出本身后判断右节点
}
}
return res;
}
}
101:对称的二叉树
思路:递归。同剑指offer28。
- 当左右孩子都为空则是对称的;
- 当只有左孩子或右孩子或左右两个节点值不相等则不对称
- 判断左右孩子的孩子是否对称。
class Solution {
public boolean isSymmetric(TreeNode root) {
return root == null ? true : check(root.left, root.right);
}
public boolean check(TreeNode left, TreeNode right){
if(left == null && right == null) return true;
else if(left == null || right == null || left.val != right.val) return false;
return check(left.left, right.right) && check(left.right, right.left);
}
}
DAY11
226:反转二叉树
思路:递归。万恶的递归啊真的看到这么简单的代码,自己半天p都憋不出来。
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null) return null;
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
invertTree(root.left);
invertTree(root.right);
return root;
}
}
534:二叉树的直径
思路:递归。递归计算左右子树的深度,每次比较直径和当前最大值选出最大的。注意diameter函数里调用dfs,但需要的返回值是max。而dfs函数则需要返回节点深度。
class Solution {
int max = 0;
public int diameterOfBinaryTree(TreeNode root) {
dfs(root);
return max;
}
int dfs(TreeNode root){
if(root == null) return 0;
int left = dfs(root.left);
int right = dfs(root.right);
max = Math.max(left + right, max); //最大直径和当前最大值比较取大的
return Math.max(left, right) + 1; //返回节点深度
}
}
DAY12
69:x的平方根
思路:二分查找。解答里的牛顿迭代法我实在是看不懂....这个好理解一些。
- 若这个整数的平方=输入整数,则这个整数就是平方根
- 若这个整数平方>输入整数,则这个整数一定不是它的平方根
- 若这个整数<输入整数,则需要可能是它的平方根(例如8的平方根2.8取的是2不是3)
所以我们需要对最后一种情况进行判断。
class Solution {
public int mySqrt(int x) {
if(x == 0 || x == 1) return x;
int left = 1;
int right = x / 2;
while(left < right){
int mid = left + (right - left + 1) / 2;
if(mid > x / mid){ // 相当于判断mid的平方是否大于x,但乘法可能越界
right = mid - 1; //在前半部分搜索 不-1可能会越界
}else{
left = mid;
}
}
return left;
}
}
539:最小时间差(大疆笔试)
思路:这题太诡异了。列表里存储的时分,但是在表盘上可能存在指针左右两面夹角相同的,所以用数组存储当天的秒和下一天的秒(过程比较复杂)。然后将得到的秒数(就看做时间戳好理解)排序,然后选出相差最小的即为答案。(这是人类想出来的题目吗???)
class Solution {
public int findMinDifference(List<String> timePoints) {
int n = timePoints.size() * 2; //记录每天和下一天改时间点的偏移量
int[] nums = new int[n];
for(int i = 0, index = 0; i < n / 2; i++, index += 2){
String[] str = timePoints.get(i).split(":");
int hour = Integer.parseInt(str[0]), min = Integer.parseInt(str[1]);
nums[index] = hour * 60 + min;
nums[index + 1] = nums[index] +1440; //加一天24h的秒数
}
Arrays.sort(nums);
int res = nums[1] - nums[0];
for(int i = 0; i < n - 1; i++)
res = Math.min(res, nums[i + 1] - nums[i]);
return res;
}
}