1、两数之和
length的使用
length
length是属性,是数组 的长度,使用时的形式是:数组.length不加括号
例如:
1 2 String[] cp; int n = cpdomains.length;
length()
length()是字符串 自带的方法,是求字符串长度的,使用形式是:str.length()
size()
size()是列表 的自带方法,求的是列表的长度,使用形式是:list.size()
2. 两数相加
中等题,考察链表的操作和构造。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Solution { public ListNode addTwoNumbers (ListNode l1, ListNode l2) { ListNode root = new ListNode (); ListNode p = root; int add = 0 ; while (l1!=null || l2!=null || add!=0 ) { int k = add; if (l1!=null ) { k += l1.val; l1 = l1.next; } if (l2!=null ) { k += l2.val; l2 = l2.next; } if (k > 9 ) { k -= 10 ; add = 1 ; } else { add = 0 ; } ListNode temp = new ListNode (k); p.next = temp; p = p.next; } return root.next; } }
3. 无重复字符的最长子串
考察: #滑动窗口 #双指针 #Set
中等题。注意审题,因为 s 由英文字母、数字、符号和空格组成,所以不能使用位运算。这里使用 hashset。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public int lengthOfLongestSubstring (String s) { char [] sc = s.toCharArray(); int n = s.length(); HashSet<Character> set = new HashSet <Character>(); int i = 0 , j = i; int res = 0 ; while (i<n && j<n) { if (i == j && i < n-1 ) { set.add(sc[i]); j = i+1 ; } if (!set.contains(sc[j])) { set.add(sc[j]); res = Math.max(res, j-i+1 ); j++; } else { set.remove(sc[i]); i++; } } return res; } }
4、寻找两个正序数组的中位数
image-20220922220554936
5、最长回文子串
方法一、中心扩张法
image-20220920211209159
注意
注意出现“aaaa”、“aaa”这样相同字符的回文子串,因此需要在第一个while循环中判断从当前i开始左右是否相同。
在完成相同字符的判断后,进行left-1和right+1同时判断,查找回文数。
时间复杂度:O(n^2)
空间复杂度:O(1)
时间换空间
替换
将字符串转为数组的方式有:
1 char [] str = s.toCharArray();
提取字符串中某一下标的字符:
方法二、动态规划
image-20220920211102615
二维数组是动态规划最常用的方法
注意
此方法核心在于按照子串长度进行遍历,判断l长度的子串是否为回文子串,即判断除去两个端点的子串是否为回文子串
1中至少要有三个元素。在只有1个元素时,回文子串就是它自己;有两个元素时,如果两个元素相等则为回文子串。
时间复杂度:O(n^2)
空间复杂度:O(n^2)
11、盛最多水的容器
image-20220924130921512
逻辑性很强的题目!!
双指针,一个指向最左边,一个指向最右边,这两个指针所指的相对长的板后面称为长板,相对短的板称为短板。
对于左右指针逼近,容器的底边长度一定是在减小。
容器的盛水量取决于底边长度和短板 。
所以!如果移动短板,可能会遇到更短或者相等的板,那是不幸,容量变小;如果遇到长一些的板子,容量可能变小也可能不变也可能变大。
如果移动长板,底边长度一定减小,如果遇到的是长板,因为容器的高取决于短板,且底边变短,所以容量一定减小;如果遇到比自己短,比短板长的板子,和刚刚一样的道理,也是容量变小;如果遇到比短板还短的板子,那更不用说了,短边变成它自己了,容量一定减小。
因此!富贵险中求!如果移动长板,那容量是一定减少的!如果移动短板,有几率容量变大!
所以设置一个max值,每次移动都记录一下容量,当i与j相遇就说明遍历结束,最后返回max即可。
15、三数之和
image-20220924125048130
Listconst<List<Integer>> 的定义
1 List<List<Integer>> result = new ArrayList <List<Integer>>();
List<Integer>的定义
1 List<Integer> r = new ArrayList <Integer>();
List的添加操作
List的搜索操作
List查找某个元素是否在其中
int转为Integer
1 2 int a;Integer b = Integer.valueOf(a);
数组的排序
20. 有效的括号
考察: #栈
简单题,利用栈即可。
26、删除有序数组中的重复项
image-20220924130553781
双指针思路
31、下一个排列
image-20220929222726483
思路很强,考验找规律的能力。
例:
对于 123465:
第一步,从后往前找到第一对升序的两个数,也就是46
第二步,从结尾开始到6,找到第一个大于4的数 ,也就是5
第三步,将4和5对换位置,变成123564
第四步,将6开始到结尾变成升序,变成123546
简化:
第一步,从后往前找到第一对升序的两个数,也就是46
第二步,从6开始到结尾,升序排列,变成123456
第三步,在刚刚升序的部分找到第一个大于4的数,即5 ,将4和5对换位置,变成123546
其他:
对于已经是最大的数,即从头到尾是越来越小的,它的下一个数是最小的,也就是从头到尾是越来越大的,因此只要对数组进行排序即可。
33、搜索旋转排序数组
遇到时间复杂度O(log n),二话不说,直接二分查找 !
方法一、暴力(不满足log(n))
image-20221002171738960
方法二、二分法(不完全满足log(n))
image-20221002172037673
先求出向左移动的次数k,然后再用二分法判定,每次结果也便宜k。
方法三、二分法优化(满足log(n))
image-20221002175004110
这个方法简单又巧妙。
正常二分后,先判断mid的值是不是target,再看左边和右边哪个是顺序的。
如果nums[left] <= nums[mid],说明左边是顺序的。再看target是不是再左边区间里,如果是则right = mid-1;否则, left = mid+1,继续在右边找。
如果nums[left] > nums[mid],说明左边是不规则的,那么右边一定是顺序的。再看target是不是再右边区间里,如果是则left = mid+1;否则, right = mid-1,继续在左边找。
34、在排序数组中查找元素的第一个和最后一个位置
遇到时间复杂度O(log n),二话不说,直接二分查找 !
方法一、二分+扩张
image-20221002123506969
先找到值为target的数组的元素的位置,再向两边扩张,找到左右边界。
方法二、二分查找性质
image-20221002123926754
这种方法的思路很妙,要想找到数组中值为target的范围,只要找到数组中>=target和target+1的第一个值的下标。
例:
nums = [5,7,7,8,8,10], target = 8
先找target=8的第一个位置:
left = 0,right = 6,mid = 3,找到了一个,往左边找
left = 0,right = 3,mid = 1,小了
left = 2,right = 3,mid = 2,小了
left = 3,right = 3,mid = 3,当left==right,结束,此时left就是第一个
再找target=9的第一个位置:
left = 0,right = 6,mid = 3,小了
left = 4,right = 6,mid = 5,大了
left = 4,right = 5,mid = 4,小了
left = 5,right = 5,mid = 5,当left==right,结束,此时left就是第一个的位置
除此之外,我们还需要判断target=8的时候到底有没有这个值哦~
注意:
本题的right初始值为n,后来right更新为mid,和我们过去使用的二分法略有不同,因为如果mid就是我需要的那个值,且它就是第一个出现的,按照过去的算法,right=mid-1,最后可能会丢失了这个mid。但是新的算法一直用right保存mid,只要一直在找左区间,就需要将我们的目标范围不停往左边移动 ,就应该用right保存mid,最后一定会出现left、right、mid相等的情况,也就是左区间。
35. 搜索插入位置
考察: #二分查找
简单题,相当于找>=target 的第一个元素的下标。
39、组合总和⭐
方法一、双端队列+DFS
image-20221004214508995
image-20221004211227805
候选数组里有 2,如果找到了组合总和为 7 - 2 = 5 的所有组合,再在之前加上 2 ,就是 7 的所有组合。同理考虑 3,如果找到了组合总和为 7 - 3 = 4 的所有组合,再在之前加上 3 ,就是 7 的所有组合,依次这样找下去。
相关知识
List<List>
构造
1 List<List<Integer>> res = new ArrayList <>();
添加
1 res.add(new ArrayList <>(path));
Deque双端队列
两端都可以进出,FIFO(先进先出)
构造
1 Deque<Integer> dq = new LinkedList <>();
操作
端队列的开头移除元素。从 Queue 接口继承的方法完全等效于 Deque 方法,如下表所示:
抛出异常
特殊值
抛出异常
特殊值
插入
addFirst(e)
offerFirst(e)
addLast(e)
offerLast(e)
删除
removeFirst()
pollFirst()
removeLast()
pollLast()
检查
getFirst()
peekFirst()
getLast()
peekLast()
Deque与Queue的对照
add(e)
addLast(e)
offer(e)
offerLast(e)
remove()
removeFirst()
poll()
pollFirst()
element()
getFirst()
peek()
peekFirst()
在将双端队列用作堆栈时,元素被推入双端队列的开头并从双端队列开头弹出。堆栈方法完全等效于 Deque 方法,如下表所示:
push(e)
addFirst(e)
pop()
removeFirst()
peek()
peekFirst()
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import java.util.Deque;import java.util.LinkedList;import java.util.List;class Solution { public static void DFS (int [] candidates, int begin, int len, int target, Deque<Integer> path, List<List<Integer>> res) { if (target < 0 ) { return ; } else if (target == 0 ) { res.add(new ArrayList <>(path)); return ; } for (int i=begin;i<len;i++) { path.addLast(candidates[i]); DFS(candidates,i,len,target-candidates[i],path,res); path.removeLast(); } } public List<List<Integer>> combinationSum (int [] candidates, int target) { int len = candidates.length; List<List<Integer>> res = new ArrayList <>(); if (len==0 ) { return res; } Deque<Integer> path = new LinkedList <>(); DFS(candidates,0 ,len,target,path,res); return res; } }
方法二、剪枝叶
image-20221004211040594
在一开始就对数组进行排序,因为在分支中,如果target-candidates[i] < 0,那么这个分支之后的更大的i就更不可能了。直接break即可。
1 2 3 4 5 6 7 8 for (int i=begin;i<len;i++) { if (target-candidates[i]<0 ) { break ; } path.addLast(candidates[i]); DFS(candidates,i,len,target-candidates[i],path,res); path.removeLast(); }
40、组合总和II
image-20221004222241402
和39题是同样的思路
唯一的不同是每次begin是从下一个更大的数开始。
注意:
在剪枝中,注意要两次剪枝。
第一次剪枝和39题一样:
1 2 3 4 if (target - candidates[i] < 0 ) { break ; }
第二次剪枝是为了消除一样的结果,注意在i>begin时,candidates[i]==candidates[i-1]表示是在同一级有相同元素。
1 2 3 4 if (i>begin && candidates[i]==candidates[i-1 ]) { continue ; }
45、跳跃游戏II⭐
方法一、动态规划+数组
image-20221018221627713
原理和 55、跳跃游戏 一致,但是是利用 dp 数组记录到达下标为 i 的节点的最短步数。
遍历数组,在遍历中,对下标为i+nums[i]进行判断,步数为dp[i]+1,取最小的dp存入。最后输出dp[n-1]
方法二、优化
image-20221018221108378
例如,对于数组 [2,3,1,2,4,2,3],初始位置是下标 0,从下标 0 出发,最远可到达下标 2。下标 0 可到达的位置中,下标 1 的值是 3,从下标 1 出发可以达到更远的位置,因此第一步到达下标 1。
从下标 1 出发,最远可到达下标 4。下标 1 可到达的位置中,下标 4 的值是 4 ,从下标 4 出发可以达到更远的位置,因此第二步到达下标 4。
思想是上面那样,但是操作起来需要一些技巧:
对于上面的第一个部分0~2,虽然我们可以通过max知道谁能跳多远,但是我们还是需要走到边界end,也就是下标为2的地方,在这里我们可以更新步数+1,同时将新的边界end更新为刚刚走完的那一段中能走的最远的max。再继续上面的步骤。
代码真的很巧妙!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int jump (int [] nums) { int n = nums.length; int end = 0 ; int max_border = 0 ; int step = 0 ; for (int i=0 ;i<n-1 ;i++) { max_border = Math.max(max_border,i+nums[i]); if (i == end) { end = max_border; step++; } } return step; } }
46、全排列
image-20221008115241358
本题的思路来自31、下一个排列,找当前数组的下一个排列,对于 123465:
从后向前找到第一对升序的相邻的两个数,即46;
从6开始到结尾升序排列,变成123456;
在刚刚升序的子序列中,即56中,从左到右找到一个大于4的数,即5,与4交换,变成123546。
因此,本题的思路很简单了:
将数组升序排列
调用此方法
直到整个数组为逆序为止,也就是当i定位第一对升序组合的较小值时,已经定位到-1了,则退出。
拓展
Stream流
49. 字母异位词分组
考察: #哈希
中等题,hash 存储即可。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public List<List<String>> groupAnagrams (String[] strs) { HashMap<String,ArrayList<String>> hash = new HashMap <String,ArrayList<String>>(); for (int i=0 ;i<strs.length;i++) { char [] sc = strs[i].toCharArray(); Arrays.sort(sc); String key = new String (sc); ArrayList<String> temp = hash.getOrDefault(key,new ArrayList <String>()); temp.add(strs[i]); hash.put(key, temp); } List<List<String>> res = new ArrayList <>(); for (ArrayList<String> v : hash.values()) { res.add(v); } return res; } }
53、最大子数组和
image-20221019195142360
简单动态规划~写过很多次啦!
55、跳跃游戏
方法一、动态规划+dp数组
image-20221018222804356
利用dp数组记录某个点是否能到达,能到达记为1,随时判断下标为n-1即可。
方法二、优化——动态更新最大长度
image-20221018222935560
在能跳跃的区域内遍历,不断更新能跳跃的最大长度,如果能覆盖下标为n-1则为true。
只用遍历一遍即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public boolean canJump (int [] nums) { int n = nums.length; int max = 0 ; for (int i=0 ;i<n;i++) { if (i>max) { return false ; } if (max>=n-1 ) { return true ; } max = Math.max(max,i+nums[i]); } return false ; } }
56. 合并区间
考察: #数组 #数组排序
中等题,但是简单。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class Solution { public int [][] merge(int [][] intervals) { Arrays.sort(intervals, new Comparator <int []>() { public int compare (int [] a1, int [] a2) { if (a1[0 ]!=a2[0 ]) { return a1[0 ]-a2[0 ]; } return a1[1 ]-a2[1 ]; } }); List<List<Integer>> al = new ArrayList <>(); int start = intervals[0 ][0 ], end = intervals[0 ][1 ]; for (int i=0 ;i<intervals.length;i++) { if (intervals[i][0 ]<=end) { end = Math.max(end,intervals[i][1 ]); } else { List<Integer> temp = new ArrayList <>(); temp.add(start); temp.add(end); al.add(temp); start = intervals[i][0 ]; end = intervals[i][1 ]; } } List<Integer> temp = new ArrayList <>(); temp.add(start); temp.add(end); al.add(temp); int [][] res = new int [al.size()][2 ]; for (int i=0 ;i<al.size();i++) { res[i][0 ] = al.get(i).get(0 ); res[i][1 ] = al.get(i).get(1 ); } return res; } }
68、文本左右对齐
方法一、枚举各种情况
image-20220924200048680
_1.java文件为精简版(将很多重复的代码放入函数)
_2.java文件更容易理解
_3.java文件思路一致,但是更加精简,且易懂⭐
image-20220924202357372
70、爬楼梯
方法一、动态规划
image-20221016215036802
如果使用递归——妥妥的时间超时!!!
所以只有正着推。可以得到规律:
1 2 3 4 dp[0 ] = 1 ; dp[1 ] = 1 ; dp[i] = dp[i-1 ] + dp[i-2 ];
简单优化
image-20221016220255962
1 2 3 4 5 6 7 int a = 1 , b = 1 , temp = 0 ;for (int i=2 ;i<=n;i++) { temp = a + b; a = b; b = temp; } return b;
74. 搜索二维矩阵
中等题。两次二分查找,先查找行,再查找列。代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Solution { public boolean searchMatrix (int [][] matrix, int target) { if (target < matrix[0 ][0 ]) { return false ; } int r = findrow(matrix,target); System.out.println(r); return find(matrix[r],target); } public int findrow (int [][] matrix, int target) { int l = 0 , r = matrix.length-1 ; while (l<r) { int m = (l + r + 1 )/2 ; if (matrix[m][0 ]<=target) { l = m; } else { r = m - 1 ; } } return l; } public boolean find (int [] arr, int target) { int l = 0 , r = arr.length-1 ; while (l <= r) { int m = (l + r) / 2 ; if (arr[m]==target) { return true ; } else if (arr[m] < target) { l = m + 1 ; } else { r = m - 1 ; } } return false ; } }
78. 子集⭐⭐
考察: #回溯
题解:题解
方法一
中等题,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public List<List<Integer>> res; public List<Integer> ans; public int [] nums; public List<List<Integer>> subsets (int [] nums) { res = new ArrayList <>(); ans = new ArrayList <>(); this .nums = nums; find(0 ); return res; } public void find (int i) { if (i==nums.length) { res.add(new ArrayList <>(ans)); return ; } find(i+1 ); ans.add(nums[i]); find(i+1 ); ans.remove(ans.size()-1 ); } }
方法二、更通用的回溯⭐(框架)
思路比较抽象,需要思考。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public List<List<Integer>> res; public List<Integer> ans; public int [] nums; public List<List<Integer>> subsets (int [] nums) { res = new ArrayList <>(); ans = new ArrayList <>(); this .nums = nums; find(0 ); return res; } public void find (int i) { res.add(new ArrayList <>(ans)); for (int j=i;j<nums.length;j++) { ans.add(nums[j]); find(j+1 ); ans.remove(ans.size()-1 ); } } }
89. 格雷编码 ⭐
题解
方法一、规律
总结了如下规律:
1. 0 位格雷码有 1 个码字;1 位格雷码有 2 个码字 2. (n+1) 位格雷码中的前 2^n 个码字(前一半)等于 n 位格雷码的全部码字,按顺序书写,加前缀 0(意味着如果 n 位的所有码字已经加入 res,就不用操作了) 3. (n+1) 位格雷码中的后 2^n 个码字 (后一半)等于 n 位格雷码的全部码字按逆序书写,并加前缀 1(相当于把 n 位的所有码字逆序遍历,并加上一个头 1) 4. n+1 位格雷码的集合 = n 位格雷码集合 (顺序) 加前缀 0 + n 位格雷码集合 (逆序) 加前缀 1
详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public List<Integer> grayCode (int n) { List<Integer> res = new ArrayList <>(); res.add(0 ); if (n==0 ) { return res; } int head = 1 ; for (int i=1 ;i<=n;i++) { int l = res.size(); for (int j=l-1 ;j>=0 ;j--) { res.add(res.get(j)+head); } head = head << 1 ; } return res; } }
方法二、格雷编码
实现公式如下:
举个🌰:n=4 时,当十进制为 7,现在最高位加个 0,所以其对应的二进制位 00111,可以通过上图的例子进行演算。
从右往左推算 00111 对应的格雷码的每一位:
1 ⊕ 1 = 0
1 ⊕ 1 = 0
0 ⊕ 1 = 1
0 ⊕ 0 = 0
所以其对应的格雷码为 0100
再举个🌰:n=4 时,当十进制为 12,现在最高位加个 0,所以其对应的二进制位 01100,可以通过上图的例子进行演算。
从右往左推算 01100 对应的格雷码的每一位:
0 ⊕ 0 = 0
1 ⊕ 0 = 1
1 ⊕ 1 = 0
0 ⊕ 1 = 1
所以其对应的格雷码为 1010
不难发现,对于十进制为 i 的数,其格雷码就是 i ^ (i>>1) ,根据这个思路很容易得到代码:
1 2 3 4 5 6 7 8 9 10 11 class Solution { public List<Integer> grayCode (int n) { List<Integer> res = new ArrayList <>(); for (int i=0 ;i<(1 <<n);i++) { res.add(i ^ (i>>1 )); } return res; } }
90. 子集 II⭐
考察: #回溯
基础题/框架题:78(子集)
中等题,在 78 题的基础上多了一步去重。需要先对 nums 进行排序,将相同的聚在一起。在 find 函数中,如果遇到连续的相同的数,只取其中的第一个。因为在同一个 find 下的 for 循环内是属于同一级别的,比如:[1, 2, 2] 分解为 [1, 2] 和 [1, 2] 是一样的,同级别的相同的去重即可。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public List<List<Integer>> res = new ArrayList <>(); public List<Integer> ans = new ArrayList <>(); public int [] nums; public List<List<Integer>> subsetsWithDup (int [] nums) { Arrays.sort(nums); this .nums = nums; find(0 ); return res; } public void find (int i) { res.add(new ArrayList <>(ans)); for (int j=i;j<nums.length;j++) { if (j>i && nums[j]==nums[j-1 ]) { continue ; } ans.add(nums[j]); find(j+1 ); ans.remove(ans.size()-1 ); } } }
94. 二叉树的中序遍历
考察: #树
简单题,考察树的中序遍历,模板如下:
1 2 3 4 5 6 7 8 public void midfind (TreeNode r) { if (r == null ) { return ; } midfind(r.left); res.add(r.val); midfind(r.right); }
101. 对称二叉树😭
考察: #树
简单题,但是思路很妙。
如果一个树的左子树与右子树镜像对称,那么这个树是对称的。如果同时满足下面的条件,两个树互为镜像: - 它们的两个根结点具有相同的值 - 每个树的右子树都与另一个树的左子树镜像对称
我们可以实现这样一个递归函数,通过「同步移动」两个指针的方法来遍历这棵树,p 指针和 q 指针一开始都指向这棵树的根,随后 p 右移时,q 左移,p 左移时,q 右移。每次检查当前 p 和 q 节点的值是否相等,如果相等再判断左右子树是否对称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public boolean isSymmetric (TreeNode root) { return check(root,root); } public boolean check (TreeNode r1, TreeNode r2) { if (r1 == null && r2 == null ) { return true ; } if (r1 == null || r2 == null ) { return false ; } return r1.val == r2.val && check(r1.left, r2.right) && check(r1.right, r2.left); } }
102. 二叉树的层序遍历
简单题。但是要注意 List<List<Integer>> 对象的构造方法。
1 List<List<Integer>> list = new ArrayList <>();
104. 二叉树的最大深度
简单题,遍历即可,同步 level 层级。
118. 杨辉三角
考察: #动态数组
简单题。但是需要注意,对于同一个 arraylist 对象,如果之前已经加入了嵌套的外层 list 中,之后再改变这个对象,嵌套在里面的也会改变,类似于一个指针。
121、买卖股票的最佳时机
image-20221021185552910
思路与1014、最佳观光组合相同。
122、买卖股票的最佳时机 II
image-20221021194731667
这题很容易想复杂了,其实这题的目的就是为了始终不亏钱。所以只要是相邻两个升序的就买了就卖,一定能赚一笔!
128. 最长连续序列⭐
考察: #Set
中等题。题目要求在 \(O(n)\) 的时间内找到最长的连续序列,思路是找到每个连续序列的第一个数,然后再一直++,就能找到这个长度。那么问题是如何找到第一个数,或者说会不会出现中间的数重复判断。第一个数有一个特性是非第一个数没有的,就是 num-1 是不存在数组中。现在问题是如何快速判断一个数在不在数组中,这里选择使用 HashSet,它是 \(O(n)\) 的时间复杂度。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int longestConsecutive (int [] nums) { Set<Integer> set = new HashSet <Integer>(); int res = 0 ; for (int n : nums) { set.add(n); } for (int s : set) { if (!set.contains(s-1 )) { int k = 1 ; while (set.contains(s+1 )) { k++; s++; } res = Math.max(res, k); } } return res; } }
135、分发糖果
方法一、递归
image-20220924183620852
思想:相邻的两个值中,更大的糖果=更小的糖果+1,如果比左右都大则取其max。如果比左右都小,则糖果数为1。用数组记录数值,可以提高时间效率(如果不设置数组,则会超时)。
方法二、左右遍历
image-20220924182743805
思想:从左向右遍历一遍,设置第一个值为1,如果当前值比上一个值大,则糖果数=上一个的糖果数+1,反之将其置为1。此时从左到右的糖果大小关系已经明确。再从右往左遍历一遍,此时只需要一个temp变量记录即可,设置第一个的temp为1,如果当前值比上一个值大,则糖果数=temp+1,对于每一个小朋友都会对他从左遍历和从右遍历取最大值,也就是他最后得到的糖果值。
对于 [1,2,87,87,87,2,1]
从左向右:1,2,3,1,1,1,1
从右向左:1,1,1,1,3,2,1
综合:1,2,3,1,3,2,1
136. 只出现一次的数字
方法一、HashSet
简单题,利用 set 记录,最后遍历。注意 set 的删除为 remove()。
方法二、位运算👍
思路:元素只出现两次,而相同元素异或为 0。0 和某个只出现一次的元素异或为这个元素。代码如下:
1 2 3 4 5 6 7 8 9 class Solution { public int singleNumber (int [] nums) { int res = 0 ; for (int num : nums) { res = res ^ num; } return res; } }
152、乘积最大子数组⭐
image-20221020111954135
动态规划经典题
本题和53、最大子数组和有异曲同工之处。但是值得注意的是乘法中,两个负数相乘就会得到正数。
所以我们不能只用一个last记录以当前元素结尾的最大值,我们需要用lastmax记录当前元素结尾的最大值、用lastmin记录当前元素结尾的最小值。
最大值和最小值实时动态更新。如果当前元素<0,那么最大值就是上一个元素的最小值*nums[i]和它自身判断,最小值就是上一个元素的最大值*nums[i]和它自身判断。nums[i]>0就很简单了。
为了更广泛操作(装逼),我们可以提前计算:
1 2 int a = lastmax*nums[i];int b = lastmin*nums[i];
那么每次更新的时候只需要:
1 2 lastmax = Math.max(Math.max(a,b),nums[i]); lastmin = Math.min(Math.min(a,b),nums[i]);
其实不难理解,如果nums[i]<0,如果上一个值的max>0、min<0,那么取的是b;如果max<0、min<0,那么取的也还是b;如果max>0、min>0,那么取得还是b。但是如果nums[i]>0,那么一切都恰恰相反,都取a。所以我们只需要用a、b得最大值和它自身比较就能得到新的lastmax。
lastmin同理。
每次循环都用变量min找到最大值。
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public int maxProduct (int [] nums) { int lastmax = nums[0 ]; int lastmin = nums[0 ]; int max = nums[0 ]; for (int i=1 ;i<nums.length;i++) { int a = lastmax*nums[i]; int b = lastmin*nums[i]; lastmax = Math.max(Math.max(a,b),nums[i]); lastmin = Math.min(Math.min(a,b),nums[i]); max = Math.max(max,lastmax); } return max; } }
153. 寻找旋转排序数组中的最小值
中等题,但是很简单。代码如下:
1 2 3 4 5 6 7 8 9 10 class Solution { public int findMin (int [] nums) { for (int i=0 ;i<nums.length-1 ;i++) { if (nums[i]>nums[i+1 ]) { return nums[i+1 ]; } } return nums[0 ]; } }
189. 轮转数组
方法一、开辟额外空间
中等题。最后利用了深拷贝 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public void rotate (int [] nums, int k) { int n = nums.length; int [] res = new int [n]; k = k % n; int i = 0 ; for (int j=n-k;j<n;j++,i++) { res[i] = nums[j]; } for (int j=0 ;j<n-k;j++,i++) { res[i] = nums[j]; } System.arraycopy(res, 0 , nums, 0 , n); } }
方法二、原地翻转👍
思路:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public void rotate (int [] nums, int k) { int n = nums.length; k = k % n; reverse(nums,0 ,n-1 ); reverse(nums,0 ,k-1 ); reverse(nums,k,n-1 ); } public void reverse (int [] nums, int start, int end) { while (start < end) { int temp = nums[start]; nums[start] = nums[end]; nums[end] = temp; start++; end--; } } }
198、打家劫舍(模板题)⭐⭐
典型的动态规划入门题
思路198. 打家劫舍 - 力扣(Leetcode)
优化前
image-20221013224043773
注意:
dp[0]表示没有房间,不对应nums中任何值
dp[i]对应的应该是第i-1个屋子的最大收益。
优化后(理论上更优的)
image-20221013224912862
208. 实现 Trie (前缀树)⭐
考察: #前缀树
拓展:1032. 字符流
前缀和介绍思路:【图解算法】字典树
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 class TreeNode { boolean isEnd; TreeNode[] Children; public TreeNode () { isEnd = false ; Children = new TreeNode [26 ]; } } class Trie { TreeNode root; public Trie () { root = new TreeNode (); } public void insert (String word) { TreeNode r = root; char [] sc = word.toCharArray(); for (int i=0 ;i<word.length();i++) { if (r.Children[sc[i]-'a' ]==null ) { r.Children[sc[i]-'a' ] = new TreeNode (); } r = r.Children[sc[i]-'a' ]; } r.isEnd = true ; } public boolean search (String word) { TreeNode r = root; char [] sc = word.toCharArray(); for (int i=0 ;i<word.length();i++) { if (r.Children[sc[i]-'a' ]==null ) { return false ; } r = r.Children[sc[i]-'a' ]; } return r.isEnd; } public boolean startsWith (String prefix) { TreeNode r = root; char [] sc = prefix.toCharArray(); for (int i=0 ;i<prefix.length();i++) { if (r.Children[sc[i]-'a' ]==null ) { return false ; } r = r.Children[sc[i]-'a' ]; } return true ; } }
213、打家劫舍 II⭐
动态规划
image-20221017165225803
相比198、打家劫舍,我们可以将题目拆解为:
n (0~n-1) 个屋子分别在 0~n-2 和 1~n-1 中取最大值,这样就能保证首位不会同时出现
226. 翻转二叉树
考察: #树
简答题,只要类似后序遍历,每次返回更改过左右节点的根即可。
283. 移动零
简单题。使用双指针,i 指向第一个 0,j 指向 i+1 位置。之后遍历 j,每次如果 j 位置的元素不是 0,则将其赋值到 i 的位置,i 再++。最后将 i 及其之后的元素置为 0 即可。
300. 最长递增子序列⭐
考察: #动态规划 #最长递增子序列
拓展:1626. 无矛盾的最佳球队
中等题。这是最长递增子序列的最基础的题目!dp 保存的是以当前数组结尾的最长的个数。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public int lengthOfLIS (int [] nums) { int n = nums.length; int [] dp = new int [n]; dp[0 ] = 1 ; int res = 1 ; for (int i=1 ;i<n;i++) { int m = 1 ; for (int j=i-1 ;j>=0 ;j--) { if (nums[j] >= nums[i]) { continue ; } else { m = Math.max(m,dp[j]+1 ); } } dp[i] = m; res = Math.max(res,dp[i]); } return res; } }
438. 找到字符串中所有字母异位词
考察: #滑动窗口
中等题。因为可能会出现重复单词,所以不能使用位运算。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Solution { public List<Integer> findAnagrams (String s, String p) { char [] sc = s.toCharArray(); char [] pc = p.toCharArray(); int n = s.length(); int pn = p.length(); List<Integer> res = new ArrayList <>(); int [] map = new int [26 ]; int [] test = new int [26 ]; if (n<pn) { return res; } for (char c : pc) { map[c-'a' ]++; } for (int i=0 ;i<pn;i++) { test[sc[i]-'a' ]++; } if (Arrays.equals(map,test)) { res.add(0 ); } for (int i=1 ;i<=n-pn;i++) { test[sc[i-1 ]-'a' ]--; test[sc[i+pn-1 ]-'a' ]++; if (Arrays.equals(map,test)) { res.add(i); } } return res; } }
453. 最小操作次数使数组元素相等
思路:n-1 个小的加 1,相当于最大的减 1 。详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public int minMoves (int [] nums) { int n = nums.length; int mi = Integer.MAX_VALUE; for (int i=0 ;i<n;i++) { mi = Math.min(mi,nums[i]); } int res = 0 ; for (int i=0 ;i<n;i++) { res += (nums[i]-mi); } return res; } }
462. 最小操作次数使数组元素相等 II
先对 nums 排序,只要选择的相等数 x ∈ [nums[i], nums[j]],对于左右指针所指向的数,其操作数是固定的,即 nums[j] - nums[i],指针所指向数的差值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int minMoves2 (int [] nums) { Arrays.sort(nums); int i = 0 , j = nums.length-1 ; int res = 0 ; while (i<=j) { res += (nums[j]-nums[i]); i++; j--; } return res; } }
494. 目标和⭐
方法一、二维数组 (纯暴力)
类似于树的递归调用,很愚蠢。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public int findTargetSumWays (int [] nums, int target) { int n = nums.length; int [][] res = new int [n][1 <<n]; res[0 ][0 ] = -nums[0 ]; res[0 ][1 ] = nums[0 ]; for (int i=1 ;i<n;i++) { for (int j=0 ;j<(1 <<i);j++) { res[i][j*2 ] = res[i-1 ][j] - nums[i]; res[i][j*2 +1 ] = res[i-1 ][j] + nums[i]; } } int sum = 0 ; for (int i=0 ;i<(1 <<n);i++) { if (res[n-1 ][i]==target) { sum++; } } return sum; } }
方法二、数学+动态规划
题解:题解
注意:不能添加以下的代码段,可能有多个 0,+0 和-0 效果一样,所以不一定是 1。 1 2 3 if (sum == target) { return 1 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public int findTargetSumWays (int [] nums, int target) { int n = nums.length; int sum = 0 ; for (int num : nums) { sum += num; } if (sum < target || (sum-target)%2 !=0 ) { return 0 ; } int neg = (sum-target)/2 ; int [][] dp = new int [n+1 ][neg+1 ]; dp[0 ][0 ] = 1 ; for (int i=1 ;i<n+1 ;i++) { int num = nums[i-1 ]; for (int j=0 ;j<neg+1 ;j++) { if (j<num) { dp[i][j] = dp[i-1 ][j]; } else { dp[i][j] = dp[i-1 ][j] + dp[i-1 ][j-num]; } } } return dp[n][neg]; } }
优化、用一维数组进行动态规划
需要注意:如果正向操作可能在 j>=num 时将原本的 dp[j]覆盖,在之后的调用原本的dp[j]时会出错,所以一维的动态规划需要从后向前逆序进行 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public int findTargetSumWays (int [] nums, int target) { int n = nums.length; int sum = 0 ; for (int num : nums) { sum += num; } if (sum < target || (sum-target)%2 !=0 ) { return 0 ; } int neg = (sum-target)/2 ; int [] dp = new int [neg+1 ]; dp[0 ] = 1 ; for (int num : nums) { for (int j=neg;j>=num;j--) { dp[j] += dp[j-num]; } } return dp[neg]; } }
其他
栈
超时 ,理论上不应该,已经进行了小优化了,但是还是会超时。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int findTargetSumWays (int [] nums, int target) { int n = nums.length; Deque<Integer> dq = new LinkedList <>(); dq.addLast(0 ); for (int i=0 ;i<n;i++) { int size = dq.size(); for (int j=0 ;j<size;j++) { int k = dq.pollFirst(); dq.addLast(k-nums[i]); dq.addLast(k+nums[i]); } } int sum = 0 ; while (dq.size()!=0 ) { if (dq.pollFirst()==target) { sum++; } } return sum; } }
509、斐波那契数
image-20221015213246525
简单哦~
524、通过删除字母匹配到字典里最长单词
方法一:排序后操作
image-20221009174042475
先对List进行排序,重写排序规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 Collections.sort(dictionary,new Comparator <String>() { public int compare (String s1, String s2) { if (s1.length() == s2.length()) { if (s1.compareTo(s2)>0 ) { return 1 ; } else { return -1 ; } } return s1.length()-s2.length(); } });
先对字符串List进行排序操作,根据题目要求,需要找到最长的且字典序最小的结果,所以依次遍历每一个字符串,如果当前字符串成立,则与它相同长度的字符串无需验证。
方法二、不排序操作
image-20221009174019047
这种思路无需先排序,只要记录已经匹配成功的字符串的长度,如果在接下来的字符串长度比它小,那就无需验证;如果长度更长,则进行验证,如果验证成功,更新匹配成功的字符串长度;如果字符串长度和记录的相同,将当前字符串和这个已经匹配成功的字符串比较,只有字典序更小才进行验证。
注意
字符串的比较
543. 二叉树的直径
考察: #树
简单题,但是思路很重要。
root 的直径 = root.left 的高度 + root.right 的高度
root 的高度 = Math.max(root.left的高度, root.right的高度) + 1
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Solution { public int res = 0 ; public int diameterOfBinaryTree (TreeNode root) { findd(root); return res; } public void findd (TreeNode root) { if (root == null ) { return ; } res = Math.max(res, height(root.left)+height(root.right)); findd(root.left); findd(root.right); } public int height (TreeNode root) { if (root == null ) { return 0 ; } return Math.max(height(root.left),height(root.right)) + 1 ; } }
560. 和为 K 的子数组
考察:前缀和
方法一、前缀和
利用前缀和+两层 for 循环,比较简单易想。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int subarraySum (int [] nums, int k) { int n = nums.length; int [] map = new int [n+1 ]; map[0 ] = 0 ; for (int i=1 ;i<=n;i++) { map[i] = map[i-1 ] + nums[i-1 ]; } int res = 0 ; for (int i=1 ;i<=n;i++) { for (int j=0 ;j<i;j++) { if (map[i]-map[j]==k) { res++; } } } return res; } }
方法二、前缀和+hash 优化⭐
思路: 同样先构造一个 map 数组记录前缀和,再构造一个 hash,在遍历 map 时,记录当前 map[i]-k 的值 d,也就是我们需要在 map[0, i) 找到下标为 j,满足 map[j]为 d,那么 map[i]-map[j]和就是 k。
注意: 在下面代码实现中,一定要先更新 res,再更新 hash。举个例子:如果 k 是 0,那么 d 就是 map[i],如果我先更新 hash,假设 key 为 d 的开始没有,那么就在 hash 中加入的 {d, 1},再更新 res 即为 res+1。但是这样是对的吗?显然不对!我要找的 k 是 0,但是我再 i 之前的 map 没有任何值是和 map[i]相等,理论上以 map[i]结尾是构造不了符合条件的子串,但是这里却+1,这是不对的。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int subarraySum (int [] nums, int k) { int n = nums.length; int [] map = new int [n+1 ]; map[0 ] = 0 ; for (int i=1 ;i<=n;i++) { map[i] = map[i-1 ] + nums[i-1 ]; } int res = 0 ; HashMap<Integer,Integer> hash = new HashMap <Integer, Integer>(); hash.put(0 , 1 ); for (int i=1 ;i<=n;i++) { int d = map[i]-k; res += hash.getOrDefault(d, 0 ); hash.put(map[i], hash.getOrDefault(map[i],0 )+1 ); } return res; } }
621. 任务调度器⭐
中等题,思路很妙!思路:
将任务按类型分组,正好 A-Z 用一个 int[26]保存任务类型个数
对数组进行排序(因为我们实际上对到底是 A5 个还是 B5 个不感兴趣,我们只需要有某个单词出现 5 次即可),优先排列个数(count)最大的任务,如题得到的时间至少为 res =(count-1)* (n+1) + 1 ==> A->X->X->A->X->X->A (X 为其他任务或者待命)。
再排序下一个任务,如果下一个任务 B 个数和最大任务数一致,则 res++ ==> A->B->X->A->B->X->A->B
对于空位还满足的,直接插进去即可。(你可能会觉得,如果剩下的空位不能满足 n 的条件怎么办,就像在 n=2 的情况下,在 ABCABCABCAxxA 时候还有 2 个 D,但是剩下的空间不能满足,这时候只需要稍微调整已经排序的位置即可,也就是变成 ABCBACBABxACx,那么两个 D 就能顺利插进去)
如果空位都插满之后还有任务,那就随便在这些间隔里面插入就可以,因为间隔长度肯定会大于 n,在这种情况下就是任务的总数是最小所需时间
最后返回的实际上就是 res 和所有单词的个数的最大值,所有单词个数实际上就是 tasks 的长度。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public int leastInterval (char [] tasks, int n) { int [] words = new int [26 ]; for (char c : tasks) { words[c-'A' ]++; } Arrays.sort(words); int res = (words[25 ] - 1 ) * (n+1 ) + 1 ; int j = 24 ; while (j>=0 && words[j]==words[25 ]) { res++; j--; } return Math.max(res,tasks.length); } }
647. 回文子串⭐
中心扩张法
.
中等题,其实思路很简单,就是确定一个中间,两边同时扩张。
中心需要分类讨论,如果中心是 1 个元素,那么扩张后的元素个数都是奇数;如果中心是 2 个元素,那么扩张后的元素个数都是偶数。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public int countSubstrings (String s) { int res = 0 ; char [] sc = s.toCharArray(); int n = sc.length; for (int i=0 ;i<n;i++) { int start = i, end = i; while (start>=0 && end<n) { if (sc[start] == sc[end]) { res++; start--; end++; } else { break ; } } } for (int i=0 ;i<n-1 ;i++) { int start = i, end = i+1 ; while (start>=0 && end<n) { if (sc[start] == sc[end]) { res++; start--; end++; } else { break ; } } } return res; } }
532、数组中的 k-diff 数对
image-20221009161302609
本题思路,先将数组升序。
设置一个i和j,注意 i 如果遇到重复的就跳过,j是找 nums[i]+k 的值,对于越来越大的i,如果他的j存在,那么一定再前一个i的j的后面。
但是需要注意的是i可能会跑到j后面,所以每次要对j做判断是否需要更新位置。
671、二叉树中第二小的节点
方法一、替换法
image-20221008121811267
更新新的最小值,实际上也是遍历整个数,逻辑相对复杂一些。
核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 if (isLeaf(root)) { return root.val; } if (a == temp && b == temp) { return temp; } else if (a == temp) { return b; } else if (b == temp) { return a; } else { return Math.min(a,b); }
方法二、DFS
image-20221008121848938
利用DFS深度优先搜索,提前设置好全局变量记录最小和第二小的值,DFS遍历整个树,找到第二小的值。
DFS的使用方法:void函数,当root==null时return;,否则调用dfs(root.left)和dfs(root.right),当全部遍历完后,全局变量也被修改了
707、设计链表
image-20220923102230396
Java语言使用链表格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class MyLinkedList { static class Node { int val; Node pre; Node next; public Node () { } public Node (int val) { this .val = val; } } Node head; int size=0 ; public MyLinkedList () { head = new Node (); head.next = head; head.pre = head; } public int get (int index) { Node node = head; } } MyLinkedList linkedList = new MyLinkedList ();linkedList.get(1 );
714、买卖股票的最佳时机含手续费⭐
方法一、贪心
image-20221027164450020
比较晦涩难懂,对left的定义很重要,特别是在prices[i]>left+fee情况的left改变比较难。看代码理解吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public int maxProfit (int [] prices, int fee) { int n = prices.length; int left = prices[0 ]; int sum = 0 ; for (int i=1 ;i<n;i++) { if (prices[i]<left) { left = prices[i]; } else if (prices[i]>left+fee) { sum += prices[i]-left-fee; left = prices[i]-fee; } } return sum; } }
方法二、动态规划
image-20221027164704099
唉,动态规划,容易理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int maxProfit (int [] prices, int fee) { int n = prices.length; int [][] dp = new int [n][2 ]; dp[0 ][0 ] = 0 ; dp[0 ][1 ] = -prices[0 ]; for (int i=1 ;i<n;i++) { dp[i][0 ] = Math.max(dp[i-1 ][0 ],dp[i-1 ][1 ]+prices[i]-fee); dp[i][1 ] = Math.max(dp[i-1 ][1 ],dp[i-1 ][0 ]-prices[i]); } return dp[n-1 ][0 ]; } }
优化
image-20221027165234692
动态规划常规优化喽~
739. 每日温度
方法一、单调栈⭐
如果本题使用从后往前进行遍历,每次循环内嵌套循环,必然会超出时间复杂度,因此不能只用简单得线性查找,需要使用新的查找技术——单调栈 。
应用场景: 在一个一维数组中,找到某个数得左边或者右边第一个最小或者最大得元素位置。
题解:题解
本题需要找右边第一个大于它的元素与它的距离。如果栈为空,则直接放入当前元素的下标;如果不为空,我们需要拿当前元素依次与栈顶元素的进行比较,如果当前元素比栈顶元素大,表示它是栈顶元素之后第一个大于栈顶元素的值,那么栈顶元素出栈,他们俩之间的距离就是栈顶元素下标的结果,再继续循环上述步骤,直到当前元素比栈顶元素小,则表示栈顶元素之后的都更小,那么直接将当前元素下标入栈。当全部遍历完后,如果栈内还有元素,表示这些元素没有遇到比他们更大的,那么依次出栈,并在对应值上设置为 0。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public int [] dailyTemperatures(int [] temperatures) { Deque<Integer> dq = new LinkedList <>(); int n = temperatures.length; int [] res = new int [n]; for (int i=0 ;i<n;i++) { int k = temperatures[i]; while (dq.size()!=0 ) { int j = dq.peekLast(); if (temperatures[j] < k) { res[j] = i - j; dq.pollLast(); } else { break ; } } dq.addLast(i); } while (dq.size()!=0 ) { res[dq.pollLast()] = 0 ; } return res; } }
740、删除并获得点数⭐
image-20221017202842714
题目中说的是如果选取i值,就不能选取所有的i-1和i+1。
这看似是涉及到左右,但是实际上只要考虑左边就行了。
考虑每个值选还是不选即可(变成了198、打家劫舍小偷问题)。
746、使用最小花费爬楼梯
方法一、动态规划
image-20221016212056771
注意找到规律:
1 2 3 4 dp[0 ] = 0 ; dp[1 ] = 0 ; dp[i] = Math.min(dp[i-1 ]+cost[i-1 ],dp[i-2 ]+cost[i-2 ]);
尝试优化空间
image-20221016214807512
1 2 3 4 5 6 7 int a = 0 , b = 0 , temp = 0 ;for (int i=2 ;i<=n;i++) { temp = Math.min(b+cost[i-1 ],a+cost[i-2 ]); a = b; b = temp; } return b;
但是奇怪的是更慢了
769、最多能完成排序的块⭐
image-20221014091237039
思路:769. 最多能完成排序的块 - 力扣(Leetcode)
注意题干:它表示在 [0, n - 1] 范围内的整数的排列,表示这n个数是0~n-1。
思路:所以这个数组的下标就是整体排序好的数值。
因此我们可以从头到尾遍历,记录最左边的值和最右边的值,记录在这一段中的max和min,当且仅当max==当前块的最右边值的下标且min==当前块的最左值的下标时,这一块结束。
此时最左边的下标更新为max的值+1,再将min和max都初始化。
777、在LR字符串中交换相邻字符⭐
image-20221002122050879
思路:
由题意可以知道,L是可以穿过X向左移动,R可以穿过X向右移动,但是L和R都无法互相穿过。因此,单纯地觉得只要将start和end去掉所有的X只要相等就返回true,像下面代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public boolean canTransform (String start, String end) { if (start.length() != end.length()) { return false ; } int n = start.length(); StringBuilder sb1 = new StringBuilder (); StringBuilder sb2 = new StringBuilder (); for (int i=0 ;i<n;i++) { if (start.charAt(i) != 'X' ) { sb1.append(start.charAt(i)); } if (end.charAt(i) != 'X' ) { sb2.append(end.charAt(i)); } } return sb1.toString().equals(sb2.toString()); } }
然而出现了一个不可忽略的错误:
例:
"LXXLXRLXXL"
"XLLXRXLXLX"
题意是:XL ==> LX;RX ==> XR 。
按照上面代码所述,不仅可以XL变为LX,也可以LX变为XL,这是不合题意的!
因此需要增加一个限定条件,即start中的L要比end中对应的L的下标要大,start中的R要比end中对应的R的下标要小。
所以,不能使用一次循环,需要使用双指针进行判断。
新代码思路是:
对于i和j分别指向start和end字符串,每次分别找一个非X字符,如果相同且满足i和j的关系的,则i++和j++;如果不满足直接返回false。这样的匹配规则,如果是可以完成匹配的,应该是i和j在某一次循环内同时到达终点,不会存在i卡在某个非X字符,j已经到了终点 ,如果是这种情况(即只有一个到了终点的),则之间判断i和j是否相等,如果相等,则返回true,不相等则返回false。
779、第K个语法符号
image-20221020103345966
本题使用递归思想,不算很难,但是需要找到规律。
第一行:0
第二行:0 1
第三行:01 10
第四行:0110 1001
第五行:01101001 10010110
发现什么了!
每一行的前一半字符是上一行的完整字符。
每一行的后一半字符是前一半字符的取反,也就是上一行的完整字符取反。
那结果很明显了!
使用递归的思想,找到k在这一行的位置,用位运算轻松得到每一行的len。在前半段,它的值就等于上一行的k的位置;在后半段,那它的值就等于上一行的(k-len/2)的位置的值的取反 。当然这里不是二进制,取反有风险,if判断一下喽!
完美解决~
788、旋转数字
image-20220925165535500
792、匹配子序列的单词数
方法一、暴力+微优化
对words中的每个字符进行判断,加入一定的提前跳出判断,勉强不超时(不推荐)。
方法二、首字母+队列
Screenshot_20221117_094650_com.huawei.browser
很妙!直接看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public int numMatchingSubseq (String s, String[] words) { Deque<String>[] dq = new Deque [26 ]; int sum = 0 ; for (int i=0 ;i<26 ;i++) { dq[i] = new ArrayDeque <>(); } for (String word : words) { dq[word.charAt(0 )-'a' ].addLast(word); } for (char sc : s.toCharArray()) { int k = dq[sc-'a' ].size(); for (int i=0 ; i<k; i++) { String p = dq[sc-'a' ].pollFirst(); if (p.length()==1 ) { sum++; } else { dq[p.charAt(1 )-'a' ].addLast(p.substring(1 )); } } } return sum; } }
796、旋转字符串
image-20221014092503892
最长对的思路就是:两个字符串合并,如果在其中contains(),那么就存在。
799、香槟塔
方法一、动态规划(模拟)
Pasted image 20221120093751
虽然是动态规划,确实不好想,但是想通了很简单。
我们可以假设第一次将所有的香槟都倒在第一层的杯里,然后从第一层开始遍历,对于每一个多出来的部分,分给它下面的两个杯子 。
直接构建一个二维数组即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public double champagneTower (int poured, int query_row, int query_glass) { double [][] dp = new double [101 ][101 ]; dp[0 ][0 ] = (double )poured; for (int i=0 ; i<=query_row; i++) { for (int j=0 ; j<=i; j++) { if (dp[i][j] > 1 ) { double half = (dp[i][j]-1 )/2.0 ; dp[i][j] = 1.0 ; dp[i+1 ][j] += half; dp[i+1 ][j+1 ] += half; } } } return dp[query_row][query_glass]; } }
优化
Pasted image 20221120100619
也就是将二维数组变成一维数组。
我们可以发现每一层的结构都是依赖上层的结果,所以可以直接设置一维数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public double champagneTower (int poured, int query_row, int query_glass) { double [] dp = {poured}; for (int i=1 ; i<=query_row; i++) { double [] temp = new double [i+1 ]; for (int j=0 ; j<i; j++) { if (dp[j] > 1 ) { double half = (dp[j]-1 )/2.0 ; temp[j] += half; temp[j+1 ] += half; } } dp = temp; } return Math.min(1 , dp[query_glass]); } }
801、使序列递增的最小交换次数⭐⭐
难题!
方法一、分类讨论+动态规划
image-20221014093122666
本题题目是问要使得两边都是递增,且同下标交换 需要的最小的次数。
所以一共有三种情况: 第一种情况: 下标为i的nums1和nums2的值,只比其对应的下标为i-1的nums1 和nums2 值大。
1 (nums1[i]>nums1[i-1 ] && nums2[i]>nums2[i-1 ]) && !(nums1[i]>nums2[i-1 ] && nums2[i]>nums1[i-1 ])
第二种情况: 下标为i的nums1和nums2的值,只比其对应的下标为i-1的nums2 和nums1 值大。
1 (nums1[i]>nums2[i-1 ] && nums2[i]>nums1[i-1 ]) && !(nums1[i]>nums1[i-1 ] && nums2[i]>nums2[i-1 ])
第三种情况: 下标为i的nums1和nums2的值,比其对应的下标为i-1的nums1 和nums2 值大,且比其对应的下标为i-1的nums2 和nums1 值大。
1 (nums1[i]>nums1[i-1 ] && nums2[i]>nums2[i-1 ]) && (nums1[i]>nums2[i-1 ] && nums2[i]>nums1[i-1 ])
构建dp数组:int[][] dp = new int[n][2];
第一列对应的是如果这一组不交换所需要的最小次数,第二列对应的是这一组交换所对应的最小次数。
根据这三种情况分别分析对应的操作:
第一种情况: 下标为i的nums1和nums2的值,只比其对应的下标为i-1的nums1 和nums2 值大。如果选择不交换,那么万事大吉,直接dp[i][0] = dp[i-1][0];;如果要交换,为了满足题目的要求,i-1下标的两个值也要交换,所以dp[i][1] = dp[i-1][1] + 1;。
第二种情况: 下标为i的nums1和nums2的值,只比其对应的下标为i-1的nums2 和nums1 值大。如果选择不交换,为了满足题目意思,需要将i-1下标的两个值交换,所以dp[i][0] = dp[i-1][1];;如果要交换,为了满足题目的要求,i-1下标的就不用交换,所以dp[i][1] = dp[i-1][0] + 1;。
第三种情况: 下标为i的nums1和nums2的值,比其对应的下标为i-1的nums1 和nums2 值大,且比其对应的下标为i-1的nums2 和nums1 值大。如果选择不交换,只需要取i-1下标交换或者不交换的更小值即可,即dp[i][0] = Math.min(dp[i-1][0],dp[i-1][1]);;如果要交换,取i-1下标交换或者不交换的更小值,再+1即可,即dp[i][1] = Math.min(dp[i-1][0],dp[i-1][1]) + 1;。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 dp[0 ][0 ] = 0 ; dp[0 ][1 ] = 1 ; for (int i=1 ;i<n;i++){ if ((nums1[i]>nums1[i-1 ] && nums2[i]>nums2[i-1 ]) && (nums1[i]>nums2[i-1 ] && nums2[i]>nums1[i-1 ])) { dp[i][0 ] = Math.min(dp[i-1 ][0 ],dp[i-1 ][1 ]); dp[i][1 ] = Math.min(dp[i-1 ][0 ],dp[i-1 ][1 ]) + 1 ; } else if ((nums1[i]>nums1[i-1 ] && nums2[i]>nums2[i-1 ]) && !(nums1[i]>nums2[i-1 ] && nums2[i]>nums1[i-1 ])) { dp[i][0 ] = dp[i-1 ][0 ]; dp[i][1 ] = dp[i-1 ][1 ] + 1 ; } else if (!(nums1[i]>nums1[i-1 ] && nums2[i]>nums2[i-1 ]) && (nums1[i]>nums2[i-1 ] && nums2[i]>nums1[i-1 ])) { dp[i][0 ] = dp[i-1 ][1 ]; dp[i][1 ] = dp[i-1 ][0 ] + 1 ; } }
方法二、方法一dp数组改变
image-20221014104859252
将方法一的int[][] dp = new int[n][2];变成int[][] dp = new int[2][n];,其他思路一致。
空间换时间的操作。
方法三、优化
image-20221014105138512
只保存i-1状态的两个值即可。
808、分汤⭐
方法一、DFS+记忆化
Pasted image 20221121112857
注意到四种分汤情况都是25的倍数,所以可以先对n进行简化
然后进行dfs, 当i≤0 && j≤0时,表示两种汤都分配完了,此时应该返回 0.5; 当 i≤0 时,表示汤 A 先分配完了,此时应该返回 1; 当 j≤0 时,表示汤 B 先分配完了,此时应该返回 0。
double ans = 0.25 * (dfs(i - 4, j) + dfs(i - 3, j - 1) + dfs(i - 2, j - 2) + dfs(i - 1, j - 3));
但是需要注意,之这样写一定会显示时间超出
优化1
加入记忆化方式,也就是加入一个数组,记录在A有i和B有j的情况下的概率信息。
1 2 3 4 if (memo[i][j]==0 ) { double sum = 0.25 * (dfs(i-4 ,j) + dfs(i-3 ,j-1 ) + dfs(i-2 ,j-2 ) + dfs(i-1 ,j-3 )); memo[i][j] = sum; }
但是仍然会出现空间超出
优化2
借助数学的方式,在n=4800时,结果为0.999994994426,而题目要求的精度为\(10^{-5}\) ,并且随着n的增大,结果越来越接近1,因此,当n>4800时,直接返回1即可。
1 2 3 if (n>=4800 ) { return 1 ; }
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { double [][] memo; public double dfs (int i, int j) { if (i<=0 && j<=0 ) { return 0.5 ; } if (i<=0 ) { return 1 ; } if (j<=0 ) { return 0 ; } if (memo[i][j]==0 ) { double sum = 0.25 * (dfs(i-4 ,j) + dfs(i-3 ,j-1 ) + dfs(i-2 ,j-2 ) + dfs(i-1 ,j-3 )); memo[i][j] = sum; } return memo[i][j]; } public double soupServings (int n) { if (n>=4800 ) { return 1 ; } n = (n+24 )/25 ; memo = new double [n+1 ][n+1 ]; return dfs(n,n); } }
方法二、动态规划
Pasted image 20221121115908
动态规划难在初始化。本题需要一个二维数组进行动态规划。
表示A和B都没有了
表示A没有了
1 2 3 for (int i=1 ;i<=n;i++) { dp[0 ][i] = 1 ; }
表示B没有了(B没有返回0,所以根据初始化自动为0)
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public double soupServings (int n) { if (n>=4800 ) { return 1 ; } n = (n+24 )/25 ; double [][] dp = new double [n+1 ][n+1 ]; dp[0 ][0 ] = 0.5 ; for (int i=1 ;i<=n;i++) { dp[0 ][i] = 1 ; } for (int i=1 ;i<=n;i++) { for (int j=1 ;j<=n;j++) { dp[i][j] = 0.25 * (dp[Math.max(0 ,i-4 )][j] + dp[Math.max(0 ,i-3 )][Math.max(0 ,j-1 )] + dp[Math.max(0 ,i-2 )][Math.max(0 ,j-2 )] + dp[Math.max(0 ,i-1 )][Math.max(0 ,j-3 )]); } } return dp[n][n]; } }
809、情感丰富的文字
方法一、逐字对比+长度加速
Pasted image 20221126195121
对每个字符串进行检索,找到当前字符连续的长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class Solution { public int expressiveWords (String s, String[] words) { int sum = 0 ; char [] sc = s.toCharArray(); for (String w : words) { if (w.length() > s.length()) { continue ; } char [] wc = w.toCharArray(); int i = 0 , j = 0 ; boolean isFind = true ; while (i<s.length() && j<w.length()) { if (sc[i] == wc[j]) { char t = sc[i]; int l1 = 1 ; int l2 = 1 ; while ((i+1 )<s.length() && sc[i+1 ]==t) { l1++; i++; } while ((j+1 )<w.length() && wc[j+1 ]==t) { l2++; j++; } if (l1==l2 || (l1 > l2 && l1 >= 3 )) { i++; j++; } else { isFind = false ; break ; } } else { isFind = false ; break ; } } if (i==s.length() && j == w.length() && isFind) { sum ++; } } return sum; } }
811、子域名访问计数
image-20221005111235484
本题逻辑简单,但是涉及很多java语法知识。
HashMap(散列表)
创建
1 2 3 4 5 6 HashMap<String,Integer> hash = new HashMap <String,Integer>(); 添加键值对 ```java hash.put("abc" ,2 );
查找值
更新值
1 hash.replace("abc" ,2 ,3 );
查找键值对数量
判断是否为空
判断是否有某个key
判断是否有某个value
遍历
1 2 3 4 5 6 hash.forEach((key,value)-> { String s = value.toString() + key; res.add(s); });
1 2 3 4 5 for (Map.Entry<String,Integer> entry : map.entrySet()) { String key = entry.getKey(); Integer value = entry.getValue(); }
1 2 3 4 5 6 7 for (String key : hash.KeySet()) { } for (String value : hash.values()) { }
删除所有键值对
字符串按空格分割
对于String s = "a b c d e f g"
分割单个空格
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 String[] sa = s.split(" " );
分割多个连续空格
1 2 3 4 5 6 7 8 9 String[] sa = s.split("\\s+" );
字符串转为整数
使用Integer.parseInt(s)
1 2 String s = "12345" ;int b = Integer.parseInt(s);
长度函数
length:数组 长度
length():字符串 长度
size():列表 长度
List的构建
对于单个List
1 List<String> l = new ArrayList <String>();
对于嵌套List
1 List<List<String>> ll = new ArrayList <>();
817、链表组件
image-20221009164428565
先用一个容器将nums里的所有数值全都存储起来,之后在遍历链表判断当前值是否在nums数组中。
什么样的容器具有这样的功能呢?String?Map?对!Map最合适!
所以我们选择HashMap,和他的containsKey(也可以使用containsValue):
1 2 3 4 5 6 7 8 HashMap<Integer,Boolean> hash = new HashMap <Integer,Boolean>(); for (int num : nums) { hash.put(num,true ); } if (hash.containsKey(p.val)) { }
然后再判断有多少个组件,常规思路。
831. 隐藏个人信息
中等题,简单构造题。
855. 考场就座⭐
题解
方法一、有序集合
Pasted image 20221230231503
考察有序集合 TreeSet 的应用。
思路其实并不复杂,对于 leave 其实就是 remove () 函数;对于 seat 其实就是判断每一段之间的距离的一半 和最左边与左端点的距离 和最右边与右端点的距离 的最大值。如果是段之间的距离更大,那么点就在段中点;如果左边的距离更大,那么点就在 0;如果右边距离更大,那么点就在 n-1。
此外还需要注意局部变量和全局变量之间的转换。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class ExamRoom { TreeSet<Integer> ts; int n; public ExamRoom (int n) { ts = new TreeSet <>(); this .n = n; } public int seat () { if (ts.size()==0 ) { ts.add(0 ); return 0 ; } int left = ts.first(); int disl = ts.first()-0 ; int inx = 0 ; for (int x : ts) { if (disl < (x-left)/2 ) { disl = (x-left)/2 ; inx = (x+left)/2 ; } left = x; } int disr = n-1 -ts.last(); if (disl < disr) { inx = n-1 ; } ts.add(inx); return inx; } public void leave (int p) { ts.remove(p); } }
方法二、有序集合+优先队列
见题解。
856、括号的分数
方法一、栈
image-20221009165925676
遇到括号匹配,第一时间想到什么?——栈!!
但是Java没有栈,只能用Deque双端队列代替。
需要多次使用的方法:
1 2 3 4 5 6 7 8 Deque<String> dq = new LinkedList <>(); dq.addLast(st); String temp = dq.pollLast();
拓展:
String的相等操作:
char的相等操作
char转为String
1 String st = String.valueOf('a' );
char转为int
String转为int
1 sum = Integer.parseInt('23' );
方法二、一次遍历⭐
image-20221009170918190
这个方法思路真的很妙!
首先,所有的()都是1,所以用replace将()换成1
对s进行遍历,遇到1个(,则对于后面的1乘以2,遇到),则对后面的1除以2
例子:((())(())())
可以看出这题的结果时:(1*2 + 1*2 + 1)*2 = 1*2*2 + 1*2*2 + 1*2
替换:((1)(1)1)
从左遍历,第一个1左边有2个(、0个),所以对于1*2*2
第二个1左边有3个(、1个),所以对于1*2*2*2/2 = 1*2*2
第三个1左边有3个(、2个),所以对于1*2*2*2/2/2 = 1*2
注意:连续乘以n个2的操作,可以用number << n表示。
862、和至少为K的最短子数组⭐⭐
image-20221026215844261
很难且恶心人的一题。
贴上我的心路历程:
一开始我是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class Solution { public int shortestSubarray (int [] nums, int k) { int n = nums.length; if (n==1 ) { if (nums[0 ]>=k) { return 1 ; } else { return -1 ; } } int i=0 , j=1 ; int temp = nums[i]; int step = 1 ; int min = 100001 ; while (i<n) { if (j<n) { if (temp>=k) { min = Math.min(min,step); temp -= nums[i]; i++; step--; } else { temp += nums[j]; step++; j++; } } else { if (temp>=k) { min = Math.min(min,step); temp -= nums[i]; i++; step--; } else { temp -= nums[i]; i++; step--; } } } return min==100001 ? -1 : min; } }
后来发现,如果出现 k=5, 1, -2, 2, 7这种情况就没法找到真正的最短。
然后我使用双层for循环:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public int shortestSubarray (int [] nums, int k) { int n = nums.length; int [] preadd = new int [n+1 ]; for (int i=1 ;i<n+1 ;i++) { preadd[i] = preadd[i-1 ] + nums[i-1 ]; } int min = 100001 ; for (int i=0 ;i<n;i++) { if (nums[i]<=0 ) { continue ; } for (int j=i+1 ;j<n+1 ;j++) { if (j-i>=min) { break ; } if (preadd[j]-preadd[i]>=k) { min = Math.min(min,j-i); break ; } } } return min==100001 ? -1 : min; } }
最后,根据题解……我转换了思路。⭐
以每个元素为开始的最短的长度是一定的
preadd的作用看代码能懂吧,所以以i为开始的满足条件的数组的长度就是preadd[j]-preadd[i],长度就是j-i。
利用一个for循环控制结束的字符,用一个队列控制开始的字符。(其实数组也可,但是会慢些)
第一个while是确定结束位置,依次更新初始位置。
第二个while是对结束位置的左边进行筛选,为了减少复杂度,如果它的相邻左边的preadd比它的preadd要大,根据preadd[j]-preadd[i],如果preadd[i]要比preadd[i-1]小,那么以某个j结尾的,一定会选择i而不会以i-1开头,所以在这里可以删掉i-1了。
每次都将当前j加入队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int shortestSubarray (int [] nums, int k) { int n = nums.length; long [] preadd = new long [n+1 ]; for (int i=1 ;i<n+1 ;i++) { preadd[i] = preadd[i-1 ] + nums[i-1 ]; } int min = n+1 ; Deque<Integer> dq = new ArrayDeque <Integer>(); for (int j=0 ;j<n+1 ;j++) { while (!dq.isEmpty() && preadd[j]-preadd[dq.peekFirst()]>=k) { int i = dq.pollFirst(); min = Math.min(min,j-i); } while (!dq.isEmpty() && preadd[dq.peekLast()]>=preadd[j]) { dq.pollLast(); } dq.addLast(j); } return min==n+1 ? -1 : min; } }
870、优势洗牌⭐
image-20221009202231681
本题思路简单,但是操作困难,需要知道如何保证不动nums2的前提下,根据其值对其下标进行排序的方法
这里涉及到sort的匿名函数表达式的知识点: 1 2 3 4 5 6 7 8 Integer[] index = new Integer [n]; for (int i=0 ;i<n;i++) { index[i] = i; } Arrays.sort(index,(a,b)->nums2[a]-nums[b]);
这里需要注意的是:
使用匿名表达式的数组必须是原始数组,也就在这里index数组是不能是int,只能是Integer
Arrays.sort(index,(a,b)->function);表示的是对于index数组而言,只有当function>0时,会将(a,b)变成(b,a)
关于sort函数复写排序规则,还有一种更加常规的方法
1 2 3 4 5 6 7 8 9 10 Arrays.sort(index, new Comparator <Integer>() { public int compare (Integer a, Integer b) { if (nums2[a] > nums2[b]) { return 1 ; } else { return -1 ; } } });
878、第N个神奇数字⭐🌟
image-20221009172307797
这题思路很巧妙,我们会第一时间想到找到所有的满足条件的数,直到第n个,但是这样的工作量真的太大了,应为涉及到加法和乘法。
所以我们不妨换个思路,在很大的范围内,找到这个符合条件的数。
首先确定搜索方法,一般查找都会选择二分查找 ,效率高。
再确定范围,起点范围0,终点范围呢?其实用max(a,b)*n或者min(a,b)*n都可以。但是通过测试,我们的目标值其实更接近min(a,b)*n,但是正因为这样,如果使用min(a,b)*n,需要更多步骤才能找到,所以我们选择max(a,b)*n。
那判断是否为目标数值的条件呢?
我们通过小数找规律,可以发现,第n个符合规则的数一定是
num/a + num/b - num/gbs(a,b) == n,可以好好揣摩一下这个规则。
但是并不是只有我们目标的那个第n个符合规则的数满足,第n和第n+1个数之间的数都满足,所以我们需要找到符合这个条件的最左值。
1 2 3 4 5 6 7 8 9 10 11 while (left<right) { long mid = left + (right-left)/2 ; if (mid/a + mid/b - mid/gbs_num < n) { left = mid + 1 ; } else { right = mid; } }
最后返回left%MOD即可。
(找最小公倍数,需要找到最小公因数,这个很简单。)
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public static int gys (int a, int b) { if (b==0 ) { return a; } else { return gys(b,a%b); } } public static int gbs (int a, int b) { return a*b/gys(a,b); } public int nthMagicalNumber (int n, int a, int b) { int MOD = 1_000_000_007 ; long left = 0 ; long right = (long ) Math.max(a,b)*n; long gbs_num = gbs(a,b); while (left<right) { long mid = left + (right-left)/2 ; if (mid/a + mid/b - mid/gbs_num < n) { left = mid + 1 ; } else { right = mid; } } return (int ) (left%MOD); } }
tips
一定要注意Math库返回的都是int类型,本题需要转为long型
882、细分图中的可到达节点
891、子序列宽度之和
典型数学题目
Screenshot_20221118_105117_com.huawei.browser
仔细分析题目,可以发现子序列只与最大值和最小值有关,和序列的顺序无关。
所以可以先对数组进行排序。
再按照下面思路进行:
1 3 5 8_202211181058_07318 1
直接看代码更容易理解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { int MOD = 1000000007 ; public int sumSubseqWidths (int [] nums) { Arrays.sort(nums); long k = 1 ; long sum = 0 ; long temp = 0 ; for (int i=1 ;i<nums.length;i++) { k = (k << 1 )%MOD; temp = (temp*2 %MOD + nums[i-1 ])%MOD; sum = (sum + (nums[i]*(k-1 ) - temp)%MOD)%MOD; } return (int )sum; } }
901、股票价格跨度⭐
方法一、栈⭐
image-20221021183314076
题目翻译
返回每一天的比这一天的股票价格小于或者等于的连续天数。
思路
构造一个二元栈:
1 2 3 4 5 6 7 8 class Stock { public int price; public int day; public Stock (int price, int day) { this .price = price; this .day = day; } }
对于输入的每一个节点,默认day是1。题解
image-20221021183855971
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public int next (int price) { int day = 1 ; if (!st.isEmpty()){ while (!st.isEmpty()) { Stock temp = st.peekLast(); if (price>=temp.price) { st.pollLast(); day = day + temp.day; } else { break ; } } } st.addLast(new Stock (price,day)); return day; }
完整代码
(面向对象的写法,注意结构体的构造等知识)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class StockSpanner { Deque<Stock> st; class Stock { public int price; public int day; public Stock (int price, int day) { this .price = price; this .day = day; } } public StockSpanner () { st = new ArrayDeque <Stock>(); } public int next (int price) { int day = 1 ; if (!st.isEmpty()){ while (!st.isEmpty()) { Stock temp = st.peekLast(); if (price>=temp.price) { st.pollLast(); day = day + temp.day; } else { break ; } } } st.addLast(new Stock (price,day)); return day; } }
方法二、数组
image-20221021184036499
思路一致。
我们还需要两个指针,分别是index指针,用来指向“待输入股票”;p指针,index指针的前一个指针,用来与“待输入股票”进行price对比用的,如果它的price小于等于“待输入股票”的price,p就会向前移动。
关于p向前移动还有一点需要注意的就是,p向前移动格子的数量,就是days的具体值;当days等于1时,就向前移动1个格子;如果days等于2时,就向前移动2个格子(因为days等于2,说明已经是两个格子聚合过的值了,就不需要重复统计了)。
image-20221021184303533
初始化时注意:
1 2 3 4 5 public StockSpanner () { index = 0 ; prices = new int [100010 ]; days = new int [100010 ]; }
方法三、List
image-20221021184138152
思路一致。
902、最大为 N 的数字组合⭐⭐
image-20221018223704471
非常难的一题,难在实现上!
904、水果成篮
image-20221017104714073
构建两个数组,分别记录某一个连续值的值和出现的次数,比较简单。(当然可以将两个数组并为一个二维数组)
907、子数组的最小值之和⭐⭐
方法一、单调栈+动态规划
image-20221028190348577
维护两个数组,分别维护左边界和右边界(对于下标为i的辐射左范围为left[i]+1(left[i]不在范围内))。
建立一个单调栈,两次遍历数组,更新左边界和右边
3 4 1 2 5 6
对于1的辐射范围,左边范围长度为3,右边为4 1, 41, 12, 341, 412, 125, 3412, 4125, 1256, 34125, 41256, 341256,共12个
对于4的辐射范围,左边范围长度为1,右边为1 4,共1个
对于6的辐射范围,左边范围长度为1,右边为1 6,共1个
看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 class Solution { public static int MOD = 1000000007 ; public int sumSubarrayMins (int [] arr) { int n = arr.length; int [] left = new int [n]; int [] right = new int [n]; Deque<Integer> dq = new ArrayDeque <Integer>(); for (int i=0 ;i<n;i++) { while (!dq.isEmpty() && arr[i]<arr[dq.peekLast()]) { dq.pollLast(); } if (dq.isEmpty()) { left[i] = -1 ; } else { left[i] = dq.peekLast(); } dq.addLast(i); } dq.clear(); for (int i=n-1 ;i>=0 ;i--) { while (!dq.isEmpty() && arr[i]<=arr[dq.peekLast()]) { dq.pollLast(); } if (dq.isEmpty()) { right[i] = n; } else { right[i] = dq.peekLast(); } dq.addLast(i); } long sum = 0 ; for (int i=0 ;i<n;i++) { sum = (sum + (long )arr[i] * (i-left[i]) * (right[i]-i)) % MOD; } return (int )sum; } }
915、分割数组
方法一、最小值分割
image-20221025214008642
先找到数组的最小值,最小值包括左边的部分和右边的部分一定需要分开的,如果左边的最大值<=右边的最小值,那么ok,否则对右边部分继续找最小值,继续划分。
方法二、两次遍历
image-20221025220514828
先从后向前遍历一遍,用min[i]表示i位置及其以后区域的最小值。
再从前往后遍历一遍,用max记录以当前元素结尾的区域的最大值。
第二次遍历过程中,如果某一次遍历时发现max <= min[i+1],则i就是分隔符,返回i+1个元素。
918、环形子数组的最大和⭐
image-20221019211520951
本题是53、最大子数组和的加强版。使用的仍然是动态规划的内容。
分析
本题因为是环形,所以需要考虑两种情况。
情况1:我们的最终答案是53题中的不考虑环的情况。
情况2:我们的答案包含nums的尾部和nums的首部。
image-20221019211933787
思路
情况1很简单,直接仿照53题进行即可。
情况2复杂,需要考虑首位,但是首位又是动态的,很麻烦。但是我们能不能换种思路,我们要找到nums的最小子数组和 min。那么最长的首位组合就是整个nums之和减去这个最小子数组和。
特殊
我们需要考虑,如果情况一得到的max小于0,说明整个nums中没有一个元素是>0的,那么直接输出max(矮子当中选将军),它就是最大的那个了。否则表示数组中存在正数,那么输出max和sum-min中的最大值
927、三等分
image-20221006155904427
思路有点复杂,实现不难:
先判断每一组有多少个1,把每个1的下标都放在一个新的数组中,然后判断三组之间对应的1和1之间的距离是否相等,再判断最后一组的最后一个1后的空格其他组是否能满足。
934、最短的桥⭐
比较难的一题,题目难懂,方法难想到。
image-20221025210227986
思路
image-20221025210421377
代码
详细的思路看代码更好理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 class Solution { int [][] step = {{1 ,0 },{-1 ,0 },{0 ,1 },{0 ,-1 }}; Deque<int []> edge; public int shortestBridge (int [][] grid) { boolean isFind = false ; edge = new ArrayDeque <>(); for (int i=0 ;i<grid.length;i++) { if (isFind) { break ; } for (int j=0 ;j<grid[0 ].length;j++) { if (isFind) { break ; } if (grid[i][j]==1 ) { isFind = true ; Mark(grid,i,j); } } } int result = 1 ; while (!edge.isEmpty()) { int n = edge.size(); for (int i=0 ;i<n;i++) { int [] loc = edge.pollFirst(); for (int []s : step) { int x = loc[0 ]+s[0 ], y = loc[1 ]+s[1 ]; if (!isLegal(x,y,grid.length)) { continue ; } if (grid[x][y] == 2 ) { continue ; } if (grid[x][y] == 1 ) { return result; } if (grid[x][y] == 0 ) { grid[x][y] = 2 ; edge.addLast(new int []{x,y}); } } } result++; } return result; } public void Mark (int [][] grid, int i, int j) { if (!isLegal(i,j,grid.length)) { return ; } if (grid[i][j]==2 ) { return ; } if (grid[i][j]==1 ) { grid[i][j] = 2 ; for (int []s : step) { Mark(grid,i+s[0 ],j+s[1 ]); } } if (grid[i][j]==0 ) { grid[i][j] = 2 ; edge.addLast(new int []{i,j}); } } public boolean isLegal (int i, int j, int n) { if (i<0 || i>=n || j<0 || j>=n) { return false ; } return true ; } }
970. 强整数
中等题,但是比较简单。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Solution { public List<Integer> powerfulIntegers (int x, int y, int bound) { Set<Integer> set = new HashSet <>(); List<Integer> x_list = new ArrayList <>(); List<Integer> y_list = new ArrayList <>(); List<Integer> res = new ArrayList <>(); int sum = 1 ; if (x==1 ) { x_list.add(x); } while (x!=1 && sum <= bound) { x_list.add(sum); sum = sum*x; } sum = 1 ; if (y==1 ) { y_list.add(y); } while (y!=1 && sum <= bound) { y_list.add(sum); sum = sum*y; } for (int i=0 ;i<x_list.size();i++) { for (int j=0 ;j<y_list.size();j++) { if (x_list.get(i)+y_list.get(j)<=bound) { set.add(x_list.get(i)+y_list.get(j)); } else { break ; } } } for (int n : set) { res.add(n); } return res; } }
982. 按位与为零的三元组
我们可以先枚举任意两个数 x 和 y,用数组统计它们的按位与结果 x&y出现的次数。然后枚举 x 和 y 的按位与结果 xy,再枚举 z,如果 xy&z=0,则将 cnt[xy] 的值加入答案。
常识:两个数相与的结果一定是小于等于这两个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public int countTriplets (int [] nums) { int max_n = 0 ; for (int n : nums) { max_n = Math.max(max_n, n); } int [] re = new int [max_n+1 ]; for (int a : nums) { for (int b : nums) { re[a & b]++; } } int res = 0 ; for (int i=0 ;i<max_n+1 ;i++) { if (re[i]!=0 ) { for (int n : nums) { if ((i & n) == 0 ) { res += re[i]; } } } } return res; } }
1003. 检查替换后的词是否有效
方法一、字符串操作
不断对 s 删除"abc"字符串,如果最后能删除干净,那么返回 true,否则返回 false。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public boolean isValid (String s) { if (s.length()%3 !=0 ) { return false ; } while (s.length()!=0 ) { int i = 0 ; boolean isFind = false ; for (i=0 ;i<s.length()-2 ;i++) { if (s.substring(i,i+3 ).compareTo("abc" )==0 ) { s = s.substring(0 ,i) + s.substring(i+3 ); isFind = true ; } } if (!isFind) { System.out.println(s); return false ; } } return true ; } }
方法二、栈
其实思路与上面一致,但是上面的方法每次都要重新构造 s,会有很大的时间和空间开销,所以用栈将字符串的每个字符依次入栈,如果遇到"abc",弹出去,依次进行,如果栈空则 true。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public boolean isValid (String s) { if (s.length()%3 !=0 ) { return false ; } Deque<Character> dq = new LinkedList <>(); char [] sc = s.toCharArray(); for (int i=0 ;i<s.length();i++) { dq.addLast(sc[i]); while (dq.size()>=3 && dq.peekLast()=='c' ) { char c = dq.pollLast(); char b = dq.pollLast(); char a = dq.pollLast(); if (a!='a' || b!='b' || c!='c' ) { dq.addLast(a); dq.addLast(b); dq.addLast(c); break ; } } } if (dq.size()==0 ) { return true ; } return false ; } }
方法三、StringBuilder
这是上面两种方法的结合。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public boolean isValid (String s) { if (s.length()%3 !=0 ) { return false ; } StringBuilder sb = new StringBuilder (); char [] sc = s.toCharArray(); for (int i=0 ;i<s.length();i++) { sb.append(sc[i]); if (sb.length()>=3 && "abc" .equals(sb.substring(sb.length()-3 ))) { sb.delete(sb.length()-3 ,sb.length()); } } if (sb.length()==0 ) { return true ; } return false ; } }
1105. 填充书架⭐
考察: #动态规划
中等题,思路难。
题解:题解
这里 dp[i] 表示在放到第 i 本书的时候的总高度最优解,但这并不一定是放到最后一本书的最优解。要想找到第 i 本书的最优解,需要从后往前依次判断,如果第 [j, i] 本书都放在同一层(前提是厚度不超过), 那么总高度会不会变小。这样判断直到 i 所在的这一层的厚度到顶了,将最优解的高度存入 dp[i],表示我放到 i 这本书的时候的高度最优解。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public int minHeightShelves (int [][] books, int shelfWidth) { int n = books.length; int [] dp = new int [n+1 ]; Arrays.fill(dp, 1000 *1000 ); dp[0 ] = 0 ; for (int i=1 ; i<=n; i++) { int h = 0 , w = 0 ; for (int j=i; j>0 ; j--) { if (w + books[j-1 ][0 ] <= shelfWidth) { w += books[j-1 ][0 ]; h = Math.max(h, books[j-1 ][1 ]); dp[i] = Math.min(dp[i], dp[j-1 ]+h); } else { break ; } } } return dp[n]; } }
1014、最佳观光组合⭐
动态规划
image-20221021185104168
数学的正则运算。
values[i] + values[j] + i - j == (values[i] + i) + (values[j] - j)
完整代码,思路很清晰:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public int maxScoreSightseeingPair (int [] values) { int res = 0 ; int maxi = values[0 ]; for (int j=1 ;j<values.length;j++) { res = Math.max(res, maxi+values[j]-j); maxi = Math.max(maxi, values[j]+j); } return res; } }
1017. 负二进制转换⭐
考察: #位运算
思路:构建 bits 数组,bits[i] 表示需要 \((-2)^i\) 的次数。但是此时只需要对每个元素进行计数,有以下的规律。
对于 \(2^i\) ,如果 i 为偶数时,此时 \(2^i = (-2)^i\) ;
对于 \(2^i\) ,如果 i 为奇数时,此时 \(2^i = (-2)^{i+1} + (-2)^i\)
1 2 3 4 5 6 7 if (i % 2 == 0 ) { bits[i] += 1 ; } else { bits[i+1 ] += 1 ; bits[i] += 1 ; }
对已经构造好的 bits 数组处理,要求每个值只能是 0 或者-1。
如果 h[i]>=2: - 如果 h[i+1]>0,那么 h[i]的两份可以抵消 h[i+1]的一份(因为他们是异号)。 - 否则,h[i]的两份等价于 h[i+1]的一份和 h[i+2]的一份的和。
1 2 3 4 5 6 7 8 9 10 11 for (i=0 ;i<32 ;i++) { while (bits[i]>=2 && bits[i+1 ]>0 ) { bits[i] -= 2 ; bits[i+1 ] -= 1 ; } while (bits[i]>=2 ) { bits[i] -= 2 ; bits[i+1 ] += 1 ; bits[i+2 ] += 1 ; } }
题解:负二进制转换 题解
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class Solution { public String baseNeg2 (int n) { if (n == 0 ) { return "0" ; } int [] bits = new int [32 ]; int i = 0 ; while (n!=0 ) { if ((n & 1 ) == 1 ) { if (i % 2 == 0 ) { bits[i] += 1 ; } else { bits[i+1 ] += 1 ; bits[i] += 1 ; } } n = n >> 1 ; i++; } for (i=0 ;i<32 ;i++) { while (bits[i]>=2 && bits[i+1 ]>0 ) { bits[i] -= 2 ; bits[i+1 ] -= 1 ; } while (bits[i]>=2 ) { bits[i] -= 2 ; bits[i+1 ] += 1 ; bits[i+2 ] += 1 ; } } boolean isstart = false ; StringBuilder sb = new StringBuilder (); for (i=31 ;i>=0 ;i--) { if (bits[i]!=0 ) { isstart = true ; } if (isstart) { sb.append(String.valueOf(bits[i])); } } return sb.toString(); } }
1019. 链表中的下一个更大节点
中等题。思路:将链表先变成数组,然后利用栈的原理找到第一个大于的元素,存入栈的是元素在数组的下标。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Solution { public int [] nextLargerNodes(ListNode head) { ListNode t = head; int len = 0 ; while (t!=null ) { len++; t = t.next; } int [] nums = new int [len]; t = head; int i = 0 ; while (t!=null ) { nums[i] = t.val; t = t.next; i++; } Deque<Integer> dq = new LinkedList <Integer>(); int [] res = new int [len]; for (i=0 ;i<len;i++) { while (dq.size()!=0 ) { if (nums[dq.peekLast()]<nums[i]) { res[dq.pollLast()] = nums[i]; } else { break ; } } dq.addLast(i); } while (dq.size()!=0 ) { res[dq.pollFirst()] = 0 ; } return res; } }
1023. 驼峰式匹配
中等题,但是很简单,直接对应遍历即可。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public List<Boolean> camelMatch (String[] queries, String pattern) { int n = queries.length; char [] pc = pattern.toCharArray(); int pl = pc.length; ArrayList<Boolean> res = new ArrayList <Boolean>(); for (int i=0 ;i<n;i++) { String s = queries[i]; char [] sc = s.toCharArray(); int k = 0 ; boolean isTrue = true ; for (int j=0 ;j<sc.length;j++) { if (k<pl && sc[j]==pc[k]) { k++; continue ; } else { if (sc[j]>='A' && sc[j]<='Z' ) { isTrue = false ; break ; } } } if (isTrue && k==pl) { res.add(true ); } else { res.add(false ); } } return res; } }
1026. 节点与其祖先之间的最大差值
考察: #树 #前序遍历
中等题,需要认真审题。要注意得是:A、B 必须在一条枝上,但不一定是相邻得。这里使用得前序遍历。
前序遍历核心代码:
1 2 3 4 5 6 7 8 9 public void find (TreeNode r) { if (r==null ) { return ; } System.out.println(r.val); find(r.left); find(r.right); return ; }
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public int res = 0 ; public int maxAncestorDiff (TreeNode root) { dfs(root,-1 ,100001 ); return res; } public void dfs (TreeNode r, int max, int min) { if (r==null ) { return ; } int d = r.val; max = Math.max(max, d); min = Math.min(min, d); res = Math.max(res, Math.abs(max-min)); dfs(r.left, max, min); dfs(r.right, max, min); return ; } }
1027. 最长等差数列⭐⭐
考察: #动态规划 #递归
题解:题解
方法一、递归
思路在题解,代码有注释。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public HashMap<Integer, Integer>[] maxLen; public int [] nums; public int res = 0 ; public int longestArithSeqLength (int [] nums) { this .nums = nums; int n = nums.length; maxLen = new HashMap [n]; for (int i=1 ;i<n;i++) { dfs(i); } return res; } public HashMap<Integer,Integer> dfs (int i) { if (maxLen[i] != null ) { return maxLen[i]; } maxLen[i] = new HashMap <Integer, Integer>(); for (int j=i-1 ;j>=0 ;j--) { int d = nums[i] - nums[j]; if (!maxLen[i].containsKey(d)) { maxLen[i].put(d, dfs(j).getOrDefault(d,1 )+1 ); res = Math.max(res, maxLen[i].get(d)); } } return maxLen[i]; } }
优化
将代码合并在一个函数内。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int longestArithSeqLength (int [] nums) { int n = nums.length; HashMap<Integer, Integer>[] maxLen = new HashMap [n]; int res = 0 ; for (int i=0 ;i<n;i++) { maxLen[i] = new HashMap <Integer, Integer>(); for (int j=i-1 ;j>=0 ;j--) { int d = nums[i] - nums[j]; if (!maxLen[i].containsKey(d)) { maxLen[i].put(d, maxLen[j].getOrDefault(d,1 )+1 ); res = Math.max(res, maxLen[i].get(d)); } } } return res; } }
方法二、动态规划
关键是区间映射。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int longestArithSeqLength (int [] nums) { int n = nums.length; int [][] dp = new int [n][1001 ]; int res = 0 ; for (int i=1 ;i<n;i++) { for (int j=i-1 ;j>=0 ;j--) { int d = nums[i] - nums[j] + 500 ; if (dp[i][d]==0 ) { dp[i][d] = dp[j][d] + 1 ; res = Math.max(res, dp[i][d]); } } } return res + 1 ; } }
1031. 两个非重叠子数组的最大和
考察: #滑动窗口
中等题,但是比较简单,注意审题。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 class Solution { public int maxSumTwoNoOverlap (int [] nums, int firstLen, int secondLen) { int n = nums.length; int [] pre = new int [n]; int [] end = new int [n]; int res = 0 ; if (firstLen+secondLen>n) { return 0 ; } int sum = 0 ; for (int i=0 ;i<n;i++) { if (i<firstLen-1 ) { pre[i] = 0 ; sum += nums[i]; } else if (i == firstLen-1 ) { sum += nums[i]; pre[i] = sum; } else { sum += nums[i]; sum -= nums[i-firstLen]; pre[i] = Math.max(pre[i-1 ], sum); } } sum = 0 ; for (int i=n-1 ;i>=0 ;i--) { sum += nums[i]; if (i>n-firstLen) { end[i] = 0 ; } else if (i==n-firstLen) { end[i] = sum; } else { sum -= nums[i+firstLen]; end[i] = Math.max(end[i+1 ],sum); } } sum = 0 ; for (int i=0 ;i<n;i++) { sum += nums[i]; if (i<secondLen-1 ) { continue ; } else { if (i-secondLen>=0 ) { sum -= nums[i-secondLen]; res = Math.max(res, sum + pre[i-secondLen]); } if (i+1 <n) { res = Math.max(res, sum + end[i+1 ]); } } } return res; } }
1032. 字符流⭐
考察: #前缀树
基础:208. 实现 Trie (前缀树)
典型的前缀树 题,写这一题前一定要先写 208 题。本题实际上就是前缀树的反思路,是以当前输入的字符为结尾,往前找。而存储 word 的时候是将每个 word 倒着存。
优化:注意条件 1 <= words[i].length <= 200,所以在遍历时可以设置提前退出条件,否则会超时。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 class TreeNode { TreeNode[] children; boolean isStart; public TreeNode () { children = new TreeNode [26 ]; isStart = false ; } } class StreamChecker { TreeNode root = new TreeNode (); StringBuilder sb = new StringBuilder (); public StreamChecker (String[] words) { TreeNode r; for (String word : words) { r = root; char [] sc = word.toCharArray(); for (int i=word.length()-1 ;i>=0 ;i--) { int idx = sc[i]-'a' ; if (r.children[idx]==null ) { r.children[idx] = new TreeNode (); } r = r.children[idx]; } r.isStart = true ; } } public boolean query (char letter) { sb.append(letter); TreeNode r = root; for (int i=sb.length()-1 ,j=0 ; i>=0 && j<201 ; i--,j++) { int idx = sb.charAt(i)-'a' ; if (r.children[idx]==null ) { return false ; } else { r = r.children[idx]; if (r.isStart==true ) { return true ; } } } return false ; } }
1033. 移动石子直到连续👍
思路:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int [] numMovesStones(int a, int b, int c) { int [] map = new int []{a,b,c}; Arrays.sort(map); if (map[2 ]-map[0 ]==2 ) { return new int []{0 ,0 }; } else { if (map[1 ]-map[0 ]<=2 || map[2 ]-map[1 ]<=2 ) { return new int []{1 , map[2 ]-map[0 ]-2 }; } else { return new int []{2 , map[2 ]-map[0 ]-2 }; } } } }
1039. 多边形三角剖分的最低得分⭐
考察: #动态规划 #记忆搜索
中等题,有难度,但是只要思路对了那就好算。
动态规划(递推)的思路。dp[i][j]表示从第 i 节点到第 j 节点的最短乘积和。转换为子问题,遍历 i 和 j 中的点 k,k 将整个图形分割为三部分,分别是 dp[i][k]、dp[k][j]、以 ijk 为顶点的三角形。按照这样的思路配合利用数组记录 dp[i][j],可以快速求出 dp[0][n-1]。值得注意的是:如果 k == i + 1,那么直接返回 0,如果 j == i + 2 直接表示 i 到 j 就是一个三角形,直接返回他们的乘积。return 之前都别忘了保存,搜索之前别忘了记忆化查找。
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public int [][] dp; public int [] values; public int minScoreTriangulation (int [] values) { int n = values.length; this .values = values; this .dp = new int [n][n]; return find(0 ,n-1 ); } public int find (int start, int end) { if (dp[start][end]!=0 ) { return dp[start][end]; } if (end - start <= 1 ) { dp[start][end] = 0 ; return 0 ; } if (end - start == 2 ) { dp[start][end] = values[start]*values[start+1 ]*values[end]; return dp[start][end]; } int min = Integer.MAX_VALUE; for (int i = start+1 ; i <= end-1 ; i++) { min = Math.min(min, find(start,i) + find(i,end) + values[start]*values[i]*values[end]); } dp[start][end] = min; return min; } }
1040. 移动石子直到连续 II⭐
考察: #滑动窗口 #双指针
基础:1033
题解:题解
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public int [] numMovesStonesII(int [] stones) { Arrays.sort(stones); int n = stones.length; int e1 = stones[n-2 ]-stones[0 ]-1 -(n-3 ); int e2 = stones[n-1 ]-stones[1 ]-1 -(n-3 ); int MaxMove = Math.max(e1,e2); int MinMove = 0 ; if (e1==0 || e2==0 ) { MinMove = Math.min(2 , MaxMove); } else { int MaxCount = 0 ; int left = 0 ; for (int right = 0 ; right < n; right++) { while (stones[right] - stones[left] + 1 > n) { left++; } MaxCount = Math.max(MaxCount, right-left+1 ); } MinMove = n - MaxCount; } return new int []{MinMove,MaxMove}; } }
1041. 困于环中的机器人
简单的中等题,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public boolean isRobotBounded (String instructions) { char [] ic = instructions.toCharArray(); int [] loc = new int [4 ]; int face = 0 ; for (int k=0 ;k<4 ;k++) { for (int i=0 ;i<ic.length;i++) { if (ic[i]=='G' ) { loc[face]++; } else if (ic[i]=='L' ) { face = (face+1 )%4 ; } else if (ic[i]=='R' ) { face = (face+4 -1 )%4 ; } } if (loc[0 ]==loc[2 ] && loc[1 ]==loc[3 ]) { return true ; } } return false ; } }
1042. 不邻接植花⭐
中等题。题解:题解 。思路如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public int [] gardenNoAdj(int n, int [][] paths) { List<Integer>[] al = new List [n]; for (int i=0 ;i<n;i++) { al[i] = new ArrayList <Integer>(); } for (int i=0 ;i<paths.length;i++) { al[paths[i][0 ]-1 ].add(paths[i][1 ]-1 ); al[paths[i][1 ]-1 ].add(paths[i][0 ]-1 ); } int [] color = new int [n]; for (int i=0 ;i<n;i++) { boolean [] tc = new boolean [5 ]; for (int k : al[i]) { tc[color[k]] = true ; } for (int j=1 ;j<=4 ;j++) { if (!tc[j]) { color[i] = j; break ; } } } return color; } }
1043. 分隔数组以得到最大和⭐
考察: #递归 #动态规划
题解:题解
方法一、递归+记忆化搜索⭐
中等题。思路:构造一个函数 dfs (i),表示以位置 i 为结尾的前面的结果。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public int [] arr; public int k; public int [] memo; public int maxSumAfterPartitioning (int [] arr, int k) { this .arr = arr; this .k = k; int n = arr.length; memo = new int [n]; Arrays.fill(memo,-1 ); return dfs(n-1 ); } public int dfs (int i) { if (i<0 ) { return 0 ; } if (memo[i] != -1 ) { return memo[i]; } int max = 0 ; int res = 0 ; for (int j=i; j>=0 && j>=i-k+1 ; j--) { max = Math.max(max, arr[j]); res = Math.max(res, dfs(j-1 ) + max*(i-j+1 )); } memo[i] = res; return memo[i]; } }
方法二、动态规划
从左向右正向判断,每次直接计算出以 i 结尾的结果。最终结果就是 dp[n]。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public int maxSumAfterPartitioning (int [] arr, int k) { int n = arr.length; int [] dp = new int [n+1 ]; for (int i=0 ; i<n; i++) { int max = 0 ; for (int j=i; j>=0 && j>=i-k+1 ; j--) { max = Math.max(max, arr[j]); dp[i+1 ] = Math.max(dp[i+1 ], dp[j] + max*(i-j+1 )); } } return dp[n]; } }
优化空间
我们可以发现在动态规划中,计算 i 值只需要 [i-k+1, i] 区间的结果,长度为 k。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int maxSumAfterPartitioning (int [] arr, int k) { int n = arr.length; int [] dp = new int [k+1 ]; for (int i=0 ; i<n; i++) { int max = 0 ; int res = 0 ; for (int j=i; j>=0 && j>=i-k+1 ; j--) { max = Math.max(max, arr[j]); res = Math.max(res, dp[j%k] + max*(i-j+1 )); } dp[(i+1 )%k] = res; } return dp[n%k]; } }
1048. 最长字符串链
考察: #动态规划 #双指针
中等题。比较简单,只需要先将数组按照长度排序,然后遍历每个数组,将它与比它长度少 1 的字符串比较,比较时用一个指针即可,当 i 指针是两个字符串不同的位置,那么长字符串 i 之后的元素一定是与短字符串 i 及其之后的元素是相等的。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class Solution { public int longestStrChain (String[] words) { int n = words.length; Arrays.sort(words, new Comparator <String>() { public int compare (String a, String b) { return a.length()-b.length(); } }); int [] dp = new int [n]; Arrays.fill(dp,1 ); int res = 0 ; for (int i=0 ;i<n;i++) { for (int j=i-1 ;j>=0 ;j--) { if (words[j].length() < words[i].length()-1 ) { break ; } else if (words[j].length() == words[i].length()-1 ) { if (isTrue(words[i],words[j])==0 ) { dp[i] = Math.max(dp[i],dp[j]+1 ); } } } res = Math.max(res, dp[i]); } return res; } public int isTrue (String a, String b) { char [] ac = a.toCharArray(); char [] bc = b.toCharArray(); int i = 0 ; while (i<b.length()) { if (ac[i]!=bc[i]) { break ; } i++; } if (i==b.length()) { return 0 ; } return a.substring(i+1 ).compareTo(b.substring(i)); } }
1053. 交换一次的先前排列
中等题,实际上就是找规律。本题实际上找的是比当前数字组合更小的最大数字组合,但是只能交换一组数。通过规律定义如下步骤:
从右往左找降序(包括相等),找到第一个不是降序的数字位置,记作 loc1。
如果 loc1 还是初始值(定义为-1),表示整个数字组合都是升序,所以直接返回 arr 即可。
如果 loc1 不是初始值,那么继续下面步骤。
在 loc1 后面找比它小一点点的数,这里不要求数一定更靠左还是靠右,只要找到比它小的最大数的第一次出现位置,记作 loc2。
交换 loc1 的数字和 loc2 的数字。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public int [] prevPermOpt1(int [] arr) { int n = arr.length; int loc1 = -1 ; for (int i=n-2 ;i>=0 ;i--) { if (arr[i]>arr[i+1 ]) { loc1 = i; break ; } } if (loc1 == -1 ) { return arr; } int loc2 = loc1 + 1 ; for (int i=loc1+1 ;i<n;i++) { if (arr[i]<arr[loc1] && arr[i]>arr[loc2]) { loc2 = i; } } int temp = arr[loc1]; arr[loc1] = arr[loc2]; arr[loc2] = temp; return arr; } }
1092. 最短公共超序列⭐⭐
考察: #递归
困难题。题解:题解
思路:利用 dfs 检索以 s1 的 i 下标结尾和 s2 的 j 下标结尾的结果的最短长度。利用 FindStr 按照 dfs 正确的顺序进行查找。
最终代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class Solution { public int [][] map; public String s1, s2; public String shortestCommonSupersequence (String str1, String str2) { s1 = str1; s2 = str2; map = new int [s1.length()][s2.length()]; return FindStr(s1.length()-1 ,s2.length()-1 ); } public int dfs (int i, int j) { if (i<0 ) { return j+1 ; } if (j<0 ) { return i+1 ; } if (map[i][j]>0 ) { return map[i][j]; } if (s1.charAt(i) == s2.charAt(j)) { map[i][j] = dfs(i-1 ,j-1 ) + 1 ; return map[i][j]; } map[i][j] = Math.min(dfs(i-1 ,j), dfs(i,j-1 )) + 1 ; return map[i][j]; } public String FindStr (int i, int j) { if (i<0 ) { return s2.substring(0 ,j+1 ); } if (j<0 ) { return s1.substring(0 ,i+1 ); } if (s1.charAt(i) == s2.charAt(j)) { return FindStr(i-1 ,j-1 ) + s1.charAt(i); } if (dfs(i,j) == dfs(i-1 ,j) + 1 ) { return FindStr(i-1 ,j) + s1.charAt(i); } else { return FindStr(i,j-1 ) + s2.charAt(j); } } }
1096. 花括号展开 II⭐
困难题,实际上就是找规律题。通过阅读题目,发现如下的规律:
如果当前字符为 { : - 如果前面有字母或者 },则先入栈 *,再入栈 {。 - 否则直接入栈 {。
如果当前字符为字母:找到以此字母为首的连续字母串,将这个串存入 Treeset 中。
如果当前字符为 , : - 如果栈顶为 *,为了保证乘法优先级更高,先取 Treeset 中的两个和此 * 进行处理……直到栈顶不是 *,再将 + 入栈。 - 否则直接将 + 入栈。
如果当前字符为 } :取栈顶运算符和两个 set 中的元素进行处理,直到遇到 {,直接将 { 出栈。
在遍历数组之后,如果栈中还有运算符,则依次出栈并处理。
这里注意运用的 TreeSet 数据结构,不能使用 HashSet,因为答案是有序的,只能使用 TreeSet 按照其默认顺序即可。
在操作时也要注意两个操作数的前后顺序,如果顺序相反则结果也会出错。
详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 class Solution { Deque<Character> dq = new LinkedList <Character>(); List<TreeSet<String>> lss = new ArrayList <TreeSet<String>>(); public void Option (char t) { if (t == '+' ) { TreeSet<String> set1 = lss.get(lss.size()-1 ); TreeSet<String> set2 = lss.get(lss.size()-2 ); set1.addAll(set2); lss.remove(lss.size()-1 ); lss.remove(lss.size()-1 ); lss.add(set1); } else if (t == '*' ) { List<String> list1 = new ArrayList <String>(lss.get(lss.size()-2 )); List<String> list2 = new ArrayList <String>(lss.get(lss.size()-1 )); TreeSet<String> set = new TreeSet <String>(); for (int j=0 ;j<list1.size();j++) { for (int k=0 ;k<list2.size();k++) { set.add((String)(list1.get(j)+list2.get(k))); } } lss.remove(lss.size()-1 ); lss.remove(lss.size()-1 ); lss.add(set); } } public List<String> braceExpansionII (String expression) { char [] ec= expression.toCharArray(); for (int i=0 ;i<ec.length;i++) { if (ec[i]=='{' ) { if (i>0 && (ec[i-1 ]=='}' || (ec[i-1 ]>='a' && ec[i-1 ]<='z' ))) { dq.addLast('*' ); } dq.addLast('{' ); } else if (ec[i]>='a' && ec[i]<='z' ) { TreeSet<String> ss = new TreeSet <>(); if (i>0 && ec[i-1 ]=='}' ) { dq.addLast('*' ); } int p = i+1 ; while (p<ec.length && (ec[p]>='a' && ec[p]<='z' )) { p++; } ss.add(expression.substring(i,p)); i = p - 1 ; lss.add(ss); } else if (ec[i]==',' ) { while (!dq.isEmpty() && dq.peekLast()=='*' ) { char t = dq.pollLast(); Option(t); } dq.addLast('+' ); } else if (ec[i]=='}' ) { while (!dq.isEmpty()) { char t = dq.pollLast(); if (t == '{' ) { break ; } Option(t); } } } while (!dq.isEmpty()) { char t = dq.pollLast(); if (t == '{' ) { break ; } Option(t); } return new ArrayList <String>(lss.get(0 )); } }
1124. 表现良好的最长时间段
中等题。
思路:找子串最大长度,表示满足一个子串,使得其左边和右边要么是边界、要么是不劳累,且其内部劳累大于不劳累。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Solution { public int longestWPI (int [] hours) { int n = hours.length; ArrayList<int []> al = new ArrayList <int []>(); al.add(new int []{-1 ,0 ,0 }); int l = 0 , nl = 0 ; for (int i=0 ;i<n;i++) { if (hours[i]>8 ) { l++; } else { nl++; al.add(new int []{i,l,nl}); } } al.add(new int []{n,l,nl}); int max = 0 ; for (int left=0 ; left<al.size(); left++) { for (int right=left+1 ; right<al.size(); right++) { int t = al.get(right)[1 ]-al.get(left)[1 ] , nt = al.get(right)[2 ]-al.get(left)[2 ]; if (al.get(right)[0 ]>=0 && al.get(right)[0 ]<n) { if (hours[al.get(right)[0 ]]>8 ) { t--; } else { nt--; } } if (t > nt) { max = Math.max(max,al.get(right)[0 ]-al.get(left)[0 ]-1 ); } } } return max; } }
1125. 最小的必要团队⭐
考察: #背包问题
题解:题解
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 class Solution { public long all; public long [][] memo; public int [] skill; public int [] smallestSufficientTeam(String[] req_skills, List<List<String>> people) { HashMap<String,Integer> hash = new HashMap <String,Integer>(); int m = req_skills.length; for (int i=0 ;i<m;i++) { hash.put(req_skills[i],i); } int n = people.size(); skill = new int [n]; for (int i=0 ;i<n;i++) { for (int j=0 ;j<people.get(i).size();j++) { skill[i] = skill[i] | 1 <<hash.get(people.get(i).get(j)); } } memo = new long [n][1 <<m]; for (int i=0 ;i<n;i++) { Arrays.fill(memo[i],-1 ); } all = (1L << n) - 1 ; long res = dfs(n-1 , (1 <<m)-1 ); int [] ans = new int [Long.bitCount(res)]; int j = 0 ; for (int i=0 ;i<n;i++) { if (((res >> i) & 1 ) == 1 ) { ans[j] = i; j++; } } return ans; } public long dfs (int i, int j) { if (j==0 ) { return 0 ; } if (i<0 ) { return all; } if (memo[i][j] != -1 ) { return memo[i][j]; } long res1 = dfs(i-1 ,j); long res2 = (1L << i) | dfs(i-1 , j & ~skill[i]); if (Long.bitCount(res1) < Long.bitCount(res2)) { memo[i][j] = res1; return res1; } else { memo[i][j] = res2; return res2; } } }
1137、第 N 个斐波那契数
image-20221015214614019
简单哦~
1138. 字母板上的路径
思路:构建一个 hash 表,存储每个字母的位置。在移动时一定要注意‘z’的判断,如果 if (start[0]+1==5 && start[1]>=1) 成立,则不能向下移动。
具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution { public String alphabetBoardPath (String target) { HashMap<Character,int []> hash = new HashMap <Character,int []>(); for (int i=0 ;i<26 ;i++) { hash.put((char )('a' +i),new int []{i/5 ,i%5 }); } StringBuilder sb = new StringBuilder (); char [] tc = target.toCharArray(); int [] start = new int []{0 ,0 }; for (int i=0 ;i<tc.length;i++) { int [] end = hash.get(tc[i]); while (start[0 ]!=end[0 ] || start[1 ]!=end[1 ]) { while (start[0 ]>end[0 ]) { start[0 ]--; sb.append("U" ); } while (start[0 ]<end[0 ]) { if (start[0 ]+1 ==5 && start[1 ]>=1 ) { break ; } else { start[0 ]++; } sb.append("D" ); } while (start[1 ]>end[1 ]) { start[1 ]--; sb.append("L" ); } while (start[1 ]<end[1 ]) { start[1 ]++; sb.append("R" ); } } sb.append("!" ); } return sb.toString(); } }
1139. 最大的以 1 为边界的正方形
中等题,思路比较简单。构造两个二维数据,分别记录以当前为止 (i, j) 的上方连续 1 和左方连续 1 的个数(都包括自己),然后取其中较小值,判断如果以这个值作为边的长度,另外两个顶点是否能满足条件,如果不满足则缩小边的长度,继续判断。
具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 class Solution { public int largest1BorderedSquare (int [][] grid) { int n = grid.length , m = grid[0 ].length; int res = 0 ; int [][] u1 = new int [n][m]; int [][] l1 = new int [n][m]; for (int i=0 ;i<n;i++) { l1[i][0 ] = grid[i][0 ]; if (grid[i][0 ]==1 ) { if (i>0 ) { u1[i][0 ] = u1[i-1 ][0 ] + 1 ; } else { u1[i][0 ] = 1 ; } res = 1 ; } } for (int i=0 ;i<m;i++) { u1[0 ][i] = grid[0 ][i]; if (grid[0 ][i]==1 ) { if (i>0 ) { l1[0 ][i] = l1[0 ][i-1 ] + 1 ; } else { l1[0 ][i] = 1 ; } res = 1 ; } } for (int i=1 ;i<n;i++) { for (int j=1 ;j<m;j++) { if (grid[i][j]==1 ) { u1[i][j] = u1[i-1 ][j] + 1 ; l1[i][j] = l1[i][j-1 ] + 1 ; int edge = Math.min(u1[i][j],l1[i][j]); for (int e=edge;e>0 ;e--) { if (u1[i][j-e+1 ]>=e && l1[i-e+1 ][j]>=e) { res = Math.max(res,e); break ; } } } } } return res*res; } }
1144. 递减元素使数组呈锯齿状
中等题,但是思路比较简单。只需要判断奇数下标比偶数下标的小 和偶数下标比奇数下标小 这两种情况下,需要的最小操作的次数。
小优化:当判断奇数下标比偶数下标的小 的情况时,只需要以奇数位置为判断点,如果它比两边大就缩小,如果比两边小则继续判断下一个奇数下标。当判断偶数下标比奇数下标小 的情况时,只需要判断偶数位置,如果比两边大则缩小,如果比两边小则继续判断下一个偶数下标。
详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { public int movesToMakeZigzag (int [] nums) { int n = nums.length; int res = 0 ; for (int i=1 ;i<n;i=i+2 ) { int k = nums[i]; if (i-1 >=0 && k>=nums[i-1 ]) { res += k - nums[i-1 ] + 1 ; k = nums[i-1 ] - 1 ; } if (i+1 <n && k>=nums[i+1 ]) { res += k - nums[i+1 ] + 1 ; k = nums[i+1 ] - 1 ; } } int total = 0 ; for (int i=0 ;i<n;i=i+2 ) { int k = nums[i]; if (i-1 >=0 && k>=nums[i-1 ]) { total += k - nums[i-1 ] + 1 ; k = nums[i-1 ] - 1 ; } if (i+1 <n && k>=nums[i+1 ]) { total += k - nums[i+1 ] + 1 ; k = nums[i+1 ] - 1 ; } } return Math.min(res,total); } }
1145. 二叉树着色游戏
题解:没有思路?一张图秒懂!(Python/Java/C++/Go) - 二叉树着色游戏 - 力扣(LeetCode)
看完题解,可能还有点懵。简单说就是以 x 为中心,可以分为三部分,分别是其左子树、右子树、还有剩下的部分。要如果 y 在第一步先占领了 x 四周的某一个部分,那么这一部分的所有元素都是 y 所有了。如果这一部分的元素个数比其他部分的元素个数之和还多,那么 y 是稳赢 !
所以正确的步骤应该是: 先利用 dfs 找到 x 的节点 node,再分别用 dfs 算出其左子树的元素个数、右子树的元素个数、其他部分的元素个数。选其中元素个数最多的部分作为 y 要占领的,如果这一部分的元素比其他部分的元素个数和都多,那么稳赢。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Solution { public TreeNode findn1 (TreeNode root, int x) { if (root == null || root.val == x) { return root; } if (findn1(root.left,x)!=null ) { return findn1(root.left,x); } else { return findn1(root.right,x); } } public int dfs (TreeNode root) { if (root==null ) { return 0 ; } return dfs(root.left) + dfs(root.right) + 1 ; } public boolean btreeGameWinningMove (TreeNode root, int n, int x) { root = findn1(root,x); int ln = dfs(root.left); int rn = dfs(root.right); int n2 = Math.max(ln, Math.max(rn, n - ln - rn - 1 )); if (n2 > n - n2) { return true ; } else { return false ; } } }
1147. 段式回文
困难题。因为找分的最多的 k 的数量,所以直接双指针+贪心即可。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { public int longestDecomposition (String text) { char [] tc = text.toCharArray(); int len = tc.length; int start = 0 , end = len - 1 ; int res = 0 ; int l = 1 ; while (start<=end) { int i = start; int j = end - l + 1 ; if (i>=j) { res++; break ; } boolean isFind = true ; for (;i<start+l;i++,j++) { if (tc[i]!=tc[j]) { isFind = false ; } } if (isFind) { res += 2 ; start = start + l; end = end - l; l = 1 ; } else { l++; } } return res; } }
1163. 按字典序排在最后的子串⭐⭐
考察: #双指针
困难题,使用暴力会超时。很抽象的解法。题解:题解
思路: 定义两个指针 i,j。i 表示最终结果的起始位置,j 表示开始判断的起始位置,定义 k 表示判断的偏移。每次判断 sc[i+k] 和 sc[j+k] : - 如果 sc[i+k]==sc[j+k],则 k++,这表示 [i, i+k] 和 [j, j+k] 的元素完全一样,但因为 i 一直在 j 之前,所以以 i 开头的字符串一定比以 j 结尾的字符串要大。 - 如果 sc[i+k]<sc[j+k],这表示 [i, i+k-1] 和 [j, j+k-1] 的元素完全一样,那么但是因为 j+k 位置的元素比 i+k 大,那么 i 就不能作为开头了,因为 j 作为开始位置的字符串一定是大于 i 作为开始位置的字符串的。那 i 直接等于 j 吗? 这也不对,因为 i 只是表示以 i 开头进行判断,可能 i+1、i+2……开头会有更大的结果,但是我们可以发现在 [i, i+k-1] 开头都比不过 [j, j+k-1] 开头,但是 i+k 开头我们能判断出来吗? 显然不能,所以我们将 i=i+k+1,j 如果大于新的 i 那就不变,如果 j 小于等于新的 i,那就 j=i+1,k 重置为 0。 - 如果 sc[i+k]>sc[j+k],这表示 [i, i+k-1] 和 [j, j+k-1] 的元素完全一样,但是 i+k 的元素更大,那么表示在选择 [i, i+k-1] 为开头都已经是已经是已知的最大,但是以 i 结束的一定就是最大的吗,不一定,我们需要重新将 j 置为 j+k+1,k 重置为 0,再对应 i 重新判断。
思路很抽象,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public String lastSubstring (String s) { int n = s.length(); char [] sc = s.toCharArray(); int i = 0 , j = 1 , k = 0 ; while (j+k<n) { if (sc[i+k]==sc[j+k]) { k++; } else if (sc[i+k] < sc[j+k]) { i = i+k+1 ; j = Math.max(i+1 , j); k = 0 ; } else { j = j+k+1 ; k = 0 ; } } return s.substring(i); } }
1172. 餐盘栈
考察: #小根堆 #栈
困难题。典型的思维题。题解:题解 。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 class DinnerPlates { private int capacity; private List<Deque<Integer>> stacks = new ArrayList <>(); private PriorityQueue<Integer> idx = new PriorityQueue <>(); public DinnerPlates (int capacity) { this .capacity = capacity; } public void push (int val) { if (!idx.isEmpty() && idx.peek()>=stacks.size()) { idx.clear(); } if (!idx.isEmpty()) { Deque<Integer> temp = stacks.get(idx.peek()); temp.push(val); if (temp.size() == capacity) { idx.poll(); } } else { Deque<Integer> temp = new LinkedList <>(); temp.push(val); stacks.add(temp); if (capacity > 1 ) { idx.add(stacks.size() - 1 ); } } } public int pop () { return popAtStack(stacks.size()-1 ); } public int popAtStack (int index) { if (index < 0 || index >= stacks.size() || stacks.get(index).isEmpty()) { return -1 ; } Deque<Integer> temp = stacks.get(index); if (temp.size() == capacity) { idx.add(index); } int val = temp.pop(); while (!stacks.isEmpty() && stacks.get(stacks.size()-1 ).isEmpty()) { stacks.remove(stacks.size()-1 ); } return val; } }
1187. 使数组严格递增⭐
考察: #动态规划
困难题,思路很重要。
题解:题解
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 class Solution { public int makeArrayIncreasing (int [] arr1, int [] arr2) { int MAXN = 1000000007 ; arr2 = Arrays.stream(arr2).sorted().distinct().toArray(); int n = arr1.length+2 ; int [] arr = new int [n]; arr[0 ] = -1 ; for (int i=0 ;i<n-2 ;i++) { arr[i+1 ] = arr1[i]; } arr[n-1 ] = MAXN; int [] dp = new int [n]; Arrays.fill(dp,MAXN); dp[0 ] = 0 ; for (int i=1 ;i<n;i++) { int j = lower_bound(arr2, arr[i]); for (int k = 1 ; k<=Math.min(i-1 , j); k++) { if (arr[i-k-1 ] < arr2[j-k]) { dp[i] = Math.min(dp[i], dp[i-k-1 ]+k); } } if (arr[i-1 ] < arr[i]) { dp[i] = Math.min(dp[i],dp[i-1 ]); } } int res = dp[n-1 ]; if (res>=MAXN) { return -1 ; } return res; } public int lower_bound (int [] arr2, int k) { int l = 0 , r = arr2.length; while (l<r) { int m = (l+r)/2 ; if (arr2[m]<k) { l = m + 1 ; } else { r = m; } } return l; } }
注意
1210. 穿过迷宫的最少移动次数
方法一、广度优先搜索
当遇到最少移动次数 ,首先想到的就是广度优先搜索 ,广度优先搜索使用的是队列 。
思路:以蛇尾巴为考察点,蛇有两种状态,当蛇为横着时,蛇只会向右、向下平行、顺时针旋转;当蛇为竖着时,蛇只会向下、向右平行、逆时针旋转。每次考察的时候需要判断当前位置当前状态是否第一次访问、是否越界、是否有墙。
详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 class Solution { public int minimumMoves (int [][] grid) { int n = grid.length; int [][][] dist = new int [n][n][2 ]; for (int i=0 ;i<n;i++) { for (int j=0 ;j<n;j++) { Arrays.fill(dist[i][j],-1 ); } } dist[0 ][0 ][0 ] = 0 ; Deque<int []> dq = new LinkedList <>(); dq.offerLast(new int []{0 , 0 , 0 }); while (!dq.isEmpty()) { int [] arr = dq.pollFirst(); int x = arr[0 ], y = arr[1 ], status = arr[2 ]; if (status==0 ) { if (y+2 < n && grid[x][y+2 ] == 0 && dist[x][y+1 ][0 ] == -1 ) { dist[x][y+1 ][0 ] = dist[x][y][0 ] + 1 ; dq.offerLast(new int []{x, y+1 , 0 }); } if (x+1 < n && grid[x+1 ][y] == 0 && grid[x+1 ][y+1 ] == 0 && dist[x+1 ][y][0 ] == -1 ) { dist[x+1 ][y][0 ] = dist[x][y][0 ] + 1 ; dq.offerLast(new int []{x+1 , y, 0 }); } if (x+1 < n && y+1 < n && grid[x+1 ][y] == 0 && grid[x+1 ][y+1 ]==0 && dist[x+1 ][y][1 ] == -1 ){ dist[x][y][1 ] = dist[x][y][0 ] + 1 ; dq.offerLast(new int []{x, y, 1 }); } } else { if (y+1 < n && grid[x][y+1 ] == 0 && grid[x+1 ][y+1 ] == 0 && dist[x][y+1 ][1 ] == -1 ) { dist[x][y+1 ][1 ] = dist[x][y][1 ] + 1 ; dq.offerLast(new int []{x, y + 1 , 1 }); } if (x+2 < n && grid[x+2 ][y] == 0 && dist[x+1 ][y][1 ] == -1 ) { dist[x+1 ][y][1 ] = dist[x][y][1 ] + 1 ; dq.offerLast(new int []{x + 1 , y, 1 }); } if (x+1 < n && y+1 < n && grid[x][y+1 ] == 0 && grid[x+1 ][y+1 ] == 0 && dist[x][y][0 ] == -1 ) { dist[x][y][0 ] = dist[x][y][1 ] + 1 ; dq.offerLast(new int []{x, y, 0 }); } } } return dist[n - 1 ][n - 2 ][0 ]; } }
1233. 删除子文件夹
比较简单,字符串的匹配问题。
1234. 替换子串得到平衡字符串⭐
非常难且恶心的一道题。
思路和很多题一样,找中间的最小子串,实际上要找的是两边的满足条件的最大子串。定义一个 left=0,先遍历 right 从头到尾,当遇到某个 right 不满足外面每个字母<=l 这个条件时,继续右移 left,一直更新最小的 min。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public int balancedString (String s) { char [] sc = s.toCharArray(); int l = sc.length/4 ; HashMap<Character,Integer> hash = new HashMap <Character,Integer>(); int [] remainder = new int [4 ]; hash.put('Q' ,0 ); hash.put('W' ,1 ); hash.put('E' ,2 ); hash.put('R' ,3 ); for (int i=0 ;i<sc.length;i++) { remainder[hash.get(sc[i])]++; } if (remainder[0 ]==l && remainder[1 ]==l && remainder[2 ]==l && remainder[3 ]==l) { return 0 ; } int left = 0 ; int min = sc.length; for (int right=0 ;right<sc.length;right++) { remainder[hash.get(sc[right])]--; while (left <=right && remainder[0 ]<=l && remainder[1 ]<=l && remainder[2 ]<=l && remainder[3 ]<=l) { min = Math.min(min,right-left+1 ); remainder[hash.get(sc[left])]++; left++; } } return min; } }
1237. 找出给定方程的正整数解
中等题,但是是阅读理解,唯一有用的是 Arrays.asList 的使用。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public List<List<Integer>> findSolution (CustomFunction customfunction, int z) { List<List<Integer>> res = new ArrayList <List<Integer>>(); for (int x=1 ;x<=z;x++) { for (int y=1 ;y<=z;y++) { if (customfunction.f(x,y)==z) { res.add(Arrays.asList(x,y)); } } } return res; } }
1238. 循环码排列⭐
方法一、格雷编码
详细见 89. 格雷编码 ,这里唯一的不同是定义了一个 start,其实不难想到先将整个二进制数组转化为格雷码数组,从 start 开始进行写入 res,再将前面部分补上即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public List<Integer> circularPermutation (int n, int start) { List<Integer> res = new ArrayList <>(); int [] gray = new int [1 <<n]; int j = 0 ; for (int i=0 ;i<(1 <<n);i++) { gray[i] = i ^ (i>>1 ); if (gray[i]==start) { j = i; } } for (int i=j;i<j+(1 <<n);i++) { int k = i%(1 <<n); res.add(k ^ (k>>1 )); } return res; } }
优化
但是这其实是可以优化的,标准的格雷码是从 0 开始,其实 start ⊕ 0 = start,所以直接将标准的格雷码⊕ start 即可。详细代码如下:
1 2 3 4 5 6 7 8 9 class Solution { public List<Integer> circularPermutation (int n, int start) { List<Integer> res = new ArrayList <>(); for (int i=0 ;i<(1 <<n);i++) { res.add(i ^ (i>>1 ) ^ start); } return res; } }
1247. 交换字符使得字符串相同
中等题,但容易想的很复杂。其实思路很简单,对于 s1 和 s2 不同的组合,有如下的处理情况:
51391c182efbd1f86b0c93246c15503.jpg
不难看出,只要有偶数个 xy 对或者偶数个 yx 对,都能通过一半的次数消去不同;对于一个 yx 对和一个 xy 对,需要 2 次处理。只有在处理完后还有 1 个 xy 对或者 yx 对时,表示无法完全消除。详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public int minimumSwap (String s1, String s2) { char [] s1c = s1.toCharArray(); char [] s2c = s2.toCharArray(); int xy = 0 , yx = 0 ; int res = 0 ; for (int i=0 ;i<s1c.length;i++) { if (s1c[i]!=s2c[i]) { if (s1c[i]=='x' ) { xy ++; } else { yx ++; } } } res += xy/2 ; xy = xy%2 ; res += yx/2 ; yx = yx%2 ; if (xy==0 && yx==0 ) { return res; } else if (xy==1 && yx==1 ) { return res+2 ; } else { return -1 ; } } }
1255. 得分最高的单词集合
困难题,使用的是 DFS 遍历每种情况。题解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Solution { int [] surplus = new int [26 ]; int [] score; String[] words; int res = 0 ; public void dfs (int i,int total) { if (i<0 ) { res = Math.max(res,total); return ; } dfs(i-1 ,total); boolean haveAll = true ; char [] wc = words[i].toCharArray(); for (char c : wc) { surplus[c-'a' ]--; if (surplus[c-'a' ]<0 ) { haveAll = false ; } total += score[c-'a' ]; } if (haveAll==true ) { dfs(i-1 ,total); } for (char c : wc) { surplus[c-'a' ]++; } } public int maxScoreWords (String[] words, char [] letters, int [] score) { this .words = words; this .score = score; for (int i=0 ;i<letters.length;i++) { surplus[letters[i]-'a' ]++; } dfs(words.length-1 ,0 ); return res; } }
1275、找出井字棋的获胜者
image-20221009160316785
常规简单题。
注意
1 2 3 String[] s = new String [3 ]; int [] i = new int [3 ]; boolean b = new boolean [3 ];
1326. 灌溉花园的最少水龙头数目⭐⭐
题解:题解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public int minTaps (int n, int [] ranges) { int [] right = new int [n+1 ]; for (int i=0 ;i<=n;i++) { int l = Math.max(0 ,i-ranges[i]); int r = i+ranges[i]; right[l] = Math.max(right[l],r); } int res = 0 ; int last = 0 ; int pre = 0 ; for (int i=0 ;i<n;i++) { last = Math.max(last,right[i]); if (last<=i) { return -1 ; } if (i==pre) { res++; pre = last; } } return res; } }
1376. 通知所有员工所需的时间
考察: #图 #dfs #bfs
方法一、邻接表+dfs
中等题,按部就班 dfs 即可。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { public List<List<Integer>> list; public int res = 0 ; public int [] informTime; public int numOfMinutes (int n, int headID, int [] manager, int [] informTime) { list = new ArrayList <>(); this .informTime = informTime; for (int i=0 ;i<n;i++) { list.add(new ArrayList <>()); } for (int i=0 ;i<n;i++) { if (manager[i]==-1 ) { continue ; } list.get(manager[i]).add(i); } for (int i=0 ;i<n;i++) { if (manager[i]==-1 ) { dfs(i, 0 ); } } return res; } public void dfs (int i, int time) { res = Math.max(res, time); if (list.get(i).size()==0 ) { return ; } for (int j=0 ; j<list.get(i).size(); j++) { dfs(list.get(i).get(j),time+informTime[i]); } } }
1441、用栈操作构建数组
image-20221015120642642
比较简单,注意ArrayList的构建:
1 ArrayList<String> al = new ArrayList <String>();
1487. 保证文件名唯一
利用 HashMap 记录,其 value 记录的值 v 表示[1, v]都是确定已经用过的了,下次可以直接从 v+1 进行查找。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public String[] getFolderNames(String[] names) { int n = names.length; String[] sc = new String [n]; HashMap<String,Integer> hash = new HashMap <String,Integer>(); for (int i=0 ;i<n;i++) { String name = names[i]; if (hash.containsKey(name)) { int v = hash.get(name); String temp = name + "(" + (v+1 ) + ")" ; while (hash.containsKey(temp)) { v++; hash.replace(name,v); temp = name + "(" + (v+1 ) + ")" ; } hash.put(temp,0 ); sc[i] = temp; } else { hash.put(name,0 ); sc[i] = name; } } return sc; } }
1567、乘积为正数的最长子数组长度⭐
本题是152、乘积最大子数组和的升级版。
方法一、常规思路
image-20221020214723468
本思路其实很简单。
先遍历一遍找到所有的0,在0与0之间找我们的答案。
在每个0和0之间,如果负数的个数是0或者偶数,则这个范围长度就是这个范围内的max;如果不是偶数,用双指针对应范围内的首尾位置,找到第一个负数,返回除去这个负数的范围剩下的范围的长度,就是这个范围的max。具体可以看代码理解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 for (int i=0 ;i<z.size()-1 ;i++) { int left = z.get(i)+1 ; int right = z.get(i+1 ); if (f.get(i)%2 ==0 ) { max = Math.max(max,right-left); } else { int j=left,t=right-1 ; while (j<=t) { if (j==t) { max = Math.max(max,j-left); break ; } if (nums[j]<0 ) { max = Math.max(max,right-j-1 ); break ; } if (nums[t]<0 ) { max = Math.max(max,t-left); break ; } j++; t--; } } }
方法二、动态规划⭐
image-20221020221331318
题解
这个很厉害!
建立两个数组zs和fs:
1 2 int [] zs = new int [n]; int [] fs = new int [n];
处理第一个元素:
1 2 3 4 5 6 7 8 9 if (nums[0 ]>0 ) { zs[0 ] = 1 ; } else if (nums[0 ]<0 ){ fs[0 ] = 1 ; } int max = zs[0 ];
对i>=1后的所有元素进行分析:
对于nums[i]<0:
1 2 3 4 5 6 7 8 9 10 11 if (nums[i]>0 ) { zs[i] = zs[i-1 ] + 1 ; if (fs[i-1 ]==0 ) { fs[i] = 0 ; } else { fs[i] = fs[i-1 ] + 1 ; } }
对于nums[i]<0:
1 2 3 4 5 6 7 8 9 if (nums[i]<0 ) { if (fs[i-1 ]==0 ) { zs[i] = 0 ; } else { zs[i] = fs[i-1 ] + 1 ; } fs[i] = zs[i-1 ] + 1 ; }
对于nums[i]==0:
1 2 3 4 if (nums[i]==0 ) { zs[i] = 0 ; fs[i] = 0 ; }
动态规划优化
image-20221020221215423
每次只使用了i-1的相关值,可以设置两个变量即可。
1574. 删除最短的子数组使剩余数组有序
考察: #滑动窗口
中等题,但是比较考验思路。首先找到左边非降序的子序列,left 为其结尾位置;再从右往左找右边部分的非降序子序列,right 为其开始位置。在 left 计算出来之后直接处理,如果 left 为结尾,那么表示全部元素非降序,那么直接返回 0。之后初始化窗口,也就是最大的窗口其实就是只要左边非降序部分的剩余部分或者只要右边非降序部分的剩余部分,取最小值 ,再依次从头移动 start,和 right 判断,如果 arr[start]<=arr[right],那么更新窗口;否则继续右移 right 直到 right 到 n,那么意思就是只要左边非降序部分。注意窗口更新时都需要与自己取 min。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public int findLengthOfShortestSubarray (int [] arr) { int n = arr.length; int left, right; for (left=0 ;left<n-1 ;left++) { if (arr[left] > arr[left+1 ]) { break ; } } if (left == n-1 ) { return 0 ; } for (right=n-1 ;right>0 ;right--) { if (arr[right] < arr[right-1 ]) { break ; } } System.out.println(left); System.out.println(right); int start = 0 ; int res = Math.min(n-left-1 , right); while (start <= left) { while (right < n && arr[start] > arr[right]) { right++; } res = Math.min(res, right-start-1 ); start++; } return res; } }
1590. 使数组和能被 P 整除
方法一、前缀和 + 余数
思路:我们要找到一个子数组,使得整个数组减去它就能被 p 整数,那么可以发现:子数组的余数等于整个数组的余数 ,我们需要找到一个最短的满足这个要求的子数组。
这里涉及到几个小优化。我们设置前缀,是防止在后面求子数组和的时候要进行遍历求和,而前缀和直接用收尾的前缀和相减即可得到。但是为了优化代码和简化操作,这里实际上是前缀和的余数,一样的,他们对比的标准也是 nums 的余数。在下面的两层 for 循环内,我们不应该一个设置头一个设置尾进行循环,而是应该一个设置长度,一个设置头,这样找到一个最小的子数组就可以直接返回了。
详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public int minSubarray (int [] nums, int p) { int n = nums.length; int [] pre = new int [n]; int y = 0 ; for (int i=0 ;i<n;i++) { y = (y + nums[i] % p) % p; pre[i] = y; } if (y == 0 ) { return 0 ; } for (int i=1 ;i<n;i++) { for (int start=0 ;start<=n-i;start++) { if (start==0 ) { if (pre[i-1 ]==y) { return i; } } else { if ((pre[start+i-1 ] - pre[start-1 ] + p)%p == y) { return i; } } } } return -1 ; } }
1599. 经营摩天轮的最大利润
中等题,读懂题目很重要!代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 class Solution { public int minOperationsMaxProfit (int [] customers, int boardingCost, int runningCost) { int surplus = 0 ; int ready = 0 ; int i = 1 ; int res = 0 ; int max = 0 ; for (i=1 ;i<=customers.length;i++) { surplus += customers[i-1 ]; if (surplus==0 ) { continue ; } else if (surplus<4 && surplus>0 ) { ready += surplus; surplus = 0 ; } else { ready += 4 ; surplus -= 4 ; } if (ready * boardingCost - i * runningCost > res) { res = ready * boardingCost - i * runningCost; max = i; } } i--; while (surplus > 0 ) { i++; if (surplus <= 4 ) { ready += surplus; if (ready * boardingCost - i * runningCost > res) { res = ready * boardingCost - i * runningCost; max = i; } break ; } else { ready += 4 ; surplus -= 4 ; if (ready * boardingCost - i * runningCost > res) { res = ready * boardingCost - i * runningCost; max = i; } } } if (res<=0 ) { return -1 ; } else { return max; } } }
1604. 警告一小时内使用相同员工卡大于等于三次的人
我们先用哈希表 hash 记录每个员工的所有打卡时间。注意这里需要将字符串的时间转为以分钟为单位的整形时间。
然后遍历哈希表,对于每个员工,我们先判断员工的打卡次数是否大于等于 3,如果不是,则跳过该员工。否则,我们将该员工的所有打卡时间按照时间先后排序,然后遍历排序后的打卡时间,判断下标距离为 2 的两个时间是否在同一小时内,如果是,则将该员工加入答案数组。
最后,将答案数组按照字典序排序,即可得到答案。
详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Solution { public List<String> alertNames (String[] keyName, String[] keyTime) { HashMap<String,ArrayList<Integer>> hash = new HashMap <String,ArrayList<Integer>>(); ArrayList<String> res = new ArrayList <String>(); for (int i=0 ;i<keyName.length;i++) { int t = Integer.parseInt(keyTime[i].substring(0 , 2 )) * 60 + Integer.parseInt(keyTime[i].substring(3 )); if (!hash.containsKey(keyName[i])) { ArrayList<Integer> al = new ArrayList <Integer>(); al.add(t); hash.put(keyName[i],al); } else { ArrayList<Integer> al = hash.get(keyName[i]); al.add(t); hash.replace(keyName[i],al); } } for (Map.Entry<String,ArrayList<Integer>> entry : hash.entrySet()) { String key = entry.getKey(); ArrayList<Integer> value = entry.getValue(); if (value.size()<=2 ) { continue ; } else { Collections.sort(value); for (int i=0 ;i<value.size()-2 ;i++) { if (value.get(i+2 )-value.get(i)<=60 ) { res.add(key); break ; } } } } Collections.sort(res); return res; } }
1605. 给定行和列的和求可行矩阵
贪心到底。每次 res[i][j] = Math.min(rowSum[i],colSum[j]) 并同时更新 rowSum[i] -= res[i][j] 和 colSum[j] -= res[i][j],一定存在解。详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int [][] restoreMatrix(int [] rowSum, int [] colSum) { int m = rowSum.length; int n = colSum.length; int [][] res = new int [m][n]; for (int i=0 ;i<m;i++) { for (int j=0 ;j<n;j++) { res[i][j] = Math.min(rowSum[i],colSum[j]); rowSum[i] -= res[i][j]; colSum[j] -= res[i][j]; } } return res; } }
1615. 最大网络秩
中等题,但是比较简单想到,利用邻接矩阵再小优化即可,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public int maximalNetworkRank (int n, int [][] roads) { int [][] map = new int [n][n+1 ]; for (int [] road : roads) { map[road[0 ]][road[1 ]] = 1 ; map[road[0 ]][n]++; map[road[1 ]][road[0 ]] = 1 ; map[road[1 ]][n]++; } int max = 0 ; for (int i=0 ;i<n;i++) { for (int j=i+1 ;j<n;j++) { if (map[i][j]==1 ) { max = Math.max(max, map[i][n]+map[j][n]-1 ); } else { max = Math.max(max, map[i][n]+map[j][n]); } } } return max; } }
1616. 分割两个字符串得到回文串⭐
考察: #回文串
中等题,但是想要在 O (n) 的时间复杂度里通过还是需要思考的。
我们可以分为两种情况,分别是 a 在 b 前面和 b 在 a 前面。现在讨论 a 在 b 前面的情况,我们可以直接进行判断 a 的头和 b 的尾是否相等,如果相等,则双指针继续往中间移动。如果直到两个指针遇到了,那就表示一定可以 true,如果在某一处两个指针不相等,那么开始判断 a 或者 b 这两个指针之间的元素是不是构成回文串,如果 a 或者 b 构成,那么也 true,如果不行则返回 false。讨论 b 在 a 前面的情况同理。
举个例子:
a = abcabaaaa
b = aaabdacba
按照上面的规则:
abc|abaaaa
aaabda|cba
我们需要判断 aba 或者 bda 是不是回文串,如本例,第一个 a 子串的 aba 是,那么最后可以划分:
abcaba |aaa
aaabda|cba
变成:abcabacba
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public boolean isPalindrome (String s, int i, int j) { char [] sc = s.toCharArray(); while (i<=j) { if (sc[i]!=sc[j]) { return false ; } i++; j--; } return true ; } public boolean check (String a, String b) { int i = 0 , j = a.length()-1 ; while (i<=j && a.charAt(i)==b.charAt(j)) { i++; j--; } return (isPalindrome(a,i,j) || isPalindrome(b,i,j)); } public boolean checkPalindromeFormation (String a, String b) { return (check(a,b) || check(b,a)); } }
1625. 执行操作后字典序最小的字符串
考察: #哈希 #队列
中等题,利用 Hash 存储和 queue 队列进行判断。每次从队列中取出一个字符串,对它做两种操作,如果已经存在于 hash 中,则不管;如果没有在 hash 中,则存入 hash,并再放入 queue 中。结束条件为:队列为空。
感悟:本题说明了,bfs 是利用队列进行完成,优化可以搭配 hash 进行查重(hash 可以是 hashset、treeset、hashmap 等等,只要包含 contains () 即可)
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class Solution { public String Add (String s, int a) { char [] sc = s.toCharArray(); int n = s.length(); for (int i=n-1 ;i>=0 ;i=i-2 ) { sc[i] = Integer.toString((sc[i] - '0' + a)%10 ).charAt(0 ) ; } return String.valueOf(sc); } public String Rotate (String s, int b) { int n = s.length(); return s.substring(n-b) + s.substring(0 ,n-b); } public String findLexSmallestString (String s, int a, int b) { int n = s.length(); String res = s; Deque<String> dq = new LinkedList <>(); TreeSet<String> ts = new TreeSet <>(); dq.addLast(s); ts.add(s); while (!dq.isEmpty()) { String t = dq.pollFirst(); if (res.compareTo(t)>0 ) { res = t; } String s1 = Add(t,a); if (!ts.contains(s1)) { ts.add(s1); dq.addLast(s1); } String s2 = Rotate(t,b); if (!ts.contains(s2)) { ts.add(s2); dq.addLast(s2); } } return res; } }
1626. 无矛盾的最佳球队⭐
考察: #动态规划 #最长递增子序列
基础题:300. 最长递增子序列
中等题,典型的动态规划题。
按照分数进行从小到大排序,分数相等的按照年龄进行从小到大排序。只需要从左向右遍历构建 dp,dp 数组表示以当前数结尾的最大和。每遍历一个数时,再从当前数左边那个数从右往左遍历,因为越往前面表示分数越小,如果满足条件那年龄应该是相等或者更小,所以如果前面的年龄更大则直接 continue,直到把前面遍历完,找到最大和放入这个数的 dp 下。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { public int bestTeamScore (int [] scores, int [] ages) { int n = scores.length; int [][] sl = new int [n][2 ]; for (int i=0 ;i<n;i++) { sl[i][0 ] = ages[i]; sl[i][1 ] = scores[i]; } Arrays.sort(sl, new Comparator <int []>() { public int compare (int [] a, int [] b) { if (a[1 ]!=b[1 ]) { return a[1 ]-b[1 ]; } return a[0 ]-b[0 ]; } }); int [] dp = new int [n]; dp[0 ] = sl[0 ][1 ]; int max = dp[0 ]; for (int i=1 ;i<n;i++) { int m = sl[i][1 ]; for (int j=i-1 ;j>=0 ;j--) { if (sl[j][0 ]>sl[i][0 ]) { continue ; } m = Math.max(m, sl[i][1 ] + dp[j]); } dp[i] = m; max = Math.max(max,dp[i]); } return max; } }
1630. 等差子数组
中等题,但是暴力模拟即可。
用到了数组的深拷贝 :System.arraycopy(原数组, 原数组起点, 新数组, 新数组起点, 长度);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public List<Boolean> checkArithmeticSubarrays (int [] nums, int [] l, int [] r) { int n = nums.length, m = l.length; ArrayList<Boolean> res = new ArrayList <>(); for (int i=0 ;i<m;i++) { int left = l[i], right = r[i]; int [] child = new int [right-left+1 ]; System.arraycopy(nums, left, child, 0 , right-left+1 ); Arrays.sort(child); int dis = child[1 ]- child[0 ]; boolean isTrue = true ; for (int j=1 ;j<child.length;j++) { if (child[j]-child[j-1 ] != dis) { isTrue = false ; break ; } } res.add(isTrue); } return res; } }
1637. 两点之间不包含任何点的最宽垂直区域
中等题,但是很简单。
1638. 统计只差一个字符的子串数目
方法一、暴力
直接遍历两个字符串的起点 i、j,再同时遍历相同长度,看看在这个长度内是否只有一个不同,如果成立,sum++。时间复杂度 \(O(n^3)\) 。
方法二、极致优化
题解:题解 ,复杂度 \(O(nm)\)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int countSubstrings (String s, String t) { char [] sc = s.toCharArray(); char [] tc = t.toCharArray(); int sum = 0 ; int n = s.length(), m = t.length(); for (int d = 1 -m; d<n; d++) { int i = Math.max(0 , d); int k0 = i - 1 , k1 = i - 1 ; for (; i < n && i - d < m; i++) { if (sc[i]!=tc[i-d]) { k0 = k1; k1 = i; } sum += k1 - k0; } } return sum; } }
1640、能否连接形成数组
image-20220922221718430
1641. 统计字典序元音字符串的数目
中等题,找规律即可。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int countVowelStrings (int n) { int [] map = new int [5 ]; map[0 ] = 1 ; for (int i=1 ;i<5 ;i++) { map[i] = 1 + map[i-1 ]; } if (n==1 ) { return map[4 ]; } int k = 2 ; while (k<=n) { for (int i=1 ;i<5 ;i++) { map[i] += map[i-1 ]; } k++; } return map[4 ]; } }
1652、拆炸弹
image-20220924104449440
负数的求余:
被除数的绝对值与除数绝对值取余的值即为余数绝对值,余数符号与被除数一致。
1653. 使字符串平衡的最少删除次数⭐
方法一、分割
中等题,遍历分隔符。在分隔符的左边删掉 b,右边(及其自己)删掉 a。记录删除最少时的位置所删除的次数。题解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 class Solution { public int minimumDeletions (String s) { char [] sc = s.toCharArray(); int n = sc.length; int [][] nn = new int [2 ][n]; int a = 0 ; for (int i=0 ;i<n;i++) { if (sc[i]=='a' ) { a++; } } for (int i=0 ;i<n;i++) { if (i==0 ) { if (sc[i]=='b' ) { nn[0 ][i] = 1 ; nn[1 ][i] = a; } else { nn[0 ][i] = 0 ; nn[1 ][i] = a; a--; } continue ; } if (sc[i]=='b' ) { nn[0 ][i] = nn[0 ][i-1 ] + 1 ; nn[1 ][i] = a; } else { nn[0 ][i] = nn[0 ][i-1 ]; nn[1 ][i] = a; a--; } } int res = n; for (int i=0 ;i<=n;i++) { if (i==0 ) { res = Math.min(res,nn[1 ][i]); } else if (i==n) { res = Math.min(res,nn[0 ][i-1 ]); } else { res = Math.min(res,nn[0 ][i-1 ] + nn[1 ][i]); } } return res; } }
方法二、动态规划
考虑 s 的最后一个字母:
如果它是‘b’,则无需删除,问题规模缩小,变成「使 s 的前 n−1 个字母平衡的最少删除次数」。
如果它是‘a’:
删除它,则答案为「使 s 的前 n−1 个字母平衡的最少删除次数」加上 1。
保留它,那么前面的所有‘b’ 都要删除;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int minimumDeletions (String s) { int bn = 0 ; int res = 0 ; for (char c : s.toCharArray()) { if (c == 'b' ) { bn++; } else { res = Math.min(res + 1 , bn); } } return res; } }
1658. 将 x 减到 0 的最小操作数⭐
Pasted image 20230107130117
本题较难,需要转换思路。
把问题转换成nums中移除一个最长的子数组,使得剩余元素的和为 x。 换句话说,要从nums中找最长的子数组,其元素和等于 s−x,这里 s为 nums所有元素之和。 最后答案为 nums 的长度减去最长子数组的长度。
题中想要找到两端最短和,转换思路就是找到 nums 的满足条件的最长子数组 。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public int minOperations (int [] nums, int x) { int snums = Arrays.stream(nums).sum() - x; int n = nums.length; int left = 0 ; int sum = 0 ; int res = -1 ; if (snums<0 ) { return -1 ; } for (int right=0 ;right<n;right++) { sum += nums[right]; while (sum > snums) { sum -= nums[left]; left++; } if (sum == snums) { res = Math.max(res, right-left+1 ); } } if (res==-1 ) { return -1 ; } else { return n-res; } } }
1 2 3 求数组和: ```java sum = Arrays.stream(nums).sum();
1663. 具有给定数值的最小字符串
Pasted image 20230126163114
比较简单的题目~
1664. 生成平衡数组的方案数
Pasted image 20230128112355
比较简单的一题,维护两个数组,动态区分正向和反向的偶数和和奇数和。
1669. 合并两个链表
Pasted image 20230131093641
比较简单。唯一要注意的是指针在没有定义时为 null,而不是 NULL。
1694、重新格式化电话号码
方法一、常规逻辑
image-20221001102615465
先除去多余字符,再插入'-'
substring()使用方法:
1 2 3 s = s.substring(1 ,5 ); s = s.substring(5 );
replace()使用方法
方法二、StringBuilder方法
image-20221001115656084
String内容是不可变的,StringBuilder内容是可变的
StringBuilder处理字符串性能比String好
StringBuilder的创建
1 StringBuilder sb = new StringBuilder ();
StringBuilder的尾部插入
StringBuilder的中间插入
StringBuilder的长度
StringBuilder的删除
StringBuilder转为String
1 String s = sb.toString();
String转为StringBuilder
1 StringBuilder sb = new StringBuilder (s);
1700、无法吃午餐的学生数量
方法一、队列
image-20221019105203982
使用队列进行判断,容易想到。
方法二、优化
image-20221019105321277
披萨和学生数量相同,所以不会出现披萨没了学生还有的情况。
所以除了会因为卡在第一个披萨的情况而结束,就只可能是成功的状态而结束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int countStudents (int [] students, int [] sandwiches) { int [] nums = new int [2 ]; for (int i=0 ;i<students.length;i++) { nums[students[i]]++; } for (int i=0 ;i<sandwiches.length;i++) { nums[sandwiches[i]]--; if (nums[sandwiches[i]]==-1 ) { return sandwiches.length - i; } } return 0 ; } }
1739、放置盒子🌟
题解:放置盒子
方法一、数学找规律
Pasted image 20221225135724
Pasted image 20221225135834
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public int minimumBoxes (int n) { int sum = 0 ; int res = 0 ; int c = 0 ; int cs = 0 ; for (int i=1 ;i<=n;i++) { cs += i; sum += cs; if (sum == n) { res = i*(i+1 )/2 ; return res; } else if (sum > n) { sum -= cs; c = i-1 ; res = c*(c+1 )/2 ; break ; } } for (int j=1 ;j<=c+1 ;j++) { sum += j; if (sum == n) { res += j; return res; } else if (sum > n) { res += j; return res; } } return res; } }
方法 2、开平方和开立方优化
详细看题解,不太会写……
1732、找到最高海拔
Screenshot_20221119_004204_com.huawei.browser
简单题,没啥意思~
1750. 删除字符串两端相同字符后的最短长度
Pasted image 20221228112432
很简单的题目,意义不大。
1752、检查数组是否经排序和轮转得到
Pasted image 20221127015201
简单题,循环查数组,实现起来比较巧妙,判断是否有两个增区间,如果有则比较首位的大小;如果多于 2 个增区间,那就错;如果只有一个增区间,那就对。
1753. 移除石子的最大得分
Pasted image 20221221110600
比较简单的一题,一共就三个数,每次动态选取最大的两个减 1 即可。
1754、构造字典序最大的合并字符串⭐
Pasted image 20221224152639
本题考查对 String 的 compareTo 的灵活运用,需要熟知 compareTo 的核心。compareTo 可以比较两个字符串,字典序和长度都会影响结果。
本题不能按照字符单独比较,因为如果出现:
b a a b b a a a 按照字符比较,是 b a a b b a a a;但是正确的答案应该是 b b a a b a a a。
因此只能用 compareTo 进行比较。每次提取较大的那个字符串的第一个字符,再继续比较,这样既考虑了大小比较,又考虑了上述例子中的情况。
按照 compareTo 的情况: 分别出现:b (1) b (2) a (1) a (1) b (1) a (2) a (2) a (2)
详细代码如下,代码不难,思想难:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public String largestMerge (String word1, String word2) { int n1 = word1.length(), n2 = word2.length(); int i = 0 , j = 0 ; StringBuilder sb = new StringBuilder (); while (i<n1 && j<n2) { if (word1.substring(i).compareTo(word2.substring(j))>0 ) { sb.append(word1.charAt(i)); i++; } else { sb.append(word2.charAt(j)); j++; } } if (i<n1) { sb.append(word1.substring(i)); } if (j<n2) { sb.append(word2.substring(j)); } return sb.toString(); } }
1759、统计同构子字符串的数目
Pasted image 20221226002129
简单题,注意 long 的定义和相加。
res += (long)(i-start)*(i-start+1)/2%MOD;
详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { int MOD = 1000000007 ; public int countHomogenous (String s) { char [] sc = s.toCharArray(); int n = sc.length; int start = 0 ; long res = 0 ; for (int i=1 ;i<n;i++) { if (sc[i]==sc[start]) { continue ; } else { res += (long )(i-start)*(i-start+1 )/2 %MOD; start = i; } } res += (long )(n-start)*(n-start+1 )/2 %MOD; return (int )res; } }
1760、袋子里最少数目的球⭐
Pasted image 20221220135905
二分法,思路较难。
题目想要求得最小的最大值 ,这很容易想到二分法 。但是二分法要怎么用呢,这里就是二分遍历。每次找到一个数,如果它成立则往一边二分,否则往另一边二分。
观察题目发现,在只有“操作至多 maxOperations 次”这么一个条件的限制下,很难把握把袋子平均分成几份才是最好的。所以我们考虑人为增加一个条件,即“每个袋子至多有 y 个球”。
此时问题便转化成了“给定 maxOperations 次操作次数,能否可以使得单个袋子里球数目的最大值不超过 y ”。
从 1 和 nums 数组的最大值之间二分。如果 y 可以实现,我们尝试继续压低 y,即将 right 更新为 y-1,然后继续二分;如果 y 不能实现,我们将 y 提高,即将此时 left 更新为 y+1,然后继续二分。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int minimumSize (int [] nums, int maxOperations) { Arrays.sort(nums); int left = 1 , right = nums[nums.length-1 ]; while (left < right) { int r = (left + right)/2 ; int opt = 0 ; for (int num : nums) { opt += (num-1 ) / r; } if (opt>maxOperations) { left = r+1 ; } else { right = r; } } return right; } }
1764. 通过连接另一个数组的子数组得到一个数组⭐⭐
方法一、KMP⭐
经典 KMP,考察的就是 KMP 的计算和 next 数组的构建。
KMP 算法的实现步骤其实就是:text(长) 和 pattern(短) 数组进行匹配,当在 text[i] 和 pattern[j] 位置发生了第一次的不相同,但是可以清楚 pattern[0]~pattern[j-1] 的位置和 text[j] 往前 j 个元素都是相同的。KMP 要求 i 不动,但 j 要进行移动到 new_j,使得 pattern[0]~pattern[new_j] 与 text[i] 前 new_j 个元素完全相等。
现在如何从 j 找到这个 new_j 就是新的问题。所以需要构建一个 next 数组,假设 new_j=next[j] ,那么 0~new_j-1 间的 new_j 个元素与 j 之前的 new_j 个元素都完全相同。此时可以直接比较 text[i] 和 next[new_j],这表示已经匹配成功 new_j 个元素了。
注意:next 数组的长度和 group 的长度一致。默认 next[0]=-1,每一个next[j] 是为了求出 next[j+1]。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class Solution { private int [] GetNext(int [] group) { int n = group.length; int [] next = new int [n]; next[0 ] = -1 ; int j = 0 , t = next[0 ]; while (j<n-1 ) { if (t==-1 || group[t]==group[j]) { t++; j++; next[j] = t; } else { t = next[t]; } } return next; } private int KMP (int [] nums, int [] group, int start) { int [] next = GetNext(group); int n = nums.length, m = group.length; int j = 0 ; while (start<n && j<m) { if (j==-1 || nums[start]==group[j]) { start++; j++; } else { j = next[j]; } } if (j==m) { return start; } else { return -1 ; } } public boolean canChoose (int [][] groups, int [] nums) { int start = 0 ; for (int [] group : groups) { start = KMP(nums, group, start); if (start == -1 ) { return false ; } } return true ; } }
1781、所有子字符串美丽值之和
Pasted image 20221224200703
暴力解法。循环固定左端和右端,再遍历这一段,找到最大和最小出现的次数,加入 res,3 个嵌套 for。意义不大。
1784、检查二进制字符串字段
方法一、正常思路
image-20221003091100144
方法二、一行代码
image-20221003091323854
本题可以转化为只能出现一次10,也就是只能出现111111000000这种情况。
1785. 构成特定和需要添加的最少元素
虽然是中等题,但是很简单。只需要分类讨论:
2191a50b89ff4f95a40b9bad3641d54.jpg
根据规律,不难发现,其实只要将 dis 都转为正数,循环与 limit 比较即可。或者直接进行除法操作,注意存在什么都不用操作的可能。代码如下:
1 2 3 4 5 6 7 8 9 10 11 class Solution { public int minElements (int [] nums, int limit, int goal) { long dis = 0 ; long res = 0 ; for (int n : nums) { dis += n; } dis = Math.abs(dis-goal); return (int )((dis + limit - 1 ) / limit); } }
1790、仅执行一次字符串交换能否使两个字符串相等
image-20221011221427327
思路简单,不多说了。
1792. 最大平均通过率⭐
中等题,但是不太好想,如果有思路就会很简单。
首先我考虑的时背包问题,但是一顿操作后并不能实现。根据 题解 ,转换了思路,每次加一个学生,选择增量最大的确定永久增加,循环判断即可。
这里考察大根堆 的构建。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public double maxAverageRatio (int [][] classes, int extraStudents) { int c = classes.length; PriorityQueue<double []> q = new PriorityQueue <double []>((a,b) -> { double x = (a[0 ]+1 )/(a[1 ]+1 ) - a[0 ]/a[1 ]; double y = (b[0 ]+1 )/(b[1 ]+1 ) - b[0 ]/b[1 ]; return Double.compare(y, x); }); for (int [] cl : classes) { q.offer(new double []{(double )cl[0 ],(double )cl[1 ]}); } for (int i=0 ;i<extraStudents;i++) { double [] temp = q.poll(); temp[0 ]++; temp[1 ]++; q.offer(temp); } double res = 0 ; while (!q.isEmpty()) { double [] temp = q.poll(); res += temp[0 ]/temp[1 ]; } return res/c; } }
tips
double 元素比较大小,使用 Double.compare(x,y),如果 x>y 则返回正数,如果 y>x 则返回负数。
1797. 设计一个验证系统
普通工程题,意义不大。
注意:hashmap 在 put 时如果有相同 key,value 会进行覆盖。
1798. 你能构造出连续值的最大数目⭐
方法一、动态规划+贪心算法
利用动态规划的思想。先使 coins 升序,我们假设 res 之前的数(连续的)都能凑出来,那么在考察 res 时,如果 coins[i] 能小于 res,那么 coins[i] 加上 res-coins[i] 就可以构造出来 res,其中 res-coins[i] 是一定能构造出来的;如果 coins[i]大于 res,那么没有办法通过加上什么数达到当前需要的 res。
具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int getMaximumConsecutive (int [] coins) { Arrays.sort(coins); int res = 1 ; for (int c : coins) { if (c>res) { break ; } else { res += c; } } return res; } }
1799. N 次操作后的最大分数和⭐
Pasted image 20221222132834
动态规划+位运算压缩存储
难题!!!
Pasted image 20221222133224
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Solution { public int gcd (int x, int y) { if (y == 0 ) { return x; } return gcd(y, x%y); } public int maxScore (int [] nums) { int l = nums.length; int [][] gcdmap = new int [l][l]; for (int i=0 ;i<l;i++) { for (int j=0 ;j<l;j++) { gcdmap[i][j] = gcd(nums[i],nums[j]); } } int len = 1 << l; int [] res = new int [len]; for (int k=0 ;k<len;k++) { int cnt = Integer.bitCount(k); if (cnt%2 ==0 ) { for (int i=0 ;i<l;i++) { if (((k>>i) & 1 ) == 1 ) { for (int j=i+1 ;j<l;j++) { if (((k>>j) & 1 ) ==1 ) { res[k] = Math.max(res[k],res[k^(1 <<i)^(1 <<j)]+cnt/2 *gcdmap[i][j]); } } } } } } return res[len-1 ]; } }
1800、最大升序子数组和
image-20221007095515941
思路简单,只要判断当前值是否比上一个值大,如果大,那以当前值结尾的最大升序子数组和 = 以上一个值为结尾的最大升序子数组和 + 当前值的大小。否则值为本身。
1801、积压订单中的订单总数
方法一、HashMap 搜索+ArrayList 排序
Pasted image 20230102130246
思路比较简单,设置两个 hash 和两个 list,list 用来存储值,分别从小到大排序和从大到小排序,hash 可以根据 list 的值作为 key 查到对应的数量,再进行处理。涉及的 Java 基础语法比较多。
需要注意 (int)(res%MOD) 才是正确的返回,(int)res%MOD 不对。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 class Solution { public int getNumberOfBacklogOrders (int [][] orders) { int MOD = 1000000007 ; HashMap<Integer,Integer> buy = new HashMap <Integer,Integer>(); HashMap<Integer,Integer> sell = new HashMap <Integer,Integer>(); ArrayList<Integer> buylist = new ArrayList (); ArrayList<Integer> selllist = new ArrayList (); for (int [] or : orders) { if (or[2 ]==0 ) { while (selllist.size()>0 ) { if (or[0 ]>=selllist.get(0 ) && or[1 ]>0 ) { int key = selllist.get(0 ); int value = sell.get(key); if (or[1 ]>=value) { or[1 ] -= value; sell.remove(key); selllist.remove(0 ); } else { sell.replace(key,value-or[1 ]); or[1 ]=0 ; break ; } } else { break ; } } if (or[1 ]>0 ) { buy.put(or[0 ],(buy.getOrDefault(or[0 ],0 )+or[1 ])%MOD); buylist.add(or[0 ]); Collections.sort(buylist, new Comparator <Integer>() { public int compare (Integer n1, Integer n2) { return n2-n1; } }); } } else { while (buylist.size()>0 ) { if (or[0 ]<=buylist.get(0 ) && or[1 ]>0 ) { int key = buylist.get(0 ); int value = buy.get(key); if (or[1 ]>=value) { or[1 ] -= value; buy.remove(key); buylist.remove(0 ); } else { buy.replace(key,value-or[1 ]); or[1 ]=0 ; break ; } } else { break ; } } if (or[1 ]>0 ) { sell.put(or[0 ],(sell.getOrDefault(or[0 ],0 )+or[1 ])%MOD); selllist.add(or[0 ]); Collections.sort(selllist); } } } long res = 0 ; for (int num : sell.values()) { res += (long )num; } for (int num : buy.values()) { res += (long )num; } return (int )(res%MOD); } }
方法二、题解🐶
Pasted image 20230102131042
题解
1802. 有界数组中指定下标处的最大值
Pasted image 20230104230948
根据题目要求,其实只要从 index 左右递减,每次减少 1,如果还有多余的空位,全部设置为 1 即可。(getsum ()) 函数)
0631e4632dfc62297ea12dadffdee8a
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public long getsum (long x, int len) { if (x < len) { return (long )((1 +x)*x/2 + (len-x)); } else { return (long )((x-len+1 +x)*len/2 ); } } public int maxValue (int n, int index, int maxSum) { int llen = index + 1 , rlen = n - index; int left = 1 , right = maxSum; while (left < right) { int mid = (left + right + 1 )/2 ; if ((getsum(mid,llen)+getsum(mid,rlen)-mid)<=maxSum) { left = mid; } else { right = mid-1 ; } } return left; } }
1806、还原排列的最少操作步数
Pasted image 20230109015856
中等题,通过列举多个情况找规律即可。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int reinitializePermutation (int n) { int res = 0 ; int loc = 1 ; do { if (loc*2 >=1 && loc*2 <n-1 ) { loc = loc * 2 ; res++; } else { loc = (loc * 2 - n) + 1 ; res++; } }while (loc!=1 ); return res; } }
1807. 替换字符串中的括号内容
Pasted image 20230112200229
hash 的和 stringbuidler 的结合应用。比较简单~
1813、句子相似性 III⭐
Pasted image 20230116024951
简单但是需要考虑的比较多的题目。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class Solution { public static boolean isCon (String s1,String s2) { String[] str1 = s1.split(" " ); String[] str2 = s2.split(" " ); int n1 = str1.length; int n2 = str2.length; int i = 0 ; if (str1[i].equals(str2[i])) { for (i=0 ;i<n2;i++) { if (!str1[i].equals(str2[i])) { break ; } } if (i==n2) { return true ; } } int j = n1-1 ; int t = n2-1 ; if (str1[j].equals(str2[t])) { for (j=n1-1 ,t=n2-1 ;t>=i;t--) { if (!str1[j].equals(str2[t])) { return false ; } j--; } } if (i==t+1 ) { return true ; } return false ; } public boolean areSentencesSimilar (String sentence1, String sentence2) { int n1 = sentence1.length(); int n2 = sentence2.length(); if (n1<n2) { return isCon(sentence2,sentence1); } else if (n1>n2) { return isCon(sentence1,sentence2); } else { return sentence1.equals(sentence2); } } }
1814. 统计一个数组中好对子的数目
Pasted image 20230117122523
比较难想也比较难操作的一题。核心思路:
nums[i] + rev(nums[j]) == nums[j] + rev(nums[i]) 与 nums[i] - rev(nums[i]) == nums[j] - rev(nums[j]) 等价
此外,还需要注意在乘法时 long 的乘法,(long) 只能将紧跟着的变量或者括号强制转化为 long,如果后面跟的是括号,括号里的内容会先按照原来类型进行运算,再强转。
所以:res = (res + (long)((i-left)*(i-left-1)/2)%MOD); 可能会越界,但是 res = (res + (long)(i-left)*(i-left-1)/2)%MOD; 不会
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public int rev (int n) { int res = 0 ; while (n!=0 ) { res = res * 10 + n%10 ; n = n/10 ; } return res; } public int countNicePairs (int [] nums) { int MOD = 1000000007 ; int n = nums.length; for (int i=0 ;i<n;i++) { nums[i] = nums[i] - rev(nums[i]); } Arrays.sort(nums); int left = 0 ; long res = 0 ; for (int i=0 ;i<n;i++) { if (nums[i]!=nums[left]) { if (i-left > 1 ) { res = (res + (long )(i-left)*(i-left-1 )/2 )%MOD; } left = i; } } if (left != n-1 ) { res = (res + (long )(n-left)*(n-left-1 )/2 )%MOD; } return (int )(res%MOD); } }
1817. 查找用户活跃分钟数
Pasted image 20230120125215
虽然是中等题,但是很简单,只要注意使用数组的自定义排序 即可。
1819. 序列中不同最大公约数的数目⭐
困难题,原本思路是使用 list 和 hashmap 对 nums 的每对元素求最大公约数,但是超时。此方法不可行。注意:根据题目要求,每个单独的元素也算答案。
0 和任何数 x 的最大公约数都是 x
方法一、枚举
Pasted image 20230114133455
需要转换思路,枚举以 i 为因子的所有数,如果有 2 个或 2 个以上的数存在,判断这些数的最大公约数是不是 i,如果是,那么 res++。注意,每个单独元素也算答案。
详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public int gys (int a,int b) { while (b!=0 ) { int temp = a%b; a = b; b = temp; } return a; } public int countDifferentSubsequenceGCDs (int [] nums) { Arrays.sort(nums); int max = nums[nums.length-1 ]; boolean [] ishas = new boolean [max+1 ]; int res = 0 ; for (int n : nums) { ishas[n] = true ; } for (int i=1 ;i<=max;i++) { int g = 0 ; for (int j=i;j<=max;j=j+i) { if (ishas[j]) { g = gys(j,g); } } if (g==i) { res++; } } return res; } }
方法二、枚举优化
Pasted image 20230114133600
在嵌套的 for 的判断中增加:g!=i,这表示如果出现 g==i 的情况,也就是出现 2 个或 2 个以上的数,使得 i 作为最大公约数,那么直接可以跳出循环,返回 i。
详细代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public int gys (int a,int b) { while (b!=0 ) { int temp = a%b; a = b; b = temp; } return a; } public int countDifferentSubsequenceGCDs (int [] nums) { Arrays.sort(nums); int max = nums[nums.length-1 ]; boolean [] ishas = new boolean [max+1 ]; int res = 0 ; for (int n : nums) { ishas[n] = true ; } for (int i=1 ;i<=max;i++) { int g = 0 ; for (int j=i;j<=max && g!=i;j=j+i) { if (ishas[j]) { g = gys(j,g); } } if (g==i) { res++; } } return res; } }
方法三、最强优化
1819题解
1822、数组元素积的符号
image-20221027101133285
简单模拟题……
1824. 最少侧跳次数⭐
方法一、动态规划(二维)
Pasted image 20230121124109
Pasted image 20230121124138
简而言之,在 i 点的 j 跑道上的最小侧翻次数来源于两个值,一个是 i-1 点的 j 跑道,另一个是 i 点的其他跑道。 这里需要注意,在求 i 点的其他跑道的值的时候,这些值也是来源于 i-1 点的相应的跑道,所以需要分为两步进行,先计算由 i-1 点的 j 跑道计算得到的,再计算由 i 点的其他跑道计算得到的。i 点的其他跑道实际上就是 i 点所有跑道的 dp 最小值。
详细代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public int minSideJumps (int [] obstacles) { int n = obstacles.length; int MAX = n+10 ; int [][] dp = new int [n][3 ]; dp[0 ][0 ] = 1 ; dp[0 ][1 ] = 0 ; dp[0 ][2 ] = 1 ; for (int i=1 ;i<n;i++) { for (int j=0 ;j<3 ;j++) { if (obstacles[i]==j+1 ) { dp[i][j] = MAX; } else { dp[i][j] = dp[i-1 ][j]; } } int min_step = Math.min(dp[i][0 ],Math.min(dp[i][1 ],dp[i][2 ])); for (int j=0 ;j<3 ;j++) { if (obstacles[i]!=j+1 ) { dp[i][j] = Math.min(dp[i][j],min_step+1 ); } } } return Math.min(dp[n-1 ][0 ],Math.min(dp[n-1 ][1 ],dp[n-1 ][2 ])); } }
方法二、动态规划优化(一维)
Pasted image 20230121125056
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public int minSideJumps (int [] obstacles) { int n = obstacles.length; int MAX = n+10 ; int [] dp = {1 ,0 ,1 }; for (int i=1 ;i<n;i++) { for (int j=0 ;j<3 ;j++) { if (obstacles[i]==j+1 ) { dp[j] = MAX; } else { dp[j] = dp[j]; } } int min_step = Math.min(dp[0 ],Math.min(dp[1 ],dp[2 ])); for (int j=0 ;j<3 ;j++) { if (obstacles[i]!=j+1 ) { dp[j] = Math.min(dp[j],min_step+1 ); } } } return Math.min(dp[0 ],Math.min(dp[1 ],dp[2 ])); } }
1828. 统计一个圆中点的数目
方法一、暴力
Pasted image 20230124124708
意义不大的中等题。
方法二、压缩搜索
1828题解
1913、句子相似性III
image-20221009155406386
对于题目只有三种情况:
A......
A
......A
A
A......B
AB
首先要确定谁是长的句子,谁是短的句子。
可以从第一个单词进行匹配,如果匹配上了,说明一定是1、3情况,如果遇到匹配不成功的,则从各自的最后一个单词进行匹配,如果最后两个指针遇上了,则说明成立。结束的条件是短的字符串的做指针和右指针能遇上(i = t + 1)。具体思路可以看代码,更容易懂。
1971. 寻找图中是否存在路径
Pasted image 20221219132739
典型 并查集 题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 ```java class Solution { public int find (int [] bc, int x) { if (bc[x]!=x) { bc[x] = find(bc,bc[x]); } return bc[x]; } public void union (int [] bc, int x, int y) { if (find(bc,x)==find(bc,y)) { return ; } else { bc[find(bc,x)] = find(bc,y); } } public boolean validPath (int n, int [][] edges, int source, int destination) { int [] bc = new int [n]; for (int i=0 ;i<n;i++) { bc[i] = i; } for (int i=0 ;i<edges.length;i++) { union(bc,edges[i][0 ],edges[i][1 ]); } if (find(bc,source)==find(bc,destination)) { return true ; } return false ; } }
2011. 执行操作后的变量值
Pasted image 20221223105336
简单题,没意思~
2016、增量元素之间的最大差值
image-20221009153057630
本题思路很巧妙,目的是求一对升序的两个数,而且是差值最大的。
可以将左端点定为左边第一个数,开始遍历。
如果右边的某个值比左端点大,则记录他们之间的差值,并和已经保存的差值比较,保存更大的那个。
如果右边的某个值比左端点小,那么这个值就成为了新的左端点,然后再继续遍历。
比如 3...9...2...10
以3作为左端点的时候,当遇到9时,res = 6
再往后遍历遇到比左端点3更小的2,那么更小左端点为2,继续往后遍历,遇到更大的10,发现差值为8,比6大,则更新res = 8
2027. 转换字符串的最少操作次数
Pasted image 20221227115342
简单题,不多说。
2130、链表最大孪生和
方法一、常规遍历+数组
image-20221014091817803
对链表遍历两次,第一次记录长度n;构建一个n/2长度的数据,第二次遍历的前一半直接放在数组中,后一半对数组从后往前分别加上,最后取得数组的最大值。
方法二、双端队列
image-20221014092114561
遍历一次链表,将所有的数值都按顺序加入队列中。对队列进行操作,如果队列不为空!dq.isEmpyt(),那么每次pollFirst()和pollLast(),并相加,用max记录最大的和,最后输出。
2032. 至少在两个数组中出现的值
简单题,但涉及到动态数组 和哈希表 。
方法一、HashMap+位记录
Pasted image 20221229122222
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public List<Integer> twoOutOfThree (int [] nums1, int [] nums2, int [] nums3) { HashMap<Integer,Integer> hash = new HashMap <Integer,Integer>(); ArrayList al = new ArrayList (); for (int i=0 ;i<nums1.length;i++) { hash.put(nums1[i], 1 ); } for (int i=0 ;i<nums2.length;i++) { hash.put(nums2[i], hash.getOrDefault(nums2[i],0 ) | 2 ); } for (int i=0 ;i<nums3.length;i++) { hash.put(nums3[i], hash.getOrDefault(nums3[i],0 ) | 4 ); } for (Map.Entry<Integer,Integer> entry : hash.entrySet()) { int v = entry.getValue(); if ((v & (v-1 )) != 0 ) { al.add(entry.getKey()); } } return al; } }
方法二、HashMap+排序+数量记录
Pasted image 20221229122416
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public List<Integer> twoOutOfThree (int [] nums1, int [] nums2, int [] nums3) { HashMap<Integer,Integer> hash = new HashMap <Integer,Integer>(); ArrayList al = new ArrayList (); Arrays.sort(nums1); Arrays.sort(nums2); Arrays.sort(nums3); for (int i=0 ;i<nums1.length;i++) { if (i>0 && nums1[i]==nums1[i-1 ]) { continue ; } hash.put(nums1[i], hash.getOrDefault(nums1[i],0 )+1 ); } for (int i=0 ;i<nums2.length;i++) { if (i>0 && nums2[i]==nums2[i-1 ]) { continue ; } hash.put(nums2[i], hash.getOrDefault(nums2[i],0 )+1 ); } for (int i=0 ;i<nums3.length;i++) { if (i>0 && nums3[i]==nums3[i-1 ]) { continue ; } hash.put(nums3[i], hash.getOrDefault(nums3[i],0 )+1 ); } for (Map.Entry<Integer,Integer> entry : hash.entrySet()) { if (entry.getValue()>=2 ) { al.add(entry.getKey()); } } return al; } }
2037. 使每位学生都有座位的最少移动次数
Pasted image 20221231002356
简单题,只要将 seat 和 student 两个数组排序,然后对应相减取绝对值,最后相加即可。
2042. 检查句子中的数字是否递增
Pasted image 20230103025153
简单题,大脑清晰且认真即可。
2180. 统计各位数字之和为偶数的整数个数
Pasted image 20230106174004
简单题,直接判断操作即可。
2185. 统计包含给定前缀的字符串
Pasted image 20230108010634
简单题,没啥意思~
2275、按位与结果大于零的最长组合
image-20221009154203464
本题考到位运算,但是实际上思路很简单,就看能不能想到。
a&b什么情况下不为0?只有在某一位中全都是1的时候,他们一定是部位0的。
因此,我们可以拓展到本题。
首先建立一个数组,用来记录所有数再某一位中为1的个数。(1e7<2^25 )
对每一个数进行遍历,如何找到这个数的每一位呢?
可以通过与00000001、00000010、00000100......进行与操作,如果结果不是0,那么就说明这一位为1。
这是一种思路,我们也可以对每个数每次与1与操作,然后右移一位,再与1与操作......(也就是题解的方法)
1 2 3 4 5 6 7 8 9 10 11 int num = candidates[i];int j = 0 ;while (num!=0 ) { if ((num & 1 )!=0 ) { times[j]++; } num = num >> 1 ; j++; }
最后取1最多的那个值,即为次数。如果某一位的1有y个,那么说明在这n个数中一定会有y个数,他们'且'后这一位为1(即结果不为0)。
2283. 判断一个数的数字计数是否等于数位的值
Pasted image 20230111004354
简单题,利用 hash 表进行处理即可。但是要注意 int 转为 char 的相关操作。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public boolean digitCount (String num) { char [] nc = num.toCharArray(); int n = num.length(); HashMap<Character,Integer> hash = new HashMap <Character,Integer>(); for (int i=0 ;i<n;i++) { hash.put(nc[i], hash.getOrDefault(nc[i],0 )+1 ); } for (int i=0 ;i<n;i++) { if (hash.getOrDefault((char )('0' +i),0 )!=(nc[i]-'0' )) { return false ; } } return true ; } }
2287. 重排字符形成目标字符串
Pasted image 20230113170949
虽然是简单题,但是本题的解题过程不简单。需要清楚的是:题目中只是要求用过的字符不能再次用,并没有说每次匹配好的那段所有的其他字符都不能用。因此只需要构造两个 hashmap,一个是用来记录 target 的每个字符出现的次数,另一个是记录 s 中在 target 的字符出现的次数,然后对应字符的次数相除,取最小值即可。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public int rearrangeCharacters (String s, String target) { char [] sc = s.toCharArray(); char [] tc = target.toCharArray(); HashMap<Character,Integer> hash1 = new HashMap <Character,Integer>(); HashMap<Character,Integer> hash2 = new HashMap <Character,Integer>(); for (char t : tc) { hash2.put(t,hash2.getOrDefault(t,0 )+1 ); } for (char c : sc) { if (hash2.containsKey(c)) { hash1.put(c,hash1.getOrDefault(c,0 )+1 ); } } if (hash1.size()!=hash2.size()) { return 0 ; } int sum = 100 ; for (Map.Entry<Character,Integer> entry : hash2.entrySet()) { char key = entry.getKey(); sum = Math.min(sum, hash1.get(key)/entry.getValue()); } return sum; } }
2293、极大极小游戏
简单题,方法很多。
方法一、list 操作
Pasted image 20230115012636
将 nums 转为 list,之后再操作。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public int minMaxGame (int [] nums) { List<Integer> list = Arrays.stream(nums).boxed().collect(Collectors.toList()); int size = list.size(); while (size!=1 ) { int t = 1 ; while (size!=0 ) { if (t%2 ==1 ) { list.add(Math.min(list.get(0 ),list.get(1 ))); } else { list.add(Math.max(list.get(0 ),list.get(1 ))); } t++; list.remove(0 ); list.remove(0 ); size = size-2 ; } size = list.size(); } return list.get(0 ); } }
方法二、数组原地操作
Pasted image 20230115012800
利用 /2 的特性,在原数组进行操作。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int minMaxGame (int [] nums) { int n = nums.length/2 ; while (n!=0 ) { for (int i=0 ;i<n;i++) { if (i%2 ==0 ) { nums[i] = Math.min(nums[i*2 ],nums[i*2 +1 ]); } else { nums[i] = Math.max(nums[i*2 ],nums[i*2 +1 ]); } } n = n/2 ; } return nums[0 ]; } }
2299. 强密码检验器 II
Pasted image 20230119130843
简单题。注意:判断一个字符是否包含在字符串内:
2303. 计算应缴税款总额
Pasted image 20230123154310
简单题~遍历即可。
2309. 兼具大小写的最好英文字母
Pasted image 20230127125026
虽然是简单题,但是涉及很多 java 的基础知识。比如:小写转为大写需要 -32 。 详细代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public String greatestLetter (String s) { char [] sc = s.toCharArray(); Set<Character> set = new HashSet <Character>(); char res = ' ' ; for (int i=0 ;i<sc.length;i++) { set.add(sc[i]); if (sc[i]<='z' && sc[i]>='a' ) { if (set.contains((char )(sc[i]-32 ))) { if (res==' ' || res<(char )(sc[i]-32 )) { res = (char )(sc[i]-32 ); } } } else { if (set.contains((char )(sc[i]+32 ))) { if (res==' ' || res<sc[i]) { res = sc[i]; } } } } if (res==' ' ) { return "" ; } return String.valueOf(res); } }
2315. 统计星号
Pasted image 20230129003138
简单题。
2319. 判断矩阵是否是一个 X 矩阵
Pasted image 20230131093415
简单题。
2325. 解密消息
Pasted image 20230201104134
简单题。唯一需要注意的是普通的动态数组用 ArrayList,字符动态数组用 StringBuilder。
2331. 计算布尔二叉树的值
简单题,考察树的后序遍历 ,并存入栈中(使用双端队列)。
核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public void PostOrder (TreeNode root, Deque<Integer> dq) { if (root == null ) { return ; } PostOrder(root.left,dq); PostOrder(root.right,dq); if (root.val == 2 || root.val == 3 ) { int x = dq.pollLast(); int y = dq.pollLast(); if (root.val == 2 ) { dq.addLast(x|y); } else { dq.addLast(x&y); } } else { dq.addLast(root.val); } }
2335. 装满杯子需要的最短总时长
本题只要将最大的两个值同时-1,当只剩下最后一个温度的杯子没有装满时,再直接加上即可。
2341. 数组能形成多少数对
简单题,没有意义。
2347. 最好的扑克手牌
简单题,快排即可。
2351. 第一个出现两次的字母
Pasted image 20230101002426
简单题,利用位运算判断字符是否已经判断过。
2357. 使数组中所有元素都等于零
简单题。
2363. 合并相似的物品
简单题,但是涉及到很多知识点。
hash 的添加操作
1 hash.put(i[0 ],hash.getOrDefault(i[0 ],0 )+i[1 ]);
hash 的遍历 1 2 3 4 for (Map.Entry<Integer,Integer> entry : hash.entrySet()) { list.add(entry.getKey()); list.add(entry.getValue()); }
list 的自定义遍历(注意是 Collections,不是 Arrays) 1 2 3 4 5 Collections.sort(res,new Comparator <List<Integer>>() { public int compare (List<Integer> l1, List<Integer> l2) { return l1.get(0 )-l2.get(0 ); } });
2367. 算术三元组的数目
简单题。
2373. 矩阵中的局部最大值
简单题,意义不大。
2379. 得到 K 个黑块的最少涂色次数
简单题,滑动窗口基础题!
2383、赢得比赛需要的最少训练时长
本题思路比较简单:
首先要求训练后的experience值要比对手的experience之和多1;
因为是依次挑战(按顺序挑战),所以一个个计算,如果因为energy打不过当前,那就要额外训练一段时间,让他刚好能打过,依次类推,最后能算出energy的额外训练时间。
将两个训练时间相加即可。
2389. 和有限的最长子序列
简单题,先排序,然后计算前缀和即可。
2395. 和相等的子数组
简单题,求和即可。
2399. 检查相同字母间的距离
简单题。
2404. 出现最频繁的偶数元素
简单题,无脑 hash 存储即可。
2409. 统计共同度过的日子数
简单题,但是恶心人。只要找到两个人的出发日期的最大值和离开日期的最小值,它们之间的时间差就是我们要求得得。
2413. 最小偶倍数
简单题,一行结束,优雅。
2418. 按身高排序
考察: #数组排序
简单题, #模板题 。但是注意:根据一个数组的结果排序另一个数组的 ,这是模板题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public String[] sortPeople(String[] names, int [] heights) { int n = names.length; Integer[] idx = new Integer [n]; for (int i=0 ;i<n;i++) { idx[i] = i; } Arrays.sort(idx, new Comparator <Integer>() { public int compare (Integer a, Integer b) { return heights[b]-heights[a]; } }); String[] res = new String [n]; for (int i=0 ;i<n;i++) { res[i] = names[idx[i]]; } return res; } }
2423. 删除字符使频率相同
简单模拟题。但是要考虑的东西特别多。
2427. 公因子的数目
简单题。
2469. 温度转换
简单题,没有意义。
2488. 统计中位数为 K 的子数组⭐
Tags: #前缀和 #后缀和 #哈希
困难题,思路很难!是前缀和的超级灵活考察,它不是简单的前缀相加再处理,而是用相对大小进行处理,最后记录大于 k 和小于 k 的个数之间的关系。
思路:
首先注意,由于 nums 长度为 n,且是 1~n 的不同整数组成,数组只能是一个 1 到 n 的排列。同时 k≤n,则 k 一定在数组中出现过。
那么可以在数组中先找到 k,考虑一个数组的中位数如果是 k,那么这个排序后的 k 就在中间或中间偏左。
因此可以将 k 及其右侧所有数字到 k 这个位置上的“前缀和”统计出来(这里“前缀和”并不是常规上的“前缀和”),k 位置上的 sum=0,大于 k 的位置是 sum+1,小于的是 sum-1,并都存入哈希表。哈希表的 key 就是 sum,value 就是 sum 出现的次数。不难发现 key=0 一定会出现 1 次,也就表示整个数组至少会出现一个只有 k 单独元素组成的符合条件的子数组。
接下来对 k 左侧进行相反处理,从 k 位置向左端点进行遍历,即求到 k 的“后缀和”。等于 k 就 sum=0,小于 k 是 sum+1,大于 k 是 sum-1。但是本次遍历不需要存入 hash,而是直接不断 hash. get (sum)+hash. get (sum+1) 求出结果了。
举个例子:
假设数组为[3, 2, 1, 5, 4 , 7, 6]。
先处理右端:[0, 0, 0, 0, 0 , 1, 2]。
再处理左端:[2, 1, 0, -1, 0 , 1, 2]。我们可以看出当左边遍历到 2 的时候 sum=1,意味着左边小于 k 的比大于 k 的多 1 个 ,如果要使得子数组成立,我需要再右边找到右边大于 k 的比小于 k 的多 1 个 ,或者右边找到大于 k 的比小于 k 的多 2 个 (因为中位数靠左),所以本题下标为 1~5 可以为一个结果,1~6 也可以为一个结果。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Solution { public int countSubarrays (int [] nums, int k) { int n = nums.length; int loc = 0 ; for (int i=0 ;i<n;i++) { if (nums[i]==k) { loc = i; break ; } } HashMap<Integer, Integer> hash = new HashMap <Integer,Integer>(); int res = 0 ; int sum = 0 ; for (int i=loc;i<n;i++) { if (nums[i]==k) { sum = 0 ; } else if (nums[i]>k) { sum++; } else { sum--; } hash.put(sum, hash.getOrDefault(sum,0 )+1 ); } for (int i=loc;i>=0 ;i--) { if (nums[i]==k) { sum = 0 ; } else if (nums[i]<k) { sum++; } else { sum--; } res += hash.getOrDefault(sum,0 ); res += hash.getOrDefault(sum+1 ,0 ); } return res; } }
面试题 01.02. 判定是否互为字符重排⭐
image-20220928215225321
本题可以使用将字符串转为字符数组:
1 char [] sa1 = s1.toCharArray();
再对字符数组进行排序,一样可以使用Arrays.sort():
判断两个字符数组是否相同,可以先将字符数组转为字符串:
1 String s = String.valueOf(sa1);
字符串的比较:
剑指 Offer 47. 礼物的最大价值
经典动态规划~
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public int maxValue (int [][] grid) { int m = grid.length; int n = grid[0 ].length; int [] dp = new int [n+1 ]; for (int i=0 ;i<m;i++) { for (int j=0 ;j<n;j++) { dp[j+1 ] = Math.max(dp[j]+grid[i][j],dp[j+1 ]+grid[i][j]); } } return dp[n]; } }
面试题01.05.一次编辑
image-20221014091320312
比较简单的一题,类似于1913题的思路.
面试题 01.08、零矩阵
方法一、两次遍历
image-20220930094258272
对二维数组遍历两遍,第一遍是为了找到受影响的行和列,第二遍是针对这些行和列做处理。
List查找某个元素是否在其中,可以用:(List其他操作见15题)
方法二、两个标记变量
image-20220930101828955
这个思路就是将第一行第一列作为标签,如果matrix中i行j列为0,则将第一行的j列和第一列的i行置为0即可。但是为了保存第一行和第一列的信息,我需要先判断如果第一行中没有0,但是由于matrix[i][j]为0,所以第一行的第j列必然为0。所以在操作之前先判断第一行和第一列是否有0,如果有则在最后一步将其全置为0。接下来的操作是分析除去第一行和第一列的元素,如果为0,则其对应的第一行的元素和第一列的元素变为0。遍历完之后,根据第一行和第一列的情况对内部元素操作。最后按照上面刚说的那样对第一行和第一列处理。
面试题 01.09、字符串轮转
方法一、利用子串匹配
image-20220929224340027
java中有内置的判断字符串是否在另一个字符串的函数:
只需要将其中的一个字符串连续两个相同的拼接上,如果可以循环成为它,则另一个字符串就一定包含在这个拼接的字符串中。
方法二、指针移动关系
image-20220929225202308
如果s1向左循环移动i位得到s2,那么对于s2的下标为j的元素有这样的关系:s2[j] = s1[(i+j)%j]。其实很好理解,因为如果是s1向左移动i位,相当于s2相对s1向右移动i位,所以对于s2的i位,实际就是s1的0位,这样就能得到这样的关系。注意的是对于字符串,提取下标为i的字符需要用s1.charAt(i)。
面试题 05.02. 二进制数转字符串
常规中等题,考察 StringBuilder 和除法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public String printBin (double num) { if (num==1.0 ) { return "1.0" ; } StringBuilder sb = new StringBuilder (); sb.append("0." ); double cs = 0.5 ; while (num!=0 ) { if (sb.length() > 32 ) { return "ERROR" ; } if (num >= cs*2 ) { return "ERROR" ; } if (num >= cs) { num -= cs; cs = cs / 2 ; sb.append('1' ); } else { cs = cs / 2 ; sb.append('0' ); } } if (num==0 ) { return sb.toString(); } return "ERROR" ; } }
面试题 17.05. 字母与数字
方法一、前缀和+定长
先计算前缀和,因为要找最长子串,所以定长遍历,从最长的长度开始,遍历起点,再依次缩小长度,继续遍历起点。直到找到一个长度下满足条件的起点,这个起点和长度就是满足条件的最长且靠左的。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 class Solution { public String[] findLongestSubarray(String[] array) { int n = array.length; int [] pren = new int [n]; int [] prec = new int [n]; for (int i=0 ;i<n;i++) { char c = array[i].charAt(0 ); if (i==0 ) { if ((c>='A' && c<='Z' ) || (c>='a' && c<='z' )) { prec[i] = 1 ; pren[i] = 0 ; } else { prec[i] = 0 ; pren[i] = 1 ; } } else { if ((c>='A' && c<='Z' ) || (c>='a' && c<='z' )) { prec[i] = prec[i-1 ] + 1 ; pren[i] = pren[i-1 ]; } else { prec[i] = prec[i-1 ]; pren[i] = pren[i-1 ] + 1 ; } } } boolean isFind = false ; int left=0 , right=0 ; for (int len = n; len > 0 ; len--) { for (int start=0 ;start<=n-len;start++) { if (start==0 ) { if (prec[start+len-1 ] == pren[start+len-1 ]) { left = start; right = start+len-1 ; isFind = true ; break ; } } else { if (prec[start+len-1 ] - prec[start-1 ] == pren[start+len-1 ] - pren[start-1 ]) { left = start; right = start+len-1 ; isFind = true ; break ; } } } if (isFind) { break ; } } if (right==left) { return new String [0 ]; } String[] res = new String [right-left+1 ]; for (int i=0 ;i<right-left+1 ;i++) { res[i] = array[i+left]; } return res; } }
方法二、前缀和+哈希⭐
我们设遇到字母+1,遇到数字-1,所以满足条件的子串的和就是为 0。但是找到一个和为 0 的子串实际上操作和方法一一致,没有达到优化的效果,所以需要转换思路。
假设以下标为 i 结尾的前缀和为 k,以下标为 j 结尾的前缀和也为 k,那么可以知道 i~j 之间部分的和为 0,实际上是 [i+1, j] 区间的和为 0,这个区间长度为 j-i 。
哈希表 hash 记录每个前缀和第一次出现的位置,max 记录最大子数组的长度,start 记录最大子数组时的起点位置。
关于数组的深拷贝,使用方法见:数组深拷贝
详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { public String[] findLongestSubarray(String[] array) { HashMap<Integer,Integer> hash = new HashMap <Integer,Integer>(); hash.put(0 ,-1 ); int sum = 0 ; int max = 0 , start = 0 ; for (int i=0 ;i<array.length;i++) { char c = array[i].charAt(0 ); if ((c>='A' && c<='Z' ) || (c>='a' && c<='z' )) { sum++; } else { sum--; } if (hash.containsKey(sum)) { if (i-hash.get(sum) > max) { max = i - hash.get(sum); start = hash.get(sum)+1 ; } } else { hash.put(sum,i); } } if (max == 0 ) { return new String [0 ]; } String[] res = new String [max]; System.arraycopy(array,start,res,0 ,max); return res; } }
面试题17.09、第k个数
方法一、硬算
image-20220928204023119
使用方法三的手法进行优化
image-20220928214832520
List本质上就是动态数组
创建:
1 List<Integer> list = new ArrayList <Integer>();
添加:
查询:
方法二、小根堆
image-20220928211555288
小根堆
小根堆的创建:
1 PriorityQueue<Long> q = new PriorityQueue <>();
小根堆添加操作:
小根堆判断是否为空:
小根堆弹出操作(弹出最小的数,并返回此数):
Set集合
Set 表示唯一对象的集合。集合中元素的排序是不相关的。
HashSet
HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合 。
HashSet 允许有 null 值。
HashSet 是无序的,即不会记录插入的顺序 。
HashSet 实现了 Set 接口。(这就是为什么这个解法可以使用Set<Long> set = new HashSet<>();)
Set对象的创建:
1 Set<Long> set = new HashSet<>();
1 Set<Character> set = new HashSet <>();
Set对象的添加操作:
Set对象的判断是否存在操作:
Set对象删除
方法三、多路归并(多指针)(方法一优化)
image-20220928214035095
设置3个指针,分别是对于*3、*5、*7的,每次比较选择最小的加入到数组中。
注意
在使用两个数比大小时,尽量使用Math.min(a,b)。如果时三个数,使用Math.min(a,Math.min(b,c))。
面试题 17.19、消失的两个数字
方法一、硬算(不合规矩)
image-20220926152844119
方法二、异或(官方)
image-20220926205020657
负数的补码:
对于正数9:0000 1001
对于负数-9:先-1:0000 1000;再全部取反:1111 0111。
或者先变成:1000 1001;再除了符号位取反:1111 0110;再末位+1:1111 0111。
所以: 9 = 0000 1001
-9 = 1111 0111
9 & -9 = 0000 0001(能生成二进制中为1的最小位)
拓展: 10 = 0000 1010
-10 = 1111 0110
10 & -10 = 0000 0010
异或
Java中异或用^表示
1 ^ 1 ^ 2 ^ 2 ^ 3 ^ 4 => 0 ^ 0 ^ 3 ^ 4 => 0 ^ 3 ^ 4(x = 3^4)=> 0 ^ x => x
即 a ^ a ^ b = b
Integer.MIN_VALUE = 1000 0000 0000 0000 0000 0000 0000 0000,-2 147 483 648
Integer.MAX_VALUE = 0111 1111 1111 1111 1111 1111 1111 1111, 2 147 483 647
逻辑:
这是考察异或和且的位运算算法题。我们想要找到n个数中没有出现的2个数,也就是再n个数中找出除了nums中存在的n-2个数的另外2个数。
如果先将这n个数和nums中n-2个数异或,也就是这2n-2(n-2 + n-2 + 2)个数异或,根据上面异或的相关知识,能得到结果x = x1 ^ x2(x为异或结果,x1、x2为我们想要求得的两个不在nums的数)。
得到x = x1 ^ x2,如何提取出x1和x2成为了最大的难题。我们发现x1和x2不可能相同,所以x1&x2! = 0,根据上面且的相关知识,对于x而言,它二进制最低位的1(假设是第k位)所代表的含义是x1的第k位和x2的第k位是不相同的(因为相同为0、不同为1),因此我们可以将nums分为两类,一类是第k位为0的,一类是第k位为1的。筛选出nums这两类很简单,只需要与x&-x(x&-x的结果是除了第k位为1,其他位都是0)进行且操作,如果结果是0,表示nums中的这个数的第k位是0;如果是1,表示nums中的这个数的第k位是1。这里需要进行防溢出操作,即判断x == Integer.MIN_VALUE,如果成立,说明-x已经越界,就直接将x与nums中每个数进行比较筛选。
经过上一步就已经将nums分为两部分了type1、type2,这时候只需要将n个数分为两部分,两部分分别是type1多一个x1、type2多一个x2。只需要再用一次异或,结果就分别是x1和x2了。
返回结果直接用new int[]{type1,type2}即可。
方法三、求和
image-20220926213157385
本方法使用数学基础运算。先求出n个数之和,再求出nums中n-2个数之和,则这两个和之差就是x1+x2。我们可以通过x = x1+x2,算出x1和x2的中位数t,即x1一定小于t,x2一定大于t ,求出1~t的和truenum,可以将nums遍历一遍,筛选每一个值小于等于t的num,再用truenum减去这个值,得到的结果就是x1。用x2 = x - x1得到结果。