leetCode进阶算法题+解析(三十二)

今天刷题之前先打个广告java技术交流群,群号:130031711,欢迎各位踊跃加入。平时聊天吹水或者技术交流,问题探讨啥的都可以。
然后开始今天的刷题。

只出现一次的数字2

题目:给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。

示例 :
输入: [1,2,1,3,2,5]
输出: [3,5]
注意:
结果输出的顺序并不重要,对于上面的例子, [5, 3] 也是正确答案。
你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?

思路:首先题目不难,我记得只出现一次的数字1是只有一个数字是单独出现的,所以用的位运算异或^.一样的数字会消下去。最后剩下的是求的数字。但是现在的问题是这个题两个出现一次的数字。。不考虑空间复杂度就是新建数组,下标代替数值,出现两次是2,出现一次是1。最后求出是1的两个。或者说用set,没出现过的add。出现过的remove,最后s不过既然题目说了常数空间,我还是再想想吧。说实话,我感觉还是要二进制运算,最后也能算出两个数异或的结果,重点是我想不出来要怎么把这两个数分开呢?
我有一个大胆的想法,异或出来的结果如果有1,则说明两个数这个位上一个是0一个是1(这句话可能有点废话,但是肯定要说明白,)而且两个数异或只要不是相同的数字一定会有1的存在。所以这里只要单独吧这个位数是1的提出来异或。这个位数是0的也提出来异或。最终得到的两个结果就会是两个单独的值(因为别的数异或也是成对出现,最终还是0)。至于判断一个数字的二进制的某一位是1还是0我是百度出来的(这块确实是知识盲区,做起来都要依靠百度,不直接看题解就是我最后的倔强了,哈哈)。
在这里继续表白发这个帖子的大大(csdn 上的,我顺手点赞关注了,哈哈),然后我贴出这个判断的方法:

判断一个数字二进制某一位是1还是0

这里简单说一下,第三个方法太麻烦了,肯定不用。然后我感觉方式1和方式2还是1简单些,所以我打算代码中用方式1的方式,我去写代码了:
额,代码出来了,性能超过百分百,一次及格,我直接贴出来:

class Solution {
    public int[] singleNumber(int[] nums) {
        int sum = 0;
        for(int i : nums){
            sum ^= i;
        }
        
        int n = 0;
        //找出一个是1的位数
        while(((sum>>n)&1)==0){
            n++;
        }
        int nums1 = 0;
        int nums2 = 0;
        for(int i : nums){
            if(((i>>n)&1)==1){
                nums1^=i;
            }else{
                nums2^=i;
            }
        }
        return new int[]{nums1,nums2};
    }
}

其实我一直都有自知之明,而且还恬不要脸的承认,二进制位运算我是真的差,给我时间我慢慢能理明白是怎么回事,但是让我自己想思路之类的真的很折磨,再次感谢我上面截图的那个大大发的技术贴。然后咱们说回来,这个题其实思路我是差不多想的挺好的,但是写的时候也是很麻烦,单独说这个找出第一个是1的位数我就不断在控制台打印和修改,就这个括号都是提交了两次才修改对的。
这么说吧,我感觉这个题不难,就是思路问题,但是对于我而言比较困难,因为不敢确定。我一直想着有空补补二进制运算,但是也一直没空。真的是工作中接触不到,而且还有是不知道从哪里补起。单纯的或与异或我都知道,但是就是在题目中不会下意识的去想。这个就很烦躁啊!哎,先这样吧,自省完继续刷题了。

丑数2

题目:编写一个程序,找出第 n 个丑数。丑数就是只包含质因数 2, 3, 5 的正整数。

示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:
1 是丑数。
n 不超过1690。

思路:其实这个题我记得我是做过的,好像是丑数1的时候,那时候我用了最笨的一种方法,就是先算平方差,然后挨个除,被不是2,3,5除尽就pass。不过后来我记得我看了性能排行第一的代码,是有什么算法的。哎,太久远了记不住了,不提当年,只说这道题了。我觉得知道第几个丑数的前提是从第一个开始一个个列出来应该,又仔细看了下题目,其实除了1,2,3,5剩下的所有丑数其实都是前面这三个数乘除得来的。也不知道有没有用。算了,我也不寻思什么算法不算法了,先暴力法试试能不能解决吧。

