Leetcode --- 数学运算问题2

简介: Leetcode --- 数学运算问题2

1.完全平方数(279 - 中)



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


给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。


完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。


示例 :

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


思路:本题要求使用最少的数量,可以建立一个数组,元素的最大平方数不超过n,我们只需倒序进行搜索即可,使用深度优先遍历。


  • 终止条件:n = 0,即n全部用完,更新最小值,注意当i<0,直接返回。
  • 递归函数传入参数:(数组, 当前剩余值, 当前进行到的索引值,当前使用的完全平方数的个数)


注意:这里有两个优化


  • 使用乘法加速获取当前元素的下一步的起始最大次数,如示例,12/4 = 3,在4这个完全平方数,下一次最大的次数为3。
  • 倒序遍历过程中,对于完全平方数大于当前最小值ans的情况,进行剪枝。


代码实现:

private int ans = Integer.MAX_VALUE;
public int numSquares(int n) {
    int num = (int)Math.sqrt(n);
    int[] nums = new int[num];
    for (int i = 0; i < num; i++) {
        nums[i] = (i + 1) * (i + 1);
    }
    dfs(nums, n, nums.length - 1, 0);
    return ans == Integer.MAX_VALUE ? -1 : ans;
}
private void dfs(int[] nums, int n, int i, int count) {
    if (n == 0) {
        ans = Math.min(ans, count);
        return;
    }
    if (i < 0) return;
    int start = n / nums[i];
    for (int k = start; k >= 0 && k + count < ans; --k) {
        dfs(nums, n - k * nums[i], i - 1, k + count);
    }
}


2.平方数之和(633 - 中)



题目描述:给定一个非负整数 c ,你要判断是否存在两个整数 ab,使得 a^2 + b^2 = c


示例 :

输入:c = 5
输出:true
解释:1 * 1 + 2 * 2 = 5


思路:本题主要由两种解法,一种是类似两数之和的哈希解法,构建平方数数组;另一个则是像最大矩形面积的双指针解法,利用双指针寻找可能范围内的元素(最优解)。


注意:注意其中一个数可能是0,所以平方数组和循环条件要考虑边界。


代码实现:

// hash
public boolean judgeSquareSum(int c) {
    int n = (int)Math.sqrt(c);
    int[] nums = new int[n + 1];
    Set<Integer> set = new HashSet<>();
    for (int i = 0; i <= n; ++i) {
        nums[i] = i * i;
        set.add(nums[i]);
    }
    for (int num : nums) {
        if (set.contains(c - num)) {
            return true;
        }
    }
    return false;
}
// 双指针
public boolean judgeSquareSum(int c) {
    int l = 0, r = (int)Math.sqrt(c);
    while (l <= r) {
        if (l * l + r * r == c) {
            return true;
        } else if (l * l + r * r > c) {
            r--;
        } else {
            l++;
        }
    }
    return false;
}


3.鸡蛋掉落(887 - 难)



题目描述:给你 k 枚相同的鸡蛋,并可以使用一栋从第 1 层到第 n 层共有 n 层楼的建筑。


已知存在楼层 f ,满足 0 <= f <= n ,任何从 高于 f 的楼层落下的鸡蛋都会碎,从 f 楼层或比它低的楼层落下的鸡蛋都不会破。


每次操作,你可以取一枚没有碎的鸡蛋并把它从任一楼层 x 扔下(满足 1 <= x <= n)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。


请你计算并返回要确定 f 确切的值 的 最小操作次数 是多少?


示例 :

输入:k = 1, n = 2
输出:2
解释:
鸡蛋从 1 楼掉落。如果它碎了,肯定能得出 f = 0 。 
否则,鸡蛋从 2 楼掉落。如果它碎了,肯定能得出 f = 1 。 
如果它没碎,那么肯定能得出 f = 2 。 
因此,在最坏的情况下我们需要移动 2 次以确定 f 是多少。


思路:这是一个经典动态规划问题,可以看一下李永乐老师的双蛋问题,比较通俗易懂。


  • dp[i][j]:使用 i 个鸡蛋,j 层楼梯(注意:这里 j 不表示高度,代表区间)的情况下,的最少实验的次数(约束条件)。
  • 第一个鸡蛋扔到第x层楼,两种状态:碎dp[i - 1][x- 1]或者没碎dp[i][j - x],最终枚举所有扔鸡蛋的情况,即枚举x。
  • 对于上边找x的过程,我们可以通过二分查找进行优化。dp[i - 1][x- 1]是一个随x(层数)的增加而单调递增的函数,相反,dp[i][j - x]随着层数x单调递减,我们的目标找这两函数的交点,类似于数组中找山峰/峡谷(最大/最小值),那么显然就是二分提升效率了。
  • 第一个鸡蛋在哪里扔(也算一次操作),这也是状态转移方程中为什么+1.


