文章目录
前言
需要开通vip的题目暂时跳过
笔记导航
点击链接可跳转到所有刷题笔记的导航链接
622. 设计循环队列
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
-
MyCircularQueue(k): 构造器,设置队列长度为 k 。
-
Front: 从队首获取元素。如果队列为空,返回 -1 。
-
Rear: 获取队尾元素。如果队列为空,返回 -1 。
-
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
-
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
-
isEmpty(): 检查循环队列是否为空。
-
isFull(): 检查循环队列是否已满。
-
解答
class MyCircularQueue { Node head; Node tail; public MyCircularQueue(int k) { this.head = new Node(-1); Node p = head; k--; while(k > 0){ p.next = new Node(-1); p = p.next; k--; } p.next = head; tail = head; } public boolean enQueue(int value) { if(tail.next == head)return false; if(tail.val == -1){ tail.val = value; return true; } tail = tail.next; tail.val = value; return true; } public boolean deQueue() { if(head.val != -1){ if(head == tail){ head.val = -1; }else{ head.val = -1; head = head.next; } return true; } return false; } public int Front() { return head.val; } public int Rear() { return tail.val; } public boolean isEmpty() { return head.val == -1; } public boolean isFull() { return tail.next == head; } class Node{ int val; Node next; public Node(int val){ this.val = val; } } }
-
分析
- head指向队头,tail指向队尾
- 若head.val == -1 说明队空
- tail的后继指向head说明队满
- 进队的时候,若tail.val == -1 只需要修改tail的值,否则的话 tail要移动到它的后继 再修改值
- 出队的时候,若head == tail 那么只需要修改hea的值为-1。否则的话 head需要移动到它的后继
-
提交结果
623. 在二叉树中增加一行
给定一个二叉树,根节点为第1层,深度为 1。在其第 d 层追加一行值为 v 的节点。
添加规则:给定一个深度值 d (正整数),针对深度为 d-1 层的每一非空节点 N,为 N 创建两个值为 v 的左子树和右子树。
将 N 原先的左子树,连接为新节点 v 的左子树;将 N 原先的右子树,连接为新节点 v 的右子树。
如果 d 的值为 1,深度 d - 1 不存在,则创建一个新的根节点 v,原先的整棵树将作为 v 的左子树。
-
解答
public TreeNode addOneRow(TreeNode root, int v, int d) { List<TreeNode> list = new ArrayList<>(); list.add(root); int depth = 1; if(d == 1){ TreeNode h = new TreeNode(v); h.left = root; return h; } while(!list.isEmpty()){ List<TreeNode> temp = new ArrayList<>(); for(TreeNode node:list){ if(node.left != null){ temp.add(node.left); } if(node.right != null){ temp.add(node.right); } if(depth == d - 1){ TreeNode l = new TreeNode(v); TreeNode r = new TreeNode(v); l.left = node.left; r.right = node.right; node.left = l; node.right = r; } } list = temp; depth++; } return root; }
-
分析
- 若是在第一层添加,那么直接新建一个值为v的结点h,左孩子指向root 返回h
- 否则就是层次遍历,找到第d层,进行插入值为v的结点。
-
提交结果
628. 三个数的最大乘积
给定一个整型数组,在数组中找出由三个数组成的最大乘积,并输出这个乘积。
-
解答
//方法1 public int maximumProduct(int[] nums) { Arrays.sort(nums); int len = nums.length; int num1 = nums[len-1] * nums[len-2] * nums[len-3]; int num2 = nums[0] * nums[1] * nums[len-1]; return Math.max(num1,num2); } //方法2 public int maximumProduct(int[] nums) { if (nums.length < 3) { return 0; } int min1 = Integer.MAX_VALUE, min2 = Integer.MAX_VALUE; int max1 = Integer.MIN_VALUE, max2 = Integer.MIN_VALUE, max3 = Integer.MIN_VALUE; for (int n : nums) { if (n < min1) { min2 = min1; min1 = n; } else if (n < min2) { min2 = n; } if (n > max1) { max3 = max2; max2 = max1; max1 = n; } else if (n > max2) { max3 = max2; max2 = n; } else if (n > max3) { max3 = n; } } return Math.max(min1 * min2 * max1, max1 * max2 * max3); }
-
分析
- 方法1 排序
- 两种情况存在最大值,一种是排序后最后三个数字的乘积
- 第二种是,最小的两位数是负数,负负等正,再✖️上最后一位数字
- 比较上面两种情况的大小,返回较大者
- 方法2 一次遍历
- 上面两种情况,实际上只需要用5个数字,所以一次遍历找到这5个数字即可。
-
提交结果
方法1
方法2
629. K个逆序对数组
给出两个整数 n 和 k,找出所有包含从 1 到 n 的数字,且恰好拥有 k 个逆序对的不同的数组的个数。
逆序对的定义如下:对于数组的第i个和第 j个元素,如果满i < j且 a[i] > a[j],则其为一个逆序对;否则不是。
由于答案可能很大,只需要返回 答案 mod 109 + 7 的值。
-
解答
public int kInversePairs(int n, int k) { int[][] dp = new int[n + 1][k + 1]; int M = 1000000007; for (int i = 1; i <= n; i++) { for (int j = 0; j <= k && j <= i * (i - 1) / 2; j++) { if (i == 1 && j == 0) { dp[i][j] = 1; break; } else if (j == 0) dp[i][j] = 1; else { int val = (dp[i - 1][j] + M - ((j - i) >= 0 ? dp[i - 1][j - i] : 0)) % M; dp[i][j] = (dp[i][j - 1] + val) % M; } } } return dp[n][k]; }
-
分析
- dp[i] [j] 表示1-i 的数字的排列组合 恰好包含j个逆序对的个数。
- 考虑状态转移,i+1这个数字比1-i都要大,若i+1放在最后,那么逆序对个数不变,若i+1放到倒数第二个位置,那么逆序对+1,i+1放到倒数第三个位置,那么逆序对+2
- 所以状态转移方程就出来了
- dp[i] [j] = dp[i-1] [j] + dp[i-1] [j-1] + … + dp[i-1] [j-i+1]
- dp[i] [j] 与dp[i-1] [j]相比较,可以得到
- dp[i] [j] - dp[i - 1] [j] = dp[i] [j-1] - dp[i-1] [j-i] => dp[i] [j] = dp[i - 1] [j] + dp[i] [j-1] - dp[i - 1] [j-i]
- 边界条件dp[i] [0] = 1
- dp[n] [k] 就是1-n的数字组合 k个逆序对 的组合数
-
提交结果
630. 课程表 III
这里有 n 门不同的在线课程,他们按从 1 到 n 编号。每一门课程有一定的持续上课时间(课程时间)t 以及关闭时间第 d 天。一门课要持续学习 t 天直到第 d 天时要完成,你将会从第 1 天开始。
给出 n 个在线课程用 (t, d) 对表示。你的任务是找出最多可以修几门课。
-
解答
public int scheduleCourse(int[][] courses) { Arrays.sort(courses,(a,b)->{ return a[1] - b[1]; }); PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>(){ public int compare(int[] o1,int[] o2){ return o2[0] - o1[0]; } }); int time = 0; for(int[] course:courses){ if(time + course[0] <= course[1]){ queue.offer(course); time += course[0]; }else{ if(!queue.isEmpty() && queue.peek()[0] > course[0]){ time += course[0] - queue.poll()[0]; queue.offer(course); } } } return queue.size(); }
-
分析
- 根据结束时间升序排序。
- 利用大顶堆,根据课程的持续时间降序排序。
- 遍历排序后的数组,若当前时间加上持续时间小于等于当前的结束时间,说明可以完成这个课程。
- 那么就记录下上完这个课的累积时间time
- 若不满足上面的条件,说明前面的累积时间太长了,加上现在这门课的时间,会超过关闭的时间,
- 所以要想办法让累积时间变短。
- 这也就是大顶堆的作用,记录下上过的课的课时。
- 若当前时间加上当前这门课的时长超过了 这门课的关闭时间,从大顶堆中选择一个课时最长的课取出,若该课的课时大于当前遍历的这门课的课时,几更新累积市场,并将当前遍历的课放入大顶堆当中。
- 这样累积的时间就变短了,有利于上更多的课。
- 最后返回大顶堆的大小即可。
-
提交结果
632. 最小区间
你有 k 个 非递减排列 的整数列表。找到一个 最小 区间,使得 k 个列表中的每个列表至少有一个数包含在其中。
我们定义如果 b-a < d-c 或者在 b-a == d-c 时 a < c,则区间 [a,b] 比 [c,d] 小。
-
解答
public int[] smallestRange(List<List<Integer>> nums) { List<int[]> list = new ArrayList<>(); int k = nums.size(); for(int i = 0;i < k;i++){ List<Integer> l = nums.get(i); for(int num : l){ list.add(new int[]{num,i}); } } Collections.sort(list, Comparator.comparingInt(o -> o[0])); int[] res = new int[2]; int[] count = new int[k]; int temp = 0; int j = 0; for(int i = 0;i < list.size();i++){ if(count[list.get(i)[1]]++ == 0) temp++; if(temp == k){ while(count[list.get(j)[1]] > 1)count[list.get(j++)[1]]--; if((res[0] == 0 && res[1] == 0) || res[1] - res[0] > list.get(i)[0] - list.get(j)[0]) res = new int[]{list.get(j)[0],list.get(i)[0]}; } } return res; }
-
分析
- 将所有的数字升序排序,并标记处它来自第几组列表。
- list中记录的一个元素表示 数值以及对应的来自第几组列表。
- count数组用来统计各个列表中数字出现的次数。
- res数组记录答案区间。
- 遍历list
- 若当前count中记录的这一组数字出现的次数为0,那么temp++,并且count中这一组数字出现的次数也+1
- 倘若temp等于k 说明所有组中的数字都已经出现在了滑动窗口中
- 此时倘若滑动窗口左侧对应的某一组的数字出现了不止1次,那么左侧窗口右移动,这样就是缩小区间,
- 同时计算最小的区间,记录在res当中
-
提交结果
633. 平方数之和
给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a2 + b2 = c 。
-
解答
//方法1 public boolean judgeSquareSum(int c) { for (long a = 0; a * a <= c; a++) { double b = Math.sqrt(c - a * a); if (b == (int) b) return true; } return false; } //方法2 public boolean judgeSquareSum(int c) { int left = 0; int right = (int) Math.floor(Math.sqrt(c)); while(left <= right) { int temp = left * left + right * right; if(temp > c) { right--; }else if(temp < c) { left++; }else { break; } } if(left <= right) { return true; }else { return false; } }
-
分析
- 方法1
- 假设a的平方和是其中之一,那么另一个数字的平方和就是 c - a*a
- 若c - a * a 的开方是一个整数,那么就返回true
- 方法2
- 双指针法
-
提交结果
方法1
方法2
636. 函数的独占时间
给出一个非抢占单线程CPU的 n 个函数运行日志,找到函数的独占时间。
每个函数都有一个唯一的 Id,从 0 到 n-1,函数可能会递归调用或者被其他函数调用。
日志是具有以下格式的字符串:function_id:start_or_end:timestamp。例如:“0:start:0” 表示函数 0 从 0 时刻开始运行。“0🔚0” 表示函数 0 在 0 时刻结束。
函数的独占时间定义是在该方法中花费的时间,调用其他函数花费的时间不算该函数的独占时间。你需要根据函数的 Id 有序地返回每个函数的独占时间。
-
解答
public int[] exclusiveTime(int n, List<String> logs) { int[] res = new int[n]; Stack<String> stack = new Stack<>(); int lastTime = 0; for(int i = 0;i < logs.size();i++){ String curLog = logs.get(i); String[] curLogStrs = curLog.split(":"); if(stack.isEmpty()){ stack.push(curLog); lastTime = Integer.valueOf(curLogStrs[2]); continue; } if(curLogStrs[1].equals("end")){ String lastLog = stack.pop(); String[] lastLogStrs = lastLog.split(":"); res[Integer.valueOf(curLogStrs[0])] += Integer.valueOf(curLogStrs[2]) - lastTime + 1; }else{ String lastLog = stack.peek(); String[] lastLogStrs = lastLog.split(":"); res[Integer.valueOf(lastLogStrs[0])] += Integer.valueOf(curLogStrs[2]) - lastTime -1; stack.push(curLog); } lastTime = Integer.valueOf(curLogStrs[2]); } return res; }
-
分析
- 利用栈来模拟函数调用
- 当前栈空,记录下时间,入栈 继续遍历
- 当前日志表示结束 那么就取出栈顶元素,当前函数的id的运行时间 更新
- 当前日志表示开始,那么看栈顶的日志,得到栈顶函数的id,更新运行时间。
-
提交结果
637. 二叉树的层平均值
-
解答
public List<Double> averageOfLevels(TreeNode root) { List<Double> res = new ArrayList<>(); List<TreeNode> list = new ArrayList<>(); list.add(root); while(!list.isEmpty()){ List<TreeNode> temp = new ArrayList<>(); double sum = 0; int size = list.size(); for(TreeNode node:list){ sum += node.val; if(node.left!=null)temp.add(node.left); if(node.right!=null)temp.add(node.right); } res.add(sum/size); list = temp; } return res; }
-
分析
- 层次遍历,得到每层的和,然后求平均值。
-
提交结果
638. 大礼包
在LeetCode商店中, 有许多在售的物品。
然而,也有一些大礼包,每个大礼包以优惠的价格捆绑销售一组物品。
现给定每个物品的价格,每个大礼包包含物品的清单,以及待购物品清单。请输出确切完成待购清单的最低花费。
每个大礼包的由一个数组中的一组数据描述,最后一个数字代表大礼包的价格,其他数字分别表示内含的其他种类物品的数量。
任意大礼包可无限次购买。
-
解答
int min = Integer.MAX_VALUE; public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) { dfs(price,special,needs,0,0); return min; } public void dfs(List<Integer> price, List<List<Integer>> special, List<Integer> needs,int needMoney,int index){ for(int j = index;j < special.size();j++){ List<Integer> list = special.get(j); List<Integer> remain = new ArrayList<>(); for(int i = 0;i < list.size()-1;i++){ if(list.get(i) > needs.get(i))break; remain.add(needs.get(i) - list.get(i) >=0 ? needs.get(i) - list.get(i) : 0); } if(remain.size() == needs.size()) dfs(price,special,remain,needMoney + list.get(list.size()-1),j); } for(int i = 0;i < price.size();i++){ needMoney += needs.get(i) * price.get(i); } min = Math.min(min,needMoney); }
-
分析
- 递归回溯实现
- dfs中第一个for循环 判断能否使用大礼包,若可以 则购买大礼包,递归
- 第二个for循环是当剩余的部分无法满足大礼包的条件,那么久逐个购买。
- 最后留下花费较少的答案。
-
提交结果
639. 解码方法 2
一条包含字母 A-Z 的消息通过以下的方式进行了编码:
除了上述的条件以外,现在加密字符串可以包含字符 '‘了,字符’'可以被当做1到9当中的任意一个数字。
给定一条包含数字和字符’*'的加密信息,请确定解码方法的总数。
同时,由于结果值可能会相当的大,所以你应当对109 + 7取模。(翻译者标注:此处取模主要是为了防止溢出)
-
解答
public int numDecodings(String s) { int M = 1000000007; long[] dp = new long[s.length() + 1]; dp[0] = 1; dp[1] = s.charAt(0) == '*' ? 9 : s.charAt(0) == '0' ? 0 : 1; for (int i = 1; i < s.length(); i++) { if (s.charAt(i) == '*') { dp[i + 1] = 9 * dp[i]; if (s.charAt(i - 1) == '1') dp[i + 1] = (dp[i + 1] + 9 * dp[i - 1]) % M; else if (s.charAt(i - 1) == '2') dp[i + 1] = (dp[i + 1] + 6 * dp[i - 1]) % M; else if (s.charAt(i - 1) == '*') dp[i + 1] = (dp[i + 1] + 15 * dp[i - 1]) % M; } else { dp[i + 1] = s.charAt(i) != '0' ? dp[i] : 0; if (s.charAt(i - 1) == '1') dp[i + 1] = (dp[i + 1] + dp[i - 1]) % M; else if (s.charAt(i - 1) == '2' && s.charAt(i) <= '6') dp[i + 1] = (dp[i + 1] + dp[i - 1]) % M; else if (s.charAt(i - 1) == '*') dp[i + 1] = (dp[i + 1] + (s.charAt(i) <= '6' ? 2 : 1) * dp[i - 1]) % M; } } return (int) dp[s.length()]; }
-
分析
-
动态规划
-
当前数字和前面得到的结果是有关联的。
-
初始条件dp[0] = 1,dp[1] = s.charAt(0) == ‘*’ ? 9 : s.charAt(0) == ‘0’ ? 0 : 1
-
从下标1开始遍历
-
若当前字符是"*" 那么dp[i + 1] = dp[i] * 9
若前一位是1 那么还要加上dp[i-1] * 9 表示 前i-1个字符 和后面两位字符 “1*” 的组合数
若前一位是2 那么还要加上dp[i-1] * 6 表示 前i-1个字符 和后面两位字符 “2*” 的组合数
若前一位是* 那么还要加上dp[i-1] * 15 表示 前i-1ge字符和后面两位字符 “**” 的组合数
-
若当前字符不是"*" 那么dp[i + 1] = s.charAt(i) != ‘0’ ? dp[i] : 0; 取决于当前字符是否是0
若前一位是1。那么还要加上dp[i-1] 表示 前i- 1个字符 和后面两位字符 "1x"的组合数
若前一位是2 并且当前位小于等于6 那么还要加上dp[i-1] 表示 前i-1个字符和后面两位 字符"2x"的组合数
若前一位是* 那么dp[i + 1] = (dp[i + 1] + (s.charAt(i) <= ‘6’ ? 2 : 1) * dp[i - 1]) % M;
-
最后返回dp数组中的最后一个数字
-
-
提交结果
640. 求解方程
求解一个给定的方程,将x以字符串"x=#value"的形式返回。该方程仅包含’+’,’ - '操作,变量 x 和其对应系数。
如果方程没有解,请返回“No solution”。
如果方程有无限解,则返回“Infinite solutions”。
如果方程中只有一个解,要保证返回值 x 是一个整数。
-
解答
public String solveEquation(String equation) { String[] strs = equation.split("="); int leftNumber = 0,rightNumber = 0,leftXNumber = 0,rightXNumber = 0; int num = 0; int flag = 0; int j = 0; for(int i = 0;i < strs[0].length();i++){ char cur = strs[0].charAt(i); if(cur >= '0' && cur <= '9'){ num = num * 10 + (cur - '0'); }else if(cur == 'x'){ if(num == 0){ if(i - 1 >= 0 && strs[0].charAt(i-1) == '0')continue; num++; } flag = 1; }else if(cur == '+' || cur == '-'){ if(j == 0){ if(flag == 1)leftXNumber += num; else leftNumber += num; }else{ if(flag == 1)leftXNumber -= num; else leftNumber -= num; } flag = 0; num = 0; j = cur == '+' ? 0 : 1; } } if(j == 0){ if(flag == 1)leftXNumber += num; else leftNumber += num; }else{ if(flag == 1)leftXNumber -= num; else leftNumber -= num; } num = 0; flag = 0; j = 0; for(int i = 0;i < strs[1].length();i++){ char cur = strs[1].charAt(i); if(cur >= '0' && cur <= '9'){ num = num * 10 + (cur - '0'); }else if(cur == 'x'){ if(num == 0){ if(i - 1 >= 0 && strs[1].charAt(i-1) == '0')continue; num++; } flag = 1; }else if(cur == '+' || cur == '-'){ if(j == 0){ if(flag == 1)rightXNumber += num; else rightNumber += num; }else{ if(flag == 1)rightXNumber -= num; else rightNumber -= num; } num = 0; flag = 0; j = cur == '+' ? 0 : 1; } } if(j == 0){ if(flag == 1)rightXNumber += num; else rightNumber += num; }else{ if(flag == 1)rightXNumber -= num; else rightNumber -= num; } int XNumber = leftXNumber - rightXNumber; int Number = rightNumber - leftNumber; if(XNumber == 0 && Number != 0)return "No solution"; else if(XNumber == 0 && Number == 0)return "Infinite solutions"; else if(XNumber != 0 && Number == 0)return "x=0"; else return "x=" + (Number / XNumber); }
-
分析
- 将等式分成左右两边
- 分别统计左边的x的数量和纯数字 以及右边的x的数量和纯数字
- 然后就可以求x了
- 注意"0x"的情况,所以加了这一行代码 if(i - 1 >= 0 && strs[1].charAt(i-1) == ‘0’)continue;
-
提交结果