算法题 有效的完全平方数

LeetCode 367. 有效的完全平方数

问题描述

给定一个 正整数 num,判断它是否是一个完全平方数(即是否存在某个整数 x,使得 x² = num)。

要求不要使用任何内置的库函数,例如 sqrt

示例

输入: num = 16
输出: true
解释: 4 * 4 = 16

输入: num = 14
输出: false
解释: 14 不是完全平方数

约束条件

  • 1 <= num <= 2³¹ - 1

算法思路

方法一:二分查找

核心思想:完全平方数的平方根一定在 [1, num] 范围内,使用二分查找快速定位。

步骤

  1. 设置左边界 left = 1,右边界 right = num
  2. left <= right 时:
    • 计算中点 mid = left + (right - left) / 2
    • 计算 mid² 并与 num 比较
    • 如果 mid² == num,返回 true
    • 如果 mid² < num,说明平方根在右半部分,left = mid + 1
    • 如果 mid² > num,说明平方根在左半部分,right = mid - 1
  3. 如果循环结束还没找到,返回 false

注意:计算 mid² 时要考虑整数溢出问题,可以使用 long 类型。

方法二:牛顿迭代法(数学)

核心思想:使用牛顿迭代公式求平方根,然后验证结果。

牛顿迭代公式x_{n+1} = (x_n + num / x_n) / 2

步骤

  1. 初始化 x = num
  2. 迭代计算直到收敛:x = (x + num / x) / 2
  3. 验证最终的 x 是否满足 x² == num

方法三:数学(奇数和)

核心思想:完全平方数可以表示为前 n 个奇数的和:

  • 1 = 1
  • 4 = 1 + 3
  • 9 = 1 + 3 + 5
  • 16 = 1 + 3 + 5 + 7

步骤

  1. 初始化 odd = 1
  2. 不断从 num 中减去奇数:num -= odd
  3. 如果 num == 0,说明是完全平方数
  4. 如果 num < 0,说明不是完全平方数

代码实现

方法一:二分查找

class Solution {
    /**
     * 判断一个正整数是否为完全平方数(二分查找)
     * 
     * @param num 正整数
     * @return 如果是完全平方数返回true,否则返回false
     */
    public boolean isPerfectSquare(int num) {
        // 边界情况处理
        if (num == 1) {
            return true;
        }
        
        long left = 1;          // 左边界,使用long避免溢出
        long right = num;       // 右边界
        
        while (left <= right) {
            long mid = left + (right - left) / 2;  // 防止溢出的中点计算
            long square = mid * mid;               // 计算mid的平方
            
            if (square == num) {
                return true;                       // 找到平方根
            } else if (square < num) {
                left = mid + 1;                    // 平方根在右半部分
            } else {
                right = mid - 1;                   // 平方根在左半部分
            }
        }
        
        return false;  // 未找到
    }
}

方法二:牛顿迭代

class Solution {
    /**
     * 判断一个正整数是否为完全平方数(牛顿迭代)
     * 
     * @param num 正整数
     * @return 如果是完全平方数返回true,否则返回false
     */
    public boolean isPerfectSquare(int num) {
        if (num == 1) {
            return true;
        }
        
        long x = num;  // 使用long避免溢出
        
        // 牛顿迭代:x = (x + num/x) / 2
        while (x * x > num) {
            x = (x + num / x) / 2;
        }
        
        return x * x == num;
    }
}

方法三:数学(奇数和)

class Solution {
    /**
     * 判断一个正整数是否为完全平方数(奇数和)
     * 利用性质:n² = 1 + 3 + 5 + ... + (2n-1)
     * 
     * @param num 正整数
     * @return 如果是完全平方数返回true,否则返回false
     */
    public boolean isPerfectSquare(int num) {
        int odd = 1;  // 从第一个奇数开始
        
        // 不断减去奇数
        while (num > 0) {
            num -= odd;
            odd += 2;  // 下一个奇数
        }
        
        // 如果正好减到0,说明是完全平方数
        return num == 0;
    }
}

算法分析

方法时间复杂度空间复杂度
二分查找O(log n)O(1)
牛顿迭代O(log log n)O(1)
奇数和O(√n)O(1)

算法过程

num = 16

二分查找:

  1. left = 1, right = 16
  2. mid = 8, square = 64 > 16right = 7
  3. mid = 4, square = 16 == 16 → 返回 true

牛顿迭代:

  1. x = 16
  2. x = (16 + 16/16) / 2 = 8
  3. x = (8 + 16/8) / 2 = 5
  4. x = (5 + 16/5) / 2 = 4
  5. 4² = 16 == 16 → 返回 true

奇数和:

  1. num = 16 - 1 = 15
  2. num = 15 - 3 = 12
  3. num = 12 - 5 = 7
  4. num = 7 - 7 = 0 → 返回 true

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 测试用例1:标准完全平方数
    System.out.println("Test 1 (16): " + solution.isPerfectSquare(16)); // true
    
    // 测试用例2:非完全平方数
    System.out.println("Test 2 (14): " + solution.isPerfectSquare(14)); // false
    
    // 测试用例3:边界情况-1
    System.out.println("Test 3 (1): " + solution.isPerfectSquare(1));   // true
    
    // 测试用例4:大完全平方数
    System.out.println("Test 4 (10000): " + solution.isPerfectSquare(10000)); // true
    
    // 测试用例5:大非完全平方数
    System.out.println("Test 5 (2147395600): " + solution.isPerfectSquare(2147395600)); // true (46340²)
    
    // 测试用例6:接近最大值
    System.out.println("Test 6 (2147483647): " + solution.isPerfectSquare(2147483647)); // false
    
    // 测试用例7:小完全平方数
    System.out.println("Test 7 (4): " + solution.isPerfectSquare(4));   // true
    System.out.println("Test 8 (9): " + solution.isPerfectSquare(9));   // true
    System.out.println("Test 9 (25): " + solution.isPerfectSquare(25)); // true
    
    // 测试用例8:质数(非完全平方数)
    System.out.println("Test 10 (17): " + solution.isPerfectSquare(17)); // false
}

关键点

  1. 整数溢出

    • 计算 mid * mid 时可能溢出,使用 long 类型
    • 中点计算使用 left + (right - left) / 2 避免溢出
  2. 边界情况

    • num = 1 是完全平方数(1² = 1)
    • 题目保证 num >= 1,无需处理负数
  3. 数学

    • 完全平方数的平方根一定在 [1, num] 范围内
    • 对于大数,平方根远小于原数,所以二分查找效率很高

常见问题

  1. 为什么不用 Math.sqrt()

    • 题目明确要求不能使用内置库函数
  2. 二分查找的边界条件怎么确定?

    • 左边界从 1 开始(因为 num ≥ 1)
    • 右边界可以优化为 num/2 + 1,对于大数优化效果有限
  3. 牛顿迭代法为什么有效?

    • 牛顿迭代法是求解方程 f(x) = 0 的数值方法
    • 对于求平方根,等价于求解 x² - num = 0
  4. 如何处理整数溢出?

    • 使用 long 类型存储中间结果
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值