好了,死去活来撸了不少头发终于写出来了,这个我到底还是没暴力法。其实是暴力法写着写着发现找到点规律。我决定把心里路程好好讲讲:
首先最开始提到的判断每个数是不是丑数,但是从1开始除一直到平方值太傻了,丑数只能是2,3,5组成,其实我们可以直接用已有的丑数来判断,所以我的打算是把已经求出来的丑数列出来,新的数字从第一个丑数开始除,如果除尽了判断倍数是不是也在丑数里,如果是的话肯定是丑数。但是因为要判断倍数是不是也在丑数里,所以这里就要用到了contains。我开始打算用set。但是用着用着,发现其实这个是有规律的,所以又开始沉迷于找规律。然后找规律的过程中真的发现规律了,所以就大改代码,set也删了,反正是实现了,哈哈,我先贴代码,大家可以看一下,性能超过百分之九十七:

class Solution {
    public int nthUglyNumber(int n) {
        if(n==1) return 1;
        int v2 = 0;
        int v3 = 0;
        int v5 = 0;
        //第n个丑数,先把直到n所有丑数列出来
        int[] res = new int[n];
        res[0] = 1;//1是第一个丑数
        for(int i = 1;i

感觉比我最开始的每个都判断要好多了,这样是直接找出答案的,不过要是没一开始的思路和测试也想不出这个,然后中间的坑就是if最小值是某俩数相乘这块,因为本来我是用if-else的。但是用着以后测试案例发现结果不是预期的,控制台打印每一个元素发现是6,10,12出现了两次(我用n=13测试的)。然后再一分析,23,32.25,52之类的。所以这里不能用if-else,一定要都if,出现两个相乘了要两个都加1。
当然写到这我这个代码就完成了,至于set的想法我是没最终实现,但是我觉得思路也是没问题,不知道写出来会不会超时,反正是没问题的。不过我就不去实现了,下一题了。
额,最后我还是没忍住看了下性能排行第一的代码,差不多一样的思路,只不过人家是做成了构造方法直接把所有的丑数列出来了。所以这道题就这么过了。

H指数

题目:给定一位研究者论文被引用次数的数组(被引用次数是非负整数)。编写一个方法,计算出研究者的 h 指数。h 指数的定义: “h 代表“高引用次数”(high citations),一名科研人员的 h 指数是指他(她)的 (N 篇论文中)至多有 h 篇论文分别被引用了至少 h 次。(其余的 N - h 篇论文每篇被引用次数不多于 h 次。)”

示例:
输入: citations = [3,0,6,1,5]
输出: 3
解释: 给定数组表示研究者总共有 5 篇论文,每篇论文相应的被引用了 3, 0, 6, 1, 5 次。
由于研究者有 3 篇论文每篇至少被引用了 3 次,其余两篇论文每篇被引用不多于 3 次,所以她的 h 指数是 3。
说明: 如果 h 有多种可能的值,h 指数是其中最大的那个。

思路:没有时间空间要求。要么就最傻的一个个判断。或者先排序,再从最高处开始判断,因为这个题目的意思我觉得我理解的不清楚,不知道思路是不是对的,我先去试试再说。
说真的,这个题不难,就是描述让我理解不了,一遍遍看题目,分析琢磨,我觉得我应该要补补语文了。
然后主要抓到两句:最多h篇被引用了最少h次。h指数是其中最大的那个。
根据最多h篇被引用最少h次。这两个最多最少告诉我们边界。首先我们排序可以通过下标知道这之前有多少篇,这之后多少篇。
然后被引用的次数是这个元素位置后面有多少个元素(包括当前元素),所以次数是数组总长减去当前下标(因为包含当前的,所以这里数组总长不用-1)。
然后这个最多h篇,最少h次,这两个词可以判断篇数大于次数。换言之次数小于篇数,所以有表达式:次数<=篇数(最多最少是可以相等的并且次数肯定越来越小,所以取第一个结果就是最大的那个)。于是就有了下面的代码:

class Solution {
    public int hIndex(int[] citations) {
        if(citations.length==0) return 0;
        Arrays.sort(citations);
        for(int i = 0;i

这道题真的是考验理解能力,代码上不难,我这个性能不是最好的,我觉得有可能是排序的问题?是不是我手写排序能好点?算了,我直接去看看性能第一的代码了,真的懒得写排序。。。
看完性能第一的代码我自闭了,我本来以为我是细节处理上有点问题,看完发现我是智商上有问题。。。写的挺好的,简洁方便,就是一遍没看懂,两遍还是懵,,,最后我debug了。。。我直接贴代码:

class Solution {
    public int hIndex(int[] citations) {
        int n = citations.length;
        int[] papers = new int[n + 1];
        // 计数
        for (int c: citations)
            papers[Math.min(n, c)]++;
        // 找出最大的 k
        int k = n;
        for (int s = papers[n]; k > s; s += papers[k])
            k--;
        return k;
    }
}

首先这个papers是个计数用的数组,总数肯定是数组的长度。其实这个h不会大于数组长度的,其次因为分布不同,直接计数每个数字前面后面的个数。我贴上调试的截图:


papers计数

然后其实得出的计数的结论,我们从后往前相加,只要到某一个点,这个个数不小于下标就说明我们找对值了。也就直接返回。因为这个要取最大的指数,所以从后往前遍历就行。这个想法真的是精巧,因为咱们这里不需要排出数值的大小,只是为了知道后面多少个元素就行,所以计数器的数组真的思路贼棒!我是第一次接触这样计数排序的方法,惊为天人,反正各种舔,哈哈~这个办法我记住了。下一题吧。

H指数2

题目:给定一位研究者论文被引用次数的数组(被引用次数是非负整数),数组已经按照升序排列。编写一个方法,计算出研究者的 h 指数。h 指数的定义: “h 代表“高引用次数”(high citations),一名科研人员的 h 指数是指他(她)的 (N 篇论文中)至多有 h 篇论文分别被引用了至少 h 次。(其余的 N - h 篇论文每篇被引用次数不多于 h 次。)"

示例:
输入: citations = [0,1,3,5,6]
输出: 3
解释: 给定数组表示研究者总共有 5 篇论文,每篇论文相应的被引用了 0, 1, 3, 5, 6 次。
由于研究者有 3 篇论文每篇至少被引用了 3 次,其余两篇论文每篇被引用不多于 3 次,所以她的 h 指数是 3。
说明:
如果 h 有多有种可能的值 ,h 指数是其中最大的那个。
进阶:
这是 H指数 的延伸题目,本题中的 citations 数组是保证有序的。
你可以优化你的算法到对数时间复杂度吗?

思路:额,题目一点没变,只不过增加了进阶条件,时间复杂度有要求了。首先咱们之前的算法是从前往后遍历,所以复杂度是O(n),对数时间复杂度我第一想法是二分,所以这个题二分也不是不行哈,先判断中间点,如果中间点可以满足则继续往前半段判断,中间点满足不了往后半段判断。我去写了。
好了,回来了,我觉得没啥说的,就是二分法实现了(虽然我觉得时间复杂度到不了对数,但是没办法,一说O(logN)第一反应就是二分了)。我直接贴代码:

class Solution {
    public int hIndex(int[] citations) {
        int l = 0;
        int r = citations.length;
        int n = r;
        while(l citations[mid]){
                l = mid + 1;
            }else{
                r = mid;
            }
        }
        return n-l;
    }
}

然后性能超过百分百了,我去瞅一眼题解,没问题的话这道题就这样了。
嗯,这道题就这样了,暂时看题解啥的都没啥亮点,上道题的那个计数排序是亮点但是这个题不适用。
今天的笔记就记到这里了,如果稍微帮到你了记得点个喜欢点个关注。我回尽量每天刷题并且记成笔记的,也祝大家工作顺顺利利,生活健健康康!再打个广告:java技术交流群,群号:130031711,欢迎各位踊跃加入。

你可能感兴趣的:(leetCode进阶算法题+解析(三十二))