代码实现:

public int superEggDrop(int k, int n) {
    // k 鸡蛋数 n 为楼层数
    int[][] dp = new int[k + 1][n + 1];
    for (int i = 1; i <= k; ++i) {
        dp[i][1] = 1;
    }
    for (int i = 1; i <= n; ++i) {
        dp[1][i] = i;
    }
    for(int i = 2; i <= k; ++i){
        for(int j = 2; j <= n; ++j){
            // 未使用二分的解法 x为所选楼层
            // int min = Integer.MAX_VALUE;
            // for(int x = 1; x <= j; ++x){
            //     min = Math.min(min, Math.max(dp[i - 1][x - 1], dp[i][j - x]));
            // }
            // dp[i][j] = 1 + min;
            // 改用二分查找(第一次扔的层数,替换枚举)
            int l = 1, r = j;
            while(l + 1 < r){
                int mid = l + r >> 1; 
                if(dp[i-1][mid-1] > dp[i][j-mid]){
                    r = mid;
                } else {
                    l = mid;
                }
            }
            int leftVal = Math.max(dp[i - 1][l - 1], dp[i][j - l]);
            int rightVal = Math.max(dp[i - 1][r - 1], dp[i][j - r]);
            dp[i][j] = 1 + Math.min(leftVal, rightVal);
        }
    }
    // for(int[] d:dp){
    //     System.out.println(Arrays.toString(d));
    // }
    return dp[k][n];
}


4.打印从1到最大的n位数(补充)



题目描述:输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。


示例 :

输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]


代码实现:

public int[] printNumbers(int n) {
    int x = (int)Math.pow(10, n);
    int[] nums = new int[x - 1];
    for (int i = 0; i < x - 1; ++i) {
        nums[i] = i + 1;
    }
    return nums;
}


5.自除数(728 - 易)



题目描述:自除数 是指可以被它包含的每一位数除尽的数。还有,自除数不允许包含 0 。128 是一个自除数,因为 128 % 1 == 0,128 % 2 == 0,128 % 8 == 0。


给定上边界和下边界数字,输出一个列表,列表的元素是边界(含边界)内所有的自除数。


示例 :

输入: 上边界left = 1, 下边界right = 22
输出: [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 15, 22]


代码实现:

public List<Integer> selfDividingNumbers(int left, int right) {
    List<Integer> ans = new ArrayList<>();
    for (int i = left; i <= right; ++i) {
        if (selfDividingNumbers(i)) {
            ans.add(i);
        }
    }
    return ans;
}
// 是否为自除数
public boolean selfDividingNumbers(int num) {
    int i = num;
    while (i > 0) {
        int x = i % 10;
        if (x == 0 || num % x != 0) {
            return false;
        } 
        i /= 10;
    }
    return true;
}


6.各位相加(258 - 易)



题目描述:给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数。


进阶: 你可以不使用循环或者递归,且在 O(1) 时间复杂度内解决这个问题吗?


示例 :

输入: 38
输出: 2 
解释: 各位相加的过程为:3 + 8 = 11, 1 + 1 = 2。 由于 2 是一位数,所以返回 2。


思路:本题迭代比较简单,引入一个辅助变量tmp即可。


对于进阶问题,参考@wangliang大佬:


涉及数学上一个名词,数根。数根可以计算模运算的同余,对于非常大的数字的情况下可以节省很多时间。

数字根可作为一种检验计算正确性的方法。例如,两数字的和的数根等于两数字分别的数根的和。

另外,数根也可以用来判断数字的整除性,如果数根能被3或9整除,则原来的数也能被3或9整除。


上边的应用转化一下:


  • 能够被9整除的整数,各位上的数字加起来也必然能被9整除,所以,连续累加起来,最终必然就是9。
  • 不能被9整除的整数,各位上的数字加起来,结果对9取模,和初始数对9取摸,是一样的,所以,连续累加起来,最终必然就是初始数对9取摸。


看下边一个规律:

原数: 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
数根: 1 2 3 4 5 6 7 8 9  1  2  3  4  5  6  7  8  9  1  2  3  4  5  6  7  8  9  1  2  3


