美文网首页java学习之路
leetCode进阶算法题+解析(四十)

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

作者: 唯有努力不欺人丶 | 来源:发表于2020-04-15 19:04 被阅读0次

    最近打算整理下排序算法,本来都不想刷题了,但是看群里狗群友一个比一个勤奋,所以今天的三道题还是刷了吧,这样睡觉也能安稳些。打个广告,130031711,java技术交流群,欢迎各位大佬萌新踊跃加入!

    计算各个数位都不同的数字个数

    题目:给定一个非负整数 n,计算各位数字都不同的数字 x 的个数,其中 0 ≤ x < 10n 。

    示例:
    输入: 2
    输出: 91
    解释: 答案应为除去 11,22,33,44,55,66,77,88,99 外,在 [0,100) 区间内的所有数字。

    思路:这个题我觉得不是用代码一个个去暴力解决的。其实是有一些规律的。我去找找规律
    好了,回来了,用眼去看,用心去想。我直接贴代码再解释:

    class Solution {
        public int countNumbersWithUniqueDigits(int n) {
            if(n==0)
                return 1;
            int []dp=new int [n+1];
            dp[0]=1;
             dp[1]=10;
            for(int i=2;i<=n;i++)
            {
                dp[i]=dp[i-1]+(dp[i-1]-dp[i-2])*(10-(i-1));
            }
            return dp[n];
        }
    }
    

    各位数字都不同。首先加上一个位数的结果是正常的,因为100中各个数不同的肯定包含10中各个数不同的。所以这个很好理解。
    而dp[i-1]-dp[i-2]的意思是我们上一次较上上一次多出来的各位不重复的数字。以n=3为例,n=2已经计算了0-99之间不重复的数字了,我们需要判断的是100-999之间不重复的数字,那也就只能用10-99之间的不重复的数去组成三位数,而不能使用0-9之间的不重复的数,因为他们也组成不了3位数。而10-99之间不重复的数等于dp[2]-dp[1]。
    当i=2时,说明之前选取的数字只有1位,那么我们只要与这一位不重复即可,所以其实有9(10-1)种情况(比如1,后面可以跟0,2,3,4,5,6,7,8,9)。当i=3时,说明之前选取的数字有2位,那么我们需要与2位不重复,所以剩余的有8(10-2)种(比如12,后面可以跟0,3,4,5,6,7,8,9)。
    然后这个dp 的公式就出来了。所以有了上面的代码。
    这个题其实不是代码上的难度,单纯的就是找规律。没啥可说的,下一题吧。

    水壶问题

    题目:有两个容量分别为 x升 和 y升 的水壶以及无限多的水。请判断能否通过使用这两个水壶,从而可以得到恰好 z升 的水?如果可以,最后请用以上水壶中的一或两个来盛放取得的 z升 水。

    你允许:
    装满任意一个水壶
    清空任意一个水壶
    从一个水壶向另外一个水壶倒水,直到装满或者倒空
    示例 1: (From the famous "Die Hard" example)
    输入: x = 3, y = 5, z = 4
    输出: True
    示例 2:
    输入: x = 2, y = 6, z = 5
    输出: False

    思路:这个题,我觉得我遇到了瓶颈!!真的,这个我会,一看就会的会。以前做过这样一道数学题,是个拔高题我记得。然后能不能z这么多的水是可以判断的。我记得是两个桶的最大公约数能不能被z整除。能就可以,不能就fasle。大小什么的就不用说了!!但是问题来了,除了暴力遍历我不知道java代码怎么实现。。。真的是日狗了。。我去试试吧。这种思路一大堆下不了手的情况有点烦躁。
    好了,虽然性能不咋地但是起码实现了,我去贴代码:

    class Solution {
        public boolean canMeasureWater(int x, int y, int z) {
            if(x==z || y==z) return true;
            if(x+y<z) return false;
            if(x%2==0 && y%2==0 && z%2==1) return false;
            int yue = x<y?x:y;
            while(x%yue!= 0 || y%yue!=0 ){
                yue--;
            }
            return z%yue == 0;
        }
    }
    

    思路没问题,而且这个题真的我觉得是个数学题。然后求公约数这个,我可能是在这块浪费性能了,不过确实没思路,不知道有没有现成的api,我去看看性能排行第一的代码吧。

    class Solution {
        public boolean canMeasureWater(int x, int y, int z) {
        if (x == 0 && y == 0) return z == 0;
            int a = x, b = y;
            while (b != 0) {
                a %= b;
                a ^= b;
                b ^= a;
                a ^= b;
            }
            return z == 0 || (z % a == 0 && x + y >= z);
        }
    }
    

    ????分开每个字母我都认识!但是这个代码我是一点没明白,除了第一句!
    这个比较无奈了,,感觉while操作最后实现的是a变成了最大公约数,但是怎么实现的完全看不懂。
    这道题我还是笨办法做吧,下一题了。

    最大整除子集

    题目:给出一个由无重复的正整数组成的集合,找出其中最大的整除子集,子集中任意一对 (Si,Sj) 都要满足:Si % Sj = 0 或 Sj % Si = 0。如果有多个目标子集,返回其中任何一个均可。

    示例 1:
    输入: [1,2,3]
    输出: [1,2] (当然, [1,3] 也正确)
    示例 2:
    输入: [1,2,4,8]
    输出: [1,2,4,8]

    思路:这个题怎么说呢,刚刚做了测试,1,4,2也是三个。又因为顺序是相互的,所以说其实这个顺序不重要。所以我目前的想法是先排序,在判断。而且可能有多个可选项,所以想要判断一个元素可以成为的最长串的数量,还需要去和前面所有的元素对比。打个比方,1,3,4,6,8,9,27如果是这样的例子。比如判断到9了,我们要去和8比,不是子集,继续和6比,不是子集,然后和4比,不是子集。然后和3比,是子集。这个时候其实3本身1,3.它的长度已经是2了,所以9的长度是3的长度2+1,也就是3。然后27继续去比较,因为直接是9的子集,所以是3+1也就是4.你以为这个就完事了?不是的,万一之前有更长的呢!所以还是要遍历一个遍。反正我觉得这个题就是双循环吧。我去敲代码试试。.
    好了,写完了,兜兜转转,居然变成了二维dp,我直接贴代码:

    class Solution {
        public List<Integer> largestDivisibleSubset(int[] nums) {
            LinkedList<Integer> res = new LinkedList<Integer>();
            if(nums.length==0) return res;
            Arrays.sort(nums);
            //第一个存个数,第二个存上一个元素下标
            int[][] dp = new int[nums.length][2];
            //初始数是1,但是我看的赋值,所以统一从0开始。上一个下标是-1
            dp[0][0] = 0;
            dp[0][1] = -1;
            //个数最大值
            int max = -1;
            //个数最大值的下标
            int maxIdx = 0;
            for(int i = 1;i<nums.length;i++){
                //上一个统一为-1.如果有上一个了会被替换
                dp[i][1] = -1;
                for(int j = i-1;j>=0;j--){                
                    if(nums[i]%nums[j]==0 && dp[i][0]<(dp[j][0]+1)){
                        dp[i][0] = dp[j][0]+1;
                        dp[i][1] = j;
                        if(max<dp[i][0]){
                            max = dp[i][0];
                            maxIdx = i;
                        } 
                    }
                }
            }
            while(maxIdx!=-1){
                res.addFirst(nums[maxIdx]);
                maxIdx = dp[maxIdx][1];
            }
            return res;
        }
    }
    

    其实这个题用一个数据dp也可以实现,但是在找出最大的连续子集后还要重新遍历去一遍遍找元素,所以我这里用二维数据存上一个点的下标,将数组做成链表的形式。我觉得我思路没问题,但是性能告诉我不是这么回事。我也不知道为什么性能不咋地,反正就是这样了。只超过百分之七十五的人。我去看看性能排行第一的代码吧。
    怎么说呢,大同小异,但是处理不同,我这里是二维数组,人家是两个数组。但是性能就不如人家的好,伐开森~~我直接贴代码:

    class Solution {
        public int max(int a,int b){
            return a>b?a:b;
        }
        public List<Integer> largestDivisibleSubset(int[] nums) {
            int n=nums.length;
            if(n==0)
                return new ArrayList();
            int[] last=new int[n];
            for(int i=0;i<n;i++)
                last[i]=-1;
            int[] dp=new int[n];
            int ans=0;
            Arrays.sort(nums);
            for(int i=0;i<n;i++)
                for(int j=0;j<i;j++){
                    if(nums[i]%nums[j]==0&&dp[j]+1>dp[i]){
                        dp[i]=dp[j]+1;
                        if(dp[ans]<dp[i])
                            ans=i;
                        last[i]=j;
                    }
                }
            List<Integer> ansList=new ArrayList();
            while(ans!=-1){
                ansList.add(nums[ans]);
                ans=last[ans];
            }
            return ansList;
        }
    }
    

    然后其实我觉得这种题只能要看懂一种解,就都能看懂了,因为思路是差不多的。这个题也就这样了。
    今天的笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注。也祝大家工作顺顺利利。生活健健康康!!打广告。java技术交流群,群号130031711,欢迎各位大佬萌新踊跃加入!

    相关文章

      网友评论

        本文标题:leetCode进阶算法题+解析(四十)

        本文链接:https://www.haomeiwen.com/subject/hvbtvhtx.html