zoukankan      html  css  js  c++  java
  • LeetCode Notes_#279 完全平方数

    LeetCode Notes_#279 完全平方数

    Contents

    题目

    给定正整数n,找到若干个完全平方数(比如1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
    示例1:

    输入: n = 12
    输出: 3 
    解释: 12 = 4 + 4 + 4.

    示例 2:

    输入: n = 13
    输出: 2
    解释: 13 = 4 + 9.

    思路分析

    本题有点像剪绳子问题,要把一个数字拆分,且拆分出来的部分,要满足一个最优化的条件。
    方法比较多,包括暴力递归,记忆化递归,动态规划,BFS,数学法。
    比较常见的就是中间的3种,暴力递归性能差,数学法则没有普适性,需要记下来。记忆化递归和动态规划则思路非常相似。
    所以记录了比较常见的,具有普适性的两种写法,动态规划法和BFS法。

    解答

    方法1:动态规划

    1. 计算平方数的数组,下标范围是1~(int)sqrt(n),每个位置的元素是下标的平方
    2. 根据状态转移方程,从dp[0]开始计算,迭代地计算出dp[n],就是结果
    class Solution {
        public int numSquares(int n) {
            //[0,n]一共是n + 1个数字
            int dp[] = new int[n + 1];
            //因为下面用到Math.min取最小值,所以初始化为最大,否则可能不被更新
            Arrays.fill(dp, Integer.MAX_VALUE);
            //没有实际意义,因为输入都是正数
            //设置为0可以正确计算dp[1],同时使得数组下标与numSquares(n)的结果对应起来
            dp[0] = 0;
            //max_square_index的数字的平方是小于n的最大数字,更大的平方数不可能组成n
            int max_square_index = (int)Math.sqrt(n);
            //存储所有可能组成n的平方数,下标范围是[1, max_square_index]
            int[] square_nums = new int[max_square_index + 1];
            for(int i = 1;i <= max_square_index;i++){
                //square_nums[0]是默认值0
                square_nums[i] = i * i;
            }
            //从dp[1]开始,迭代地计算出dp[n],就是最终结果
            for(int i = 1;i <= n;i++){
                for(int j = 1;j <= max_square_index;j++){
                    if(i >= square_nums[j]) 
                        dp[i] = Math.min(dp[i], dp[i - square_nums[j]] + 1);
                }
            }
            return dp[n];
        }
    }
    

    复杂度分析

    时间复杂度:
    空间复杂度:

    方法2:BFS

    将原问题转化为一个n叉树的BFS遍历问题。
    如下图,将输入的n作为根节点。
    每个节点的孩子节点的值是当前节点减去一个平方数(依次减去1,4,9...)。
    则值为0的节点距离根节点的最短路径就是结果。
    例如下图中的橙色节点,距离根节点距离是3,可以写出分解的表达式12 = 4 + 4 + 4,每一条边代表的就是一个平方数。
    代码和二叉树的层序遍历很相似,存在以下几个不同点:

    1. 这里的下一层节点不是通过左右指针来寻找,而是通过当前层节点减去平方数计算得到。
    2. 重复节点只需入队一次。
      • 如果重复节点位于同一层,那么两个节点的子树结构完全相同,计算出来的最短路径一样,如果保留则造成多余的计算。
      • 如果重复节点位于不同层,那么后访问到的节点必然离根节点更远,又因为两个节点的子树结构完全相同(即到0节点的距离相同),所以第二次之后访问的重复节点可以被忽略。
    3. 需要添加一个计数器level,记录BFS的层数。
    class Solution {
        public int numSquares(int n) {
            Queue<Integer> queue = new LinkedList<>();
            //用于判断重复节点
            HashSet<Integer> visited = new HashSet<>();
            //level从0开始算起
            int level = 0;
            queue.offer(n);
            visited.add(n);
            while(!queue.isEmpty()){
                //size是一层节点的数量
                int size = queue.size();
                for(int i = 0;i < size;i++){
                    int cur = queue.poll();
                    //遇到0节点,返回当前的level
                    if(cur == 0) return level;
                    for(int j = 1;j * j <= cur;j++){
                        int next = cur - j * j;//当前数字减去一个平方数,就是下一层的数字
                        if(!visited.contains(next)){
                            queue.offer(next);
                            visited.add(next);
                        }
                    }
                }
                level++;
            }
            //除非输入负数,不然任何数字都可以由1组成
            return -1;
        }
    }

    官方题解中使用了HashSet数据结构,将其当作队列使用,还可以起到避免重复节点的作用。不过看知乎上的讨论,HashSet并不保证输出是有序的,是否有序跟jdk版本有关,所以最好别这样写。
    Java遍历HashSet为什么输出是有序的? - 知乎
    相比于官方题解,这个题解更加清晰,供参考。
    详细通俗的思路分析,多解法

    复杂度分析

    不会分析...待学习
    时间复杂度:O()
    空间复杂度:O()

  • 相关阅读:
    各种排序算法的时间复杂度
    svn版本管理系统出现的问题解决办法
    算法时间复杂度
    js处理时间戳显示的问题
    cache缓存的BUG
    使用phpstorm提交svn代码版本管理系统遇到的问题解决办法
    20161101.20161115这两周的开发总结
    mac 上安装 redis
    终极 shell zsh
    在 mac 上利用 homebrew 安装软件
  • 原文地址:https://www.cnblogs.com/Howfars/p/13616393.html
Copyright © 2011-2022 走看看