前言
需要开通vip的题目暂时跳过
笔记导航
点击链接可跳转到所有刷题笔记的导航链接
721. 账户合并
给定一个列表 accounts,每个元素 accounts[i] 是一个字符串列表,其中第一个元素 accounts[i][0] 是 名称 (name),其余元素是 emails 表示该账户的邮箱地址。
现在,我们想合并这些账户。如果两个账户都有一些共同的邮箱地址,则两个账户必定属于同一个人。请注意,即使两个账户具有相同的名称,它们也可能属于不同的人,因为人们可能具有相同的名称。一个人最初可以拥有任意数量的账户,但其所有账户都具有相同的名称。
合并账户后,按以下格式返回账户:每个账户的第一个元素是名称,其余元素是按顺序排列的邮箱地址。账户本身可以以任意顺序返回。
-
解答
class Solution { public List<List<String>> accountsMerge(List<List<String>> accounts) { Map<String,Integer> email2Index = new HashMap<>(); Map<Integer,String> index2Email = new HashMap<>(); Map<String,String> email2Person = new HashMap<>(); int index = 0; for(int i = 0;i < accounts.size();i++){ List<String> curAccounts = accounts.get(i); String person = curAccounts.get(0); for(int j = 1;j < curAccounts.size();j++){ String email = curAccounts.get(j); if(!email2Index.containsKey(email)){ email2Index.put(email,index); index2Email.put(index++,email); email2Person.put(email,person); } } } UnionFind union = new UnionFind(index); for(int i = 0;i < accounts.size();i++){ List<String> curAccounts = accounts.get(i); int firstIndex = email2Index.get(curAccounts.get(1)); for(int j = 2;j < curAccounts.size();j++){ int secondIndex = email2Index.get(curAccounts.get(j)); union.union(firstIndex,secondIndex); } } int[] p = union.parent; Map<Integer,List<String>> personAllEmail = new HashMap<>(); for(int i = 0 ;i < p.length;i++){ int parentIndex = union.find(i); String email = index2Email.get(parentIndex); List<String> l = personAllEmail.getOrDefault(parentIndex,new ArrayList<>()); l.add(index2Email.get(i)); personAllEmail.put(parentIndex,l); } List<List<String>> res = new ArrayList<>(); for(Integer key : personAllEmail.keySet()){ List<String> emails = personAllEmail.get(key); Collections.sort(emails); List<String> r = new ArrayList<>(); r.add(email2Person.get(emails.get(0))); r.addAll(emails); res.add(r); } return res; } } class UnionFind { int[] parent; int[] sz; public UnionFind(int n) { parent = new int[n]; sz = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; sz[i] = 1; } } public void union(int index1, int index2) { int p = find(index1); int q = find(index2); if(sz[p] > sz[q]){ sz[p] += sz[q]; parent[q] = p; }else{ sz[q] += sz[p]; parent[p] = q; } } public int find(int index) { if (parent[index] != index) { parent[index] = find(parent[index]); } return parent[index]; } }
-
分析
- 并查集 + hash表
- 首先将email 映射成整数,方便使用并查集
- 遍历account,更新并查集
- 得到并查集后 相同父亲的email,属于同一个账户的 所以之前还需要维护一个email 对应的所属账户
- 有了相同父亲的eamil 集合 和他们对应的所属人 就可以得到结果了。
-
提交结果
722. 删除注释
给一个 C++ 程序,删除程序中的注释。这个程序source是一个数组,其中source[i]表示第i行源码。 这表示每行源码由\n分隔。
在 C++ 中有两种注释风格,行内注释和块注释。
字符串// 表示行注释,表示//和其右侧的其余字符应该被忽略。
字符串/* 表示一个块注释,它表示直到*/的下一个(非重叠)出现的所有字符都应该被忽略。(阅读顺序为从左到右)非重叠是指,字符串/*/并没有结束块注释,因为注释的结尾与开头相重叠。
第一个有效注释优先于其他注释:如果字符串//出现在块注释中会被忽略。 同样,如果字符串/*出现在行或块注释中也会被忽略。
如果一行在删除注释之后变为空字符串,那么不要输出该行。即,答案列表中的每个字符串都是非空的。
样例中没有控制字符,单引号或双引号字符。比如,source = “string s = “/* Not a comment. */”;” 不会出现在测试样例里。(此外,没有其他内容(如定义或宏)会干扰注释。)
我们保证每一个块注释最终都会被闭合, 所以在行或块注释之外的/*总是开始新的注释。
最后,隐式换行符可以通过块注释删除。 有关详细信息,请参阅下面的示例。
从源代码中删除注释后,需要以相同的格式返回源代码。
-
解答
public List<String> removeComments(String[] source) { List<String> res = new ArrayList<>(); StringBuilder sb = new StringBuilder(); boolean flag = false; for(String s:source){ for(int i = 0;i < s.length();i++){ char cur = s.charAt(i); if(!flag && cur == '/' && i + 1 < s.length() && s.charAt(i + 1) == '/'){ break; } if(!flag && cur == '/' && i + 1 < s.length() && s.charAt(i + 1) == '*'){ i++; flag = true; }else if(flag && cur == '*' && i + 1 < s.length() && s.charAt(i + 1) == '/'){ i++; flag = false; }else if(!flag){ sb.append(cur); } } if(!flag && sb.length() > 0){ res.add(sb.toString()); sb = new StringBuilder(); } } return res; }
-
分析
- 模拟过程
- flag标记是否处于/**/代码块。
- 若不在/**/代码块中,当遇到//的时候,则跳过之后的部分。
- 若处于代码块中,则忽略这一些。
- 其余的部分拼接起来。
- 每次遍历完一个字符串,就判断当前的位置是否在代码块当中,如果不是的话,则将目前得到的结果加入到答案集合中。
-
提交结果
724. 寻找数组的中心索引
给定一个整数类型的数组 nums,请编写一个能够返回数组 “中心索引” 的方法。
我们是这样定义数组 中心索引 的:数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。
如果数组不存在中心索引,那么我们应该返回 -1。如果数组有多个中心索引,那么我们应该返回最靠近左边的那一个。
-
解答
public int pivotIndex(int[] nums) { int len = nums.length; int[] preSum = new int[len + 1]; for(int i = 0;i < len;i++){ preSum[i+1] = preSum[i] + nums[i]; } for(int i = 0;i < len;i++){ if(preSum[i] == preSum[len] - preSum[i + 1]) return i; } return -1; }
-
分析
- 前缀和
- 遍历分割点,如果存在左右两边和相等的中心点,则返回
-
提交结果
725. 分隔链表
给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。
每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。
这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。
返回一个符合上述规则的链表的列表。
举例: 1->2->3->4, k = 5 // 5 结果 [ [1], [2], [3], [4], null ]
-
解答
public ListNode[] splitListToParts(ListNode r, int k) { int count = 0; ListNode p = r; while (p != null) { count++; p = p.next; } int remain = count % k; int num = count / k; ListNode[] res = new ListNode[k]; p = r; int index = 0; if (num == 0) { while (p != null) { res[index] = p; ListNode pre = p; p = p.next; pre.next = null; index++; } } else { while (remain > 0) { res[index] = p; int temp = num + 1; remain--; while (temp > 1) { p = p.next; temp--; } ListNode pre = p; p = p.next; pre.next = null; index++; } while (p != null) { res[index] = p; int temp = num; while (temp > 1) { p = p.next; temp--; } ListNode pre = p; p = p.next; pre.next = null; index++; } } return res; }
-
分析
- 因为分组之间的个数差不能大于1
- 所以先根据k求出每组最少num个,num = count / k。
- 然后求余数 remain = count % k。
- remain个分组在最少的个数的基础上+1,其余的组个数不变。
- 然后就是简单的遍历链表,切割链表了。
-
提交结果
728. 自除数
自除数 是指可以被它包含的每一位数除尽的数。
例如,128 是一个自除数,因为 128 % 1 == 0,128 % 2 == 0,128 % 8 == 0。
还有,自除数不允许包含 0 。
给定上边界和下边界数字,输出一个列表,列表的元素是边界(含边界)内所有的自除数。
-
解答
public List<Integer> selfDividingNumbers(int left, int right) { List<Integer> res = new ArrayList<>(); for(int i = left;i <= right;i++){ if(f(i)){ res.add(i); } } return res; } public boolean f(int number){ int n = number; if(number == 0)return false; while(number != 0){ int temp = number % 10; if(temp == 0 || n % temp != 0)return false; number /= 10; } return true; }
-
分析
- 遍历left~right的数字。
- 每个数字都判断是否满足自除数
- 满足的加入到集合当中。
-
提交结果
729. 我的日程安排表 I
实现一个 MyCalendar 类来存放你的日程安排。如果要添加的时间内没有其他安排,则可以存储这个新的日程安排。
MyCalendar 有一个 book(int start, int end)方法。它意味着在 start 到 end 时间内增加一个日程安排,注意,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end。
当两个日程安排有一些时间上的交叉时(例如两个日程安排都在同一时间内),就会产生重复预订。
每次调用 MyCalendar.book方法时,如果可以将日程安排成功添加到日历中而不会导致重复预订,返回 true。否则,返回 false 并且不要将该日程安排添加到日历中。
请按照以下步骤调用 MyCalendar 类: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end)
-
解答
public class MyCalendar { TreeMap<Integer, Integer> calendar; MyCalendar() { calendar = new TreeMap(); } public boolean book(int start, int end) { Integer prev = calendar.floorKey(start), next = calendar.ceilingKey(start); if ((prev == null || calendar.get(prev) <= start) && (next == null || end <= next)) { calendar.put(start, end); return true; } return false; } }
-
分析
- 利用TreeMap来维护没有重叠的线段。
- 默认是按照key 升序排列。
- 找到比start小的key,判断这个key对应的value是否小于等于start。
- 找到比start大的key,判断这个key是否大于等于end。
- 如果上面其中一个不能满足,则表示不能插入。
-
提交结果
738. 单调递增的数字
给定一个非负整数 N,找出小于或等于 N 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。
(当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。)
-
解答
public int monotoneIncreasingDigits(int N) { String res = String.valueOf(N); char[] chars = res.toCharArray(); char cur = chars[0]; boolean isdizeng = false; for(int i = 1;i < chars.length;i++){ if(chars[i] >= cur){ cur = chars[i]; isdizeng = true; }else { isdizeng = false; break; } } if(isdizeng)return Integer.valueOf(new String(chars)); cur = chars[0]; int index = 0; for(int i = 1;i < chars.length;i++){ if(chars[i] < cur) { break; }else{ cur = chars[i]; index = i; } } for(int i = index-1;i >=0 ;i--){ if(chars[i] == chars[index]){ index = i; }else break; } chars[index] = (char) (chars[index] - 1); for(int i = index + 1;i < chars.length;i++){ chars[i] = '9'; } return Integer.valueOf(new String(chars)); }
-
分析
- 首先第一个for循环判断是否是递增序列,如果是的话 直接返回结果
- 第二个for循环 找到满足递增序列的最后一位
- 第三个for循环 往前找与上一步找到的最后一位相等且最先出现的字符。
- 之后修改上一步找到的数字 -1 其余后面的数字修改为9
-
提交结果