LeetCode 热题 100 之 279. 完全平方数 322. 零钱兑换 139. 单词拆分 300. 最长递增子序列

本次四道题

  1. 完全平方数

  2. 零钱兑换

  3. 单词拆分

  4. 最长递增子序列

279. 完全平方数


复制代码
class Solution {
    public int numSquares(int n) {
        int[] dp = new int[n + 1];
        // 初始化:最坏情况全是1,即 dp[i] = i
        for (int i = 1; i <= n; i++) {
            dp[i] = i;
        }
        // 遍历所有完全平方数作为"硬币"
        for (int j = 1; j * j <= n; j++) {
            int square = j * j;
            for (int i = square; i <= n; i++) {
                dp[i] = Math.min(dp[i], dp[i - square] + 1);
            }
        }
        return dp[n];
    }
}
解题思路1:动态规划

这是一道典型的 "最少硬币" 类问题,我们可以用动态规划来求解:

  1. 定义状态dp[i] 表示和为 i 的完全平方数的最少数量。

  2. 初始化

    1. dp[0] = 0,因为和为 0 不需要任何数。

    2. 其余 dp[i] 初始化为一个较大的值(如 i,最坏情况是全用 1),表示初始不可达。

  3. 状态转移 :对于每个数 i,我们尝试减去所有小于等于 i 的完全平方数 j*j,并取最小值:dp[i]=min(dp[i], dp[i−j2]+1)

  4. 结果dp[n] 即为所求。


复制代码
class Solution {
    public int numSquares(int n) {
        if (isPerfectSquare(n)) {
            return 1;
        }
        if (checkAnswer4(n)) {
            return 4;
        }
        for (int i = 1; i * i <= n; i++) {
            int j = n - i * i;
            if (isPerfectSquare(j)) {
                return 2;
            }
        }
        return 3;
    }

    // 判断是否为完全平方数
    public boolean isPerfectSquare(int x) {
        int y = (int) Math.sqrt(x);
        return y * y == x;
    }

    // 判断是否能表示为 4^k*(8m+7)
    public boolean checkAnswer4(int x) {
        while (x % 4 == 0) {
            x /= 4;
        }
        return x % 8 == 7;
    }
}

解题思路2:四平方和定理

任意一个正整数都可以表示为至多 4 个完全平方数的和 。因此,本题的答案只可能是 1、2、3 或 4

我们可以根据以下规则快速判断结果:

  1. 结果为 1 :当且仅当 n 本身是一个完全平方数。

  2. 结果为 4 :当且仅当 n 满足 n % 8 == 7

  3. 结果为 2 :当且仅当 n 可以表示为两个完全平方数之和,即存在整数 ab,使得 n = a² + b²

  4. 结果为 3:如果以上情况都不满足,则结果为 3。


322. 零钱兑换


复制代码
public class Solution {
    public int coinChange(int[] coins, int amount) {
        // 定义dp数组,dp[i]代表凑成金额i所需的最少硬币数
        int[] dp = new int[amount + 1];
        // 初始化:将所有值设为一个大于最大可能硬币数(amount)的数,代表初始不可达
        java.util.Arrays.fill(dp, amount + 1);
        // 金额0需要0枚硬币
        dp[0] = 0;

        // 遍历从1到目标金额的每一个金额
        for (int i = 1; i <= amount; i++) {
            // 对于每一种硬币,尝试使用它来凑成当前金额
            for (int coin : coins) {
                // 如果当前硬币的面额小于等于当前金额,才可以使用
                if (coin <= i) {
                    // 状态转移方程:取最小值
                    dp[i] = Math.min(dp[i], dp[i - coin] + 1);
                }
            }
        }

        // 如果dp[amount]仍然大于amount,说明无法凑成,返回-1;否则返回结果
        return dp[amount] > amount ? -1 : dp[amount];
    }
}

解题思路1:动态规划
  1. 状态定义dp[i] 表示凑成总金额 i 所需要的最少硬币个数。

  2. 初始化

    1. dp[0] = 0,因为凑成金额 0 不需要任何硬币。

    2. 其余 dp[i] 初始化为一个很大的值(比如 amount + 1),表示初始状态下无法凑成该金额。

  3. 状态转移 :对于每个金额 i,遍历所有硬币面额 coin

    1. 如果 coin <= i,则 dp[i] = min(dp[i], dp[i - coin] + 1)

    2. 这个公式的含义是,凑成 i 的最少硬币数,等于凑成 i - coin 的最少硬币数再加 1(即加上当前这枚硬币)。

  4. 结果判断 :如果最终 dp[amount] 仍然大于 amount,说明无法凑成该金额,返回 -1;否则返回 dp[amount]


    解题思路2:记忆化搜素(记忆化递归)略

