算法题 平方数之和

LeetCode 633. 平方数之和

问题描述

给定一个非负整数 c,你要判断是否存在两个整数 ab,使得 a² + b² = c

示例

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

输入: c = 3
输出: false
解释: 不存在整数a,b使得a² + b² = 3

输入: c = 4
输出: true
解释: 0² + 2² = 0 + 4 = 4

算法思路

经典的数学问题

方法

  1. 暴力枚举:枚举所有可能的a值,检查c-a²是否为完全平方数
  2. 双指针:利用有序,在[0, √c]范围内使用双指针
  3. 费马定理:利用数论中的费马平方和定理

核心

  • 如果 a² + b² = c,那么 ab 的范围都在 [0, √c]
  • 假设 a ≤ b,避免重复计算
  • a = 0 时,只需要检查 c 是否为完全平方数

代码实现

方法一:双指针

class Solution {
    /**
     * 双指针:在[0, √c]范围内使用双指针
     * 
     * @param c 非负整数
     * @return 是否存在两个整数的平方和等于c
     */
    public boolean judgeSquareSum(int c) {
        // 使用long避免整数溢出
        long left = 0;
        long right = (long) Math.sqrt(c);
        
        while (left <= right) {
            long sum = left * left + right * right;
            
            if (sum == c) {
                return true;
            } else if (sum < c) {
                left++;  // 和太小,增大left
            } else {
                right--; // 和太大,减小right
            }
        }
        
        return false;
    }
}

方法二:暴力枚举

class Solution {
    /**
     * 暴力枚举:枚举a的可能值,检查c-a²是否为完全平方数
     * 
     * @param c 非负整数
     * @return 是否存在两个整数的平方和等于c
     */
    public boolean judgeSquareSum(int c) {
        // a的范围是[0, √c]
        for (long a = 0; a * a <= c; a++) {
            long bSquared = c - a * a;
            long b = (long) Math.sqrt(bSquared);
            
            // 检查b²是否等于bSquared
            if (b * b == bSquared) {
                return true;
            }
        }
        
        return false;
    }
}

方法三:优化暴力枚举

class Solution {
    /**
     * 优化暴力枚举:只枚举到√(c/2),利用a≤b
     * 
     * @param c 非负整数
     * @return 是否存在两个整数的平方和等于c
     */
    public boolean judgeSquareSum(int c) {
        // 由于a ≤ b,所以a² ≤ c/2,a ≤ √(c/2)
        long limit = (long) Math.sqrt(c / 2.0);
        
        for (long a = 0; a <= limit; a++) {
            long bSquared = c - a * a;
            long b = (long) Math.sqrt(bSquared);
            
            if (b * b == bSquared) {
                return true;
            }
        }
        
        return false;
    }
}

方法四:费马定理(数学)

class Solution {
    /**
     * 费马定理:利用数论中的费马平方和定理
     * 一个正整数n可以表示为两个平方数之和,当且仅当
     * n的质因数分解中,如4k+3的质数的指数都是偶数
     * 
     * @param c 非负整数
     * @return 是否存在两个整数的平方和等于c
     */
    public boolean judgeSquareSum(int c) {
        if (c == 0) return true;
        
        // 处理质因数2
        while (c % 2 == 0) {
            c /= 2;
        }
        
        // 检查如4k+3的质因数
        for (int i = 3; i * i <= c; i += 2) {
            int count = 0;
            while (c % i == 0) {
                count++;
                c /= i;
            }
            // 如果i ≡ 3 (mod 4) 且指数为奇数,则不能表示为平方和
            if (i % 4 == 3 && count % 2 == 1) {
                return false;
            }
        }
        
        // 如果剩余的c > 1,检查它是否为4k+3形式
        return c % 4 != 3;
    }
}

算法分析

  • 时间复杂度
    • 双指针:O(√c) - 最多移动√c次
    • 暴力枚举:O(√c) - 枚举√c个值
    • 优化暴力枚举:O(√c) - 常数因子更小
    • 费马定理:O(√c) - 质因数分解的复杂度
  • 空间复杂度:O(1) - 所有方法都只使用常数额外空间

算法过程

输入:c = 5

双指针

left = 0, right = √5 ≈ 2

1: left=0, right=2
  sum = 0² + 2² = 0 + 4 = 4 < 5 → left++

2: left=1, right=2  
  sum = 1² + 2² = 1 + 4 = 5 == 5 → 返回true

输入:c = 3

left = 0, right = √3 ≈ 1

1: left=0, right=1
  sum = 0² + 1² = 0 + 1 = 1 < 3 → left++

2: left=1, right=1
  sum = 1² + 1² = 1 + 1 = 2 < 3 → left++

3: left=2, right=1 → left > right → 返回false

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 测试用例1:标准示例
    System.out.println("Test 1 (c=5): " + solution.judgeSquareSum(5)); // true
    
    // 测试用例2:标准示例
    System.out.println("Test 2 (c=3): " + solution.judgeSquareSum(3)); // false
    
    // 测试用例3:包含0的情况
    System.out.println("Test 3 (c=4): " + solution.judgeSquareSum(4)); // true (0² + 2²)
    System.out.println("Test 4 (c=0): " + solution.judgeSquareSum(0)); // true (0² + 0²)
    System.out.println("Test 5 (c=1): " + solution.judgeSquareSum(1)); // true (0² + 1²)
    
    // 测试用例4:大数
    System.out.println("Test 6 (c=2147483647): " + solution.judgeSquareSum(2147483647)); // false
    System.out.println("Test 7 (c=1000000000): " + solution.judgeSquareSum(1000000000)); // true
    
    // 测试用例5:完全平方数
    System.out.println("Test 8 (c=16): " + solution.judgeSquareSum(16)); // true (0² + 4²)
    System.out.println("Test 9 (c=25): " + solution.judgeSquareSum(25)); // true (3² + 4² 或 0² + 5²)
    
    // 测试用例6:边界情况
    System.out.println("Test 10 (c=2): " + solution.judgeSquareSum(2)); // true (1² + 1²)
    System.out.println("Test 11 (c=6): " + solution.judgeSquareSum(6)); // false
    System.out.println("Test 12 (c=7): " + solution.judgeSquareSum(7)); // false
    System.out.println("Test 13 (c=8): " + solution.judgeSquareSum(8)); // true (2² + 2²)
    
    // 测试用例7:验证4k+3质数的情况
    System.out.println("Test 14 (c=9): " + solution.judgeSquareSum(9)); // true (0² + 3²)
    System.out.println("Test 15 (c=21): " + solution.judgeSquareSum(21)); // false (21=3×7, 3和7都是4k+3形式)
    
}

关键点

  1. 整数溢出

    • 使用long类型避免a*ab*b溢出
  2. 双指针

    • 平方函数是单调递增的,双指针能保证找到所有可能的解
  3. 边界情况

    • c = 0:返回true(0² + 0² = 0)
    • c = 1:返回true(0² + 1² = 1)
    • 完全平方数:总是有解(0² + √c² = c)
  4. 数学

    • 费马平方和定理
    • 如4k+3的质数不能表示为两个平方数之和

常见问题

  1. 为什么双指针不会漏掉解?

    • 平方和函数在固定范围内是单调的
    • 如果当前和太小,只能增大left;如果太大,只能减小right
  2. 如何处理整数溢出?

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值