leetcode-2024-3-20
文章目录
1969.数组元素的最小非零乘积(Medium)
给你一个正整数 p 。你有一个下标从 1 开始的数组 nums ,这个数组包含范围 [1, 2p - 1] 内所有整数的二进制形式(两端都 包含)。你可以进行以下操作 任意 次:
从 nums 中选择两个元素 x 和 y 。
选择 x 中的一位与 y 对应位置的位交换。对应位置指的是两个整数 相同位置 的二进制位。
比方说,如果 x = 1101 且 y = 0011 ,交换右边数起第 2 位后,我们得到 x = 1111 和 y = 0001 。
请你算出进行以上操作 任意次 以后,nums 能得到的 最小非零 乘积。将乘积对 109 + 7 取余 后返回。
注意:答案应为取余 之前 的最小值。
自己没想出来,其实有思路了就是总感觉不对劲,看了答案如果继续想应该能想出来,但是问题是这个取余肯定也会困扰我很久,还有我肯定只会暴力求幂,这里的快速幂其实是很值得学习一下的
官方解:贪心+快速幂
x * y肯定是要比(x-1) * (y+1)大的,那么什么时候会有最小值呢,就是当x=1,y变得最大的时候,对于都是二进制来说,能通过交换位来变大只能是x=1,y=2^(p-1)-2了,比如3位的时候,最小就是1,最大就是6,要保持x和y互为反码
那这道题就是一个纯数学问题了,每次取两个互为反码的数都能将其变成x=1,y=2^p-2,那么一共有多少对这样的反码对呢。
n=(2^p-2)/2,除了最后面那个数其他都可以凑对,就有2^(p-1)-1对。
p=3的时候,就有3对,p=4时,就有7对
也就是说,我们的最终答案是最后一个元素*(最后一个元素-1)^(2^(p-1)-1)
即:
快速幂:通过观察 p=3的时候,就有3对,p=4时,就有7对,可以发现3就是11,7就是111,根据幂的公式x^(111)=x^(100)*x^(010)*x^(001),
答案里巧妙地用快速幂解决,也就是说,不再是暴力连乘,而是x也自己进行幂运算,就可以将复杂度降为o(log)
1 | private static final int MOD = 1_000_000_007; |
没想出来这种方法,贪心还是练习少了
49.字母异位词分组(Medium)
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
输入: strs = [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出: [[“bat”],[“nat”,”tan”],[“ate”,”eat”,”tea”]]
自己解(也是官方解):哈希表+排序
遍历每个字符串,然后在对其排序,如果哈希表没有就加进去,list把这个排序之前的字符串放进去,有就在哈希表已有的键里面添加这个字符串。
1 | public List<List<String>> groupAnagrams(String[] strs) { |
比较简单
128.最长连续序列(Medium)
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 (n) 的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
暴力解:(其实是超时的,因为复杂度为o(nlogn))
最傻逼的一集,这里Arrays.sort就已经超出复杂度了,但是居然也没超时,甚至比超过90%,感觉有点。
最简单的思路,排序然后指针,从小到大如果一直递增就计数器一直加,如果相等那就计数器不动,如果不是,那就计数器重新变为1
1 | public int longestConsecutive(int[] nums) { |
官方解:哈希表
我们考虑枚举数组中的每个数 x,考虑以其为起点,不断尝试匹配 x+1,x+2… 是否存在,假设最长匹配到了 x+y,那么以 x 为起点的最长连续序列即为 x,x+1,x+2,x+y,其长度为 y+1,我们不断枚举并更新答案即可。
对于匹配的过程,暴力的方法是 O(n) 遍历数组去看是否存在这个数,但其实更高效的方法是用一个哈希表存储数组中的数,这样查看一个数是否存在即能优化至 O(1) 的时间复杂度。
仅仅是这样我们的算法时间复杂度最坏情况下还是会达到 O(n2)
即外层需要枚举 O(n) 个数,内层需要暴力匹配 O(n) 次),无法满足题目的要求。但仔细分析这个过程,我们会发现其中执行了很多不必要的枚举,如果已知有一个 x,x+1,x+2,⋯,x+y 的连续序列,而我们却重新从 x+1,x+2 或者是 x+y 处开始尝试匹配,那么得到的结果肯定不会优于枚举 x 为起点的答案,因此我们在外层循环的时候碰到这种情况跳过即可。
那么怎么判断是否跳过呢?由于我们要枚举的数 x 一定是在数组中不存在前驱数 x−1 的,不然按照上面的分析我们会从 x−1 开始尝试匹配,因此我们每次在哈希表中检查是否存在 x−1 即能判断是否需要跳过了。
1 | public int longestConsecutive(int[] nums) { |
非常巧妙的o(n),这里set查询是o(1),需要好好学习一下,多用用哈希