139. 单词拆分


复制代码
public class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        // 1. 将字典转成HashSet,加速单词查找
        Set<String> wordSet = new HashSet<>(wordDict);
        int n = s.length();
        
        // 2. 定义dp数组:dp[i]表示s的前i个字符能否被拆分
        boolean[] dp = new boolean[n + 1];
        
        // 3. 初始化:空字符串可以被拆分
        dp[0] = true;

        // 4. 遍历结束位置i(表示前i个字符)
        for (int i = 1; i <= n; i++) {
            // 5. 遍历起始位置j,尝试拆分出子串s[j..i-1]
            for (int j = 0; j < i; j++) {
                // 核心条件:前j个字符可拆分 + 子串在字典中
                if (dp[j] && wordSet.contains(s.substring(j, i))) {
                    dp[i] = true;
                    break; // 找到一种拆分方式即可,无需继续遍历j
                }
            }
        }

        // 6. 返回整个字符串的拆分结果
        return dp[n];
    }
}

解题思路1:动态规划
  1. 状态定义 :定义布尔数组 dp[],其中 dp[i] 表示字符串 s i 个字符 (即 s[0..i-1])能否被字典拆分。

  2. 初始化dp[0] = true,表示空字符串可以被拆分(作为递归的基准条件)。

  3. 状态转移

    1. 遍历字符串的每个结束位置 i(从 1 到字符串长度)。

    2. 对于每个 i,向前遍历起始位置 j(从 0 到i)。

    3. dp[j]true(前j个字符可拆分),且子串 s[j..i-1] 存在于字典中,则 dp[i] = true

  4. 结果 :返回 dp[s.length()],即整个字符串是否可拆分。

优化点

使用 HashSet 存储字典,将单词查找的时间复杂度从 O(n) 降为 O(1),提升效率。


300. 最长递增子序列


复制代码
public class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int[] dp = new int[nums.length];
        // 初始化,每个元素自身长度为1
        Arrays.fill(dp, 1);
        int maxLen = 1;
        
        for (int i = 1; i < nums.length; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[j] < nums[i]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            maxLen = Math.max(maxLen, dp[i]);
        }
        return maxLen;
    }
}

解题思路1:动态规划(O (n²))

思路

  • 定义 dp[i] 表示以 nums[i] 结尾的最长递增子序列的长度。

  • 初始时,每个元素自身就是一个长度为 1 的子序列,所以 dp[i] = 1

  • 对于每个 i,遍历 j0i-1

    • 如果 nums[j] < nums[i],则 dp[i] = max(dp[i], dp[j] + 1)
  • 最终答案就是 dp 数组中的最大值。


复制代码
public class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int[] tails = new int[nums.length];
        int size = 0;
        for (int num : nums) {
            int left = 0, right = size;
            // 二分查找第一个 >= num 的位置
            while (left < right) {
                int mid = (left + right) / 2;
                if (tails[mid] < num) {
                    left = mid + 1;
                } else {
                    right = mid;
                }
            }
            tails[left] = num;
            if (left == size) {
                size++;
            }
        }
        return size;
    }
}

解题思路2:贪心 + 二分查找(O (n log n))

维护一个数组 tails,其中 tails[i] 表示长度为 i+1 的递增子序列的最小可能末尾值。

遍历数组 nums

  • 如果当前数大于 tails 的最后一个元素,直接加入 tails

  • 否则,用二分查找找到 tails 中第一个大于等于当前数的位置,用当前数替换它。

tails 的长度就是最长递增子序列的长度。

相关推荐
shamalee1 小时前
Spring Security 新版本配置
java·后端·spring
美式请加冰1 小时前
位运算符的介绍和使用
数据结构·算法
tankeven1 小时前
HJ127 小红的双生串
c++·算法
Fcy6481 小时前
与链表有关的算法题
数据结构·算法·链表
KerwinChou_CN1 小时前
LangGraph 快速入门
服务器·网络·算法
不光头强1 小时前
Java中的异常
java·开发语言
毕设源码-赖学姐1 小时前
【开题答辩全过程】以 高校资源共享平台的设计与实现 为例,包含答辩的问题和答案
java
shamalee2 小时前
MS SQL Server partition by 函数实战二 编排考场人员
java·服务器·开发语言
aisifang002 小时前
MS SQL Server partition by 函数实战三 成绩排名
java