结合上边的规律,对于给定的 n 有三种情况。


  • n 是 0 ,数根就是 0。
  • n 不是 9 的倍数,数根就是 n 对 9 取余,即 n % 9。
  • n 是 9 的倍数,数根就是 9。


原数是 n,树根就可以表示成 (n-1) % 9 + 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
偏移: 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 26 27 28 29 
取余: 0 1 2 3 4 5 6 7 8  0  1  2  3  4  5  6  7  8  0  1  2  3  4  5  6  7  8  0  1  2  
数根: 1 2 3 4 5 6 7 8 9  1  2  3  4  5  6  7  8  9  1  2  3  4  5  6  7  8  9  1  2  3


为什么要先偏移,再加1呢?假设如果可以被 9 整除,直接取模得到是0,按照数根的应用应该是9才对,我们可以进行偏移之后,再进行取余+1.


代码实现:

public int addDigits(int num) {
    if (num < 9) return num;
    int tmp = num;
    while (tmp > 9) {
        num = tmp;
        tmp = 0;
        while (num > 0) {
            tmp += num % 10;
            num /= 10;
        }
    }
    return tmp;
}
// 是否为自除数
public int addDigits(int num) {
    return (num - 1) % 9 + 1;
}


7.范围求和II(598 - 易)



题目描述:给定一个初始元素全部为 0,大小为 m*n 的矩阵 M 以及在 M 上的一系列更新操作。


操作用二维数组表示,其中的每个操作用一个含有两个正整数 a 和 b 的数组表示,含义是将所有符合 0 <= i < a 以及 0 <= j < b 的元素 M[i][j] 的值都增加 1。


在执行给定的一系列操作后,你需要返回矩阵中含有最大整数的元素个数。


示例 :

输入: 
m = 3, n = 3
operations = [[2,2],[3,3]]
输出: 4
解释: 
初始状态, M = 
[[0, 0, 0],
 [0, 0, 0],
 [0, 0, 0]]
执行完操作 [2,2] 后, M = 
[[1, 1, 0],
 [1, 1, 0],
 [0, 0, 0]]
执行完操作 [3,3] 后, M = 
[[2, 2, 1],
 [2, 2, 1],
 [1, 1, 1]]
M 中最大的整数是 2, 而且 M 中有4个值为2的元素。因此返回 4。


思路:分析一下题目。可以确定的是最大值一定是 M[0][0] ,范围一定是数组中最小的行和最小的列。有了上边两个结论,因为本题只是统计个数。


注意:复用了变量m 和 n。


代码实现:

public int maxCount(int m, int n, int[][] ops) {
    for (int[] r : ops) {
        m = Math.min(m, r[0]);
        n = Math.min(n, r[1]);
    }
    return m * n;
}


相关文章
|
算法 Android开发 Python
LeetCode 周赛上分之旅 #43 计算机科学本质上是数学吗?
学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越抽象,它能覆盖的问题域就越广,理解难度也更复杂。在这个专栏里,小彭与你分享每场 LeetCode 周赛的解题报告,一起体会上分之旅。
62 0
LeetCode 周赛上分之旅 #43 计算机科学本质上是数学吗?
|
5月前
|
存储 算法 数据挖掘
python 数学+减治、下一个排列法、DFS回溯法实现:第 k 个排列【LeetCode 题目 60】
python 数学+减治、下一个排列法、DFS回溯法实现:第 k 个排列【LeetCode 题目 60】
|
5月前
|
存储 算法
力扣每日一题 6/20 数学+数组
力扣每日一题 6/20 数学+数组
34 1
|
5月前
|
算法
力扣每日一题 6/16 字符串 + 随机一题 动态规划/数学
力扣每日一题 6/16 字符串 + 随机一题 动态规划/数学
45 0
|
6月前
[leetcode 数位运算] 2578.最小分割和
[leetcode 数位运算] 2578.最小分割和
|
6月前
leetcode:268. 丢失的数字(异或运算)
leetcode:268. 丢失的数字(异或运算)
33 0
|
6月前
leetcode:292. Nim 游戏(数学推理)
leetcode:292. Nim 游戏(数学推理)
35 0
|
6月前
|
算法 测试技术 C#
【数学】LeetCode1526: 形成目标数组的子数组最少增加次数
【数学】LeetCode1526: 形成目标数组的子数组最少增加次数
|
6月前
leetcode-592:分数加减运算
leetcode-592:分数加减运算
53 0