目录

  • 连续子数组的最大和
    • 1 题目描述
    • 2 解题(java)
      • 2.1 动态规划解析
      • 2.2 空间复杂度降低
      • 2.3 Java代码
    • 3 复杂性分析
  • 回文子串
    • 1 题目描述
    • 2 解题(Java)
      • 2.1 动态规划法
      • 2.2 中心扩展法
  • 最短无序连续子数组
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 分割等和子集
    • 1 题目描述
    • 2 解题(Java)
      • 2.1 解题思路:动态规划
      • 2.2 Java代码
    • 3 复杂性分析
  • 比特位计数
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 打家劫舍 III
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 零钱兑换
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 戳气球
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 最佳买卖股票时机含冷冻期
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 最长递增子序列
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 完全平方数
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 最大正方形
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 打家劫舍
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 区域和检索 - 数组不可变
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 乘积最大子数组
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 排序链表
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 单词拆分
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 数字序列中某一位的数字
    • 1 题目描述
    • 2 解题Java
      • 2.1 解题思路
        • 2.1.1 定义变量
        • 2.1.2 确定n所在数字的位数digit
        • 2.1.3 确定n所在的数字num
        • 2.1.4 确定n是num中的哪一数位
      • 2.2 代码
    • 3 复杂性分析
  • 不用加减乘除做加法
    • 1 题目描述
    • 2 解题(Java)
      • 2.1 解题思路
      • 2.2 代码
    • 3 复杂性分析
  • 二进制中1的个数
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 不同的二叉搜索树
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 编辑距离
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 爬楼梯
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 最小路径和
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 跳跃游戏
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 最大子序和
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 最长有效括号
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 礼物的最大价值
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 最长公共子串(牛客网)
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 股票的最大利润
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 构建乘积数组
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 把数字翻译成字符串
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 最长不含重复字符的子字符串
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 接雨水
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 斐波那契数列
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 青蛙跳台阶问题
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 剪绳子
    • 1 题目描述
    • 2 解题(动态规划)
      • 2.1 解题思路
      • 2.2 代码
      • 2.3 复杂性分析
    • 3 解题(数学)
      • 3.1 解题思路
      • 3.2 代码
      • 3.3 复杂性分析
  • 统计字典序元音字符串的数目
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 获取生成数组中的最大值
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 丑数
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • n个骰子的点数
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析
  • 最长回文子串
    • 1 题目描述
    • 2 解题(Java)
      • 2.1 动态规划法
      • 2.2 中心扩展法
  • 正则表达式匹配
    • 1 题目描述
    • 2 解题(Java)
    • 3 复杂性分析

连续子数组的最大和

1 题目描述

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)。

示例1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

提示:

1 <= arr.length <= 10^5
-100 <= arr[i] <= 100

2 解题(java)

2.1 动态规划解析

1 状态定义: 设动态规划列表 dp ,dp[i] 代表以元素 nums[i] 为结尾的连续子数组最大和;

2 转移方程: 若dp[i−1]≤0 ,说明 dp[i−1] 对 dp[i] 产生负贡献,即 dp[i−1]+nums[i] 还不如 nums[i] 本身大:

  • 当 dp[i−1]>0 时:执行 dp[i] = dp[i-1] + nums[i];
  • 当 dp[i−1]≤0 时:执行 dp[i] = nums[i];

3 初始状态: dp[0]=nums[0],即以 nums[0] 结尾的连续子数组最大和为 nums[0] ;

4 返回值: 返回 dp 列表中的最大值,代表全局最大值;

2.2 空间复杂度降低

  1. 由于 dp[i] 只与 dp[i-1] 和 nums[i] 有关系,因此每次只保留dp[i-1]即可;
  2. 由于省去 dp 列表使用的额外空间,因此空间复杂度从 O(N) 降至 O(1);

2.3 Java代码

class Solution {public int maxSubArray(int[] nums) {int pre = 0, maxAns = Integer.MIN_VALUE;for (int x : nums) {pre = Math.max(pre + x, x);maxAns = Math.max(maxAns, pre);}return maxAns;}
}

3 复杂性分析

  • 时间复杂度 O(N) : 遍历一次数组 nums 即可获得结果,使用 O(N) 时间;
  • 空间复杂度 O(1) : 占用常数大小的额外空间;

回文子串

1 题目描述

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1

输入:“abc”
输出:3
解释:三个回文子串: “a”, “b”, “c”

示例 2

输入:“aaa”
输出:6
解释:6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”

提示

  • 输入的字符串长度不会超过 1000 。

2 解题(Java)

2.1 动态规划法

class Solution {public int countSubstrings(String s) {int n = s.length();int res = 0;boolean[][] dp = new boolean[n][n];for (int j=0; j<s.length(); j++) {for (int i=0; i<=j; i++) {if (s.charAt(i) == s.charAt(j) && (j-i < 3 || dp[i+1][j-1])) {dp[i][j] = true;res++;}}}return res;}
}

复杂性分析

时间复杂度为 O(N2),空间复杂度为 O(N2)。

2.2 中心扩展法

两种情况:

  1. 以1个点作为中心点向两端扩展;
  2. 以2个点作为中心点向两端扩展;
class Solution {public int countSubstrings(String s) {int res = 0;for (int i=0; i<=s.length()*2-2; i++) {int left = i / 2;int right = (i + 1) / 2;while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {res++;left--;right++;}}return res;}
}

复杂性分析

时间复杂度为 O(N2),空间复杂度为 O(1)。

最短无序连续子数组

1 题目描述

给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。

请你找出符合题意的 最短 子数组,并输出它的长度。

示例 1

输入:nums = [2,6,4,8,10,9,15]
输出:5
解释:你只需要对 [6, 4, 8, 10, 9]进行升序排序,那么整个表都会变为升序排序。

示例 2

输入:nums = [1,2,3,4]
输出:0

示例 3

输入:nums = [1]
输出:0

提示

  • 1 <= nums.length <= 104
  • -105 <= nums[i] <= 105

进阶:你可以设计一个时间复杂度为 O(n) 的解决方案吗?

2 解题(Java)

解题思路

从左向右遍历,只要碰到比已经遍历过路径内的最大值要小的元素,说明该元素需要被纳入到重排序的子数组中,最终得到右边界点;再从右向左遍历,只要碰到比已经遍历过的路径内的最小值还要大的元素,说明该元素需要被纳入到重排序的子数组中,最终得到左边界点。

class Solution {public int findUnsortedSubarray(int[] nums) {// max为遍历过程中最大值的下标,right为目标边界的右边界int right = 0, max = 0;for (int i=1; i<nums.length; i++) {if (nums[i] >= nums[max])  max = i;else right = i;}// min为遍历过程中最小值的下标,left为目标边界的左边界int left = nums.length - 1, min = nums.length - 1;for (int i=nums.length-2; i>=0; i--) {if (nums[i] <= nums[min]) min = i;else left = i;}return right <= left ? 0 : right - left + 1;}
}

3 复杂性分析

  • 时间复杂度O(n)
  • 空间复杂度O(1)

分割等和子集

1 题目描述

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。

示例 2

输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

提示

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 100

2 解题(Java)

2.1 解题思路:动态规划

1 状态定义

dp[i][j]表示从数组的[0,i]区间内挑选一些正整数,每个数只能使用一次,使得这些数的和恰好等于j。

2 初始化

if (nums[0] > target) return false;
else dp[0][nums[0]] = true;

3 状态转移方程

for (int i = 1; i < len; i++) {for (int j = 1; j <= target; j++) {// 1 不用nums[i]dp[i][j] |= dp[i - 1][j];// 2 用nums[i],前提是j >= nums[i]if (j >= nums[i]) {dp[i][j] |= (j == nums[i]) ? true : dp[i - 1][j - nums[i]];}}
}

2.2 Java代码

class Solution {public boolean canPartition(int[] nums) {int sum = 0;for (int num : nums) sum += num;if (sum % 2 == 1) return false;int target = sum / 2;// dp[i][j]表示从数组[0, i]区间内挑选一些正整数,每个数最多使用一次,使得这些数的和恰好等于j,是否成立boolean[][] dp = new boolean[nums.length][target+1];// 初始化判断if (nums[0] > target) return false;dp[0][nums[0]] = true;for (int i=1; i<nums.length; i++) {for (int j=1; j<=target; j++) {// 1 不用nums[i]dp[i][j] |= dp[i-1][j];// 2 用nums[i],前提是j >= nums[i]if(j >= nums[i]) {dp[i][j] |= j == nums[i] ? true : dp[i-1][j-nums[i]];}}}return dp[nums.length-1][target];}
}

3 复杂性分析

  • 时间复杂度:O(NC)。其中N 是数组元素的个数,C 是数组元素和的一半;
  • 空间复杂度:O(NC);

比特位计数

1 题目描述

给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。

示例 1:

输入: 2
输出: [0,1,1]

示例 2:

输入: 5
输出: [0,1,1,2,1,2]

进阶:

  • 给出时间复杂度为O(n*sizeof(integer))的解答非常容易。但你可以在线性时间O(n)内用一趟扫描做到吗?
  • 要求算法的空间复杂度为O(n)。
  • 你能进一步完善解法吗?要求在C++或任何其他语言中不使用任何内置函数(如 C++ 中的__builtin_popcount)来执行此操作。

2 解题(Java)

class Solution {public int[] countBits(int n) {int[] res = new int[n+1];for (int i=1; i<=n; i++) {if (i % 2 == 0) res[i] = res[i / 2];else res[i] = res[i - 1] + 1; }return res;}
}

3 复杂性分析

  • 时间复杂度:O(n);
  • 空间复杂度:O(n);

打家劫舍 III

1 题目描述

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

示例 1:

输入: [3,2,3,null,3,null,1]3/ \2   3\   \ 3   1输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.

示例 2:

输入: [3,4,5,1,3,null,1]3/ \4   5/ \   \ 1   3   1输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.

2 解题(Java)

动态规划+后序遍历

打劫节点A分两种情况:

  1. 打劫A:node.val + 不打劫A左节点的A左节点 + 不打劫A右节点的A右节点;
  2. 不打劫A:Math.max(打劫A左节点,不打劫A左节点)+ Math.max(打劫A右节点,不打劫A右节点);
/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode() {}*     TreeNode(int val) { this.val = val; }*     TreeNode(int val, TreeNode left, TreeNode right) {*         this.val = val;*         this.left = left;*         this.right = right;*     }* }*/
class Solution {public int rob(TreeNode root) {int[] rootStatus = dfs(root);return Math.max(rootStatus[0], rootStatus[1]);}public int[] dfs(TreeNode node) {if (node == null) {return new int[]{0, 0};}int[] l = dfs(node.left);int[] r = dfs(node.right);int selected = node.val + l[1] + r[1];int notSelected = Math.max(l[0], l[1]) + Math.max(r[0], r[1]);return new int[]{selected, notSelected};}
}

3 复杂性分析

  • 时间复杂度:O(n);
  • 空间复杂度:O(n);

零钱兑换

1 题目描述

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1

示例 2:

输入:coins = [2], amount = 3
输出:-1

示例 3:

输入:coins = [1], amount = 0
输出:0

示例 4:

输入:coins = [1], amount = 1
输出:1

示例 5:

输入:coins = [1], amount = 2
输出:2

提示:

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 231 - 1
  • 0 <= amount <= 104

2 解题(Java)

动态规划:

class Solution {public int coinChange(int[] coins, int amount) {int[] dp = new int[amount+1];Arrays.fill(dp, Integer.MAX_VALUE);dp[0] = 0;for (int i=1; i<=amount; i++) {for (int j=0; j<coins.length; j++) {if (i - coins[j] >= 0 && dp[i-coins[j]] != Integer.MAX_VALUE) dp[i] = Math.min(dp[i-coins[j]] + 1, dp[i]);}}return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];}
}

3 复杂性分析

  • 时间复杂度:O(Sn),其中 S 是金额,n 是硬币数;
  • 空间复杂度:O(S);

戳气球

1 题目描述

有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。

现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。

求所能获得硬币的最大数量。

示例 1

输入:nums = [3,1,5,8]
输出:167
解释: nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> [] coins = 315 + 358 + 138 + 181 = 167

示例 2

输入:nums = [1,5]
输出:10

提示

  • n == nums.length
  • 1 <= n <= 500
  • 0 <= nums[i] <= 100

2 解题(Java)

动态规划

class Solution {public int maxCoins(int[] nums) {if(nums == null || nums.length == 0) {return 0;}int n = nums.length;//处理边界:创建一个n+2的数组,开头和末尾都填1int[] arr = new int[n+2];Arrays.fill(arr, 1);for(int i=0; i<n; i++){arr[i+1] = nums[i];}int[][] dp = new int[n+2][n+2];//dp[i][j]为(i,j)开区间内最大值for(int j=2; j<=n+1; j++) {for(int i=j-2; i>=0; i--) {for(int k=i+1; k<j; k++) {//戳破第k个气球dp[i][j] = Math.max(dp[i][j], arr[i] * arr[k] * arr[j] + dp[i][k] + dp[k][j]);}}}return dp[0][n+1];}
}

3 复杂性分析

  • 时间复杂度:O(n3),其中状态数为n2,状态转移复杂度为O(n),总的时间复杂度为O(n2 * n);
  • 空间复杂度:O(n2),状态数为n2

最佳买卖股票时机含冷冻期

1 题目描述

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  • 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
  • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

示例:

输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

2 解题(Java)

动态规划:

class Solution {public int maxProfit(int[] prices) {if (prices.length == 0) {return 0;}int n = prices.length;// dp[i][0]: 手上持有股票的累计最大收益// dp[i][1]: 手上不持有股票,并且进入冷冻期中的累计最大收益// dp[i][2]: 手上不持有股票,并且不在冷冻期中的累计最大收益int[][] dp = new int[n][3];dp[0][0] = -prices[0];for (int i = 1; i < n; i++) {dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2] - prices[i]);dp[i][1] = dp[i - 1][0] + prices[i];dp[i][2] = Math.max(dp[i - 1][1], dp[i - 1][2]);}return Math.max(dp[n - 1][1], dp[n - 1][2]);}
}

3 复杂性分析

  • 时间复杂度:O(n);
  • 空间复杂度:O(n);

最长递增子序列

1 题目描述

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为4。

示例 2

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3

输入:nums = [7,7,7,7,7,7,7]
输出:1

提示

  • 1 <= nums.length <= 2500
  • -104 <= nums[i] <= 104

进阶

  • 你可以设计时间复杂度为 O(n2) 的解决方案吗?
  • 你能将算法的时间复杂度降低到 O(n log(n)) 吗?

2 解题(Java)

解题思路:动态规划+二分查找

  1. 新建数组 cell,用于保存最长上升子序列;

  2. 对原序列进行遍历,将每位元素二分插入 cell 中:

    • 如果 cell 中元素都比它小,将它插到最后;
    • 否则,用它覆盖掉大于等于它的元素中最小的那个;
  3. cell 未必是真实的最长上升子序列,但长度是对的;

代码

class Solution {public int lengthOfLIS(int[] nums) {List<Integer> cell = new ArrayList<>();cell.add(nums[0]);for (int i=1; i<nums.length; i++) {if (nums[i] > cell.get(cell.size()-1)) {cell.add(nums[i]);} else {int left = 0, right = cell.size() - 1;while (left < right) {int mid = (left + right) / 2;if (cell.get(mid) < nums[i]) left = mid + 1;else right = mid;}cell.remove(left);cell.add(left, nums[i]);}}return cell.size();}
}

3 复杂性分析

  • 时间复杂度:O(nlog(n)) ;
  • 空间复杂度:O(n);

完全平方数

1 题目描述

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

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

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

示例 1

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

示例 2

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

提示

  • 1 <= n <= 104

2 解题(Java)

动态规划

class Solution {public int numSquares(int n) {int[] dp = new int[n + 1];Arrays.fill(dp, Integer.MAX_VALUE);dp[0] = 0; for (int i = 1; i <= n; i++) {for (int j = 1; j * j <= i; j++) {dp[i] = Math.min(dp[i], dp[i - j * j] + 1);}}return dp[n];}
}

3 复杂性分析

  • 时间复杂度:O(n n \sqrt{n} n ​);
  • 空间复杂度:O(n);

最大正方形

1 题目描述

在一个由 ‘0’ 和 ‘1’ 组成的二维矩阵内,找到只包含 ‘1’ 的最大正方形,并返回其面积。

示例 1

输入:matrix =
[[“1”,“0”,“1”,“0”,“0”],[“1”,“0”,“1”,“1”,“1”],[“1”,“1”,“1”,“1”,“1”],[“1”,“0”,“0”,“1”,“0”]]
输出:4

示例 2

输入:matrix = [[“0”,“1”],[“1”,“0”]]
输出:1

示例 3

输入:matrix = [[“0”]]
输出:0

提示

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= m, n <= 300
  • matrix[i][j] 为 ‘0’ 或 ‘1’

2 解题(Java)

动态规划

class Solution {public int maximalSquare(char[][] matrix) {int maxSide = 0;if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return maxSide;}int rows = matrix.length, columns = matrix[0].length;int[][] dp = new int[rows][columns];for (int i=0; i<rows; i++) {for (int j=0; j<columns; j++) {if (matrix[i][j] == '1') {if (i == 0 || j == 0) dp[i][j] = 1;else dp[i][j] = Math.min(Math.min(dp[i-1][j], dp[i][j-1]), dp[i-1][j-1]) + 1;}maxSide = Math.max(maxSide, dp[i][j]);}}int maxSquare = maxSide * maxSide;return maxSquare;}
}

3 复杂性分析

  • 时间复杂度O(mn):其中 m 和 n 是矩阵的行数和列数。需要遍历原始矩阵中的每个元素计算 dp 的值;
  • 空间复杂度O(mn):其中 m 和 n 是矩阵的行数和列数。创建了一个和原始矩阵大小相同的矩阵 dp;

打家劫舍

1 题目描述

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。

示例 1

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

示例 2

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。

提示

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 400

2 解题(Java)

class Solution {public int rob(int[] nums) {int prev_prev = 0;int prev = 0;// 每次循环,计算“偷到当前房子为止的最大金额”for (int i : nums) {// 循环开始时,prev表示 dp[k-1],prev_prev表示 dp[k-2]// dp[k] = max{dp[k-1], dp[k-2] + i}int temp = Math.max(prev, prev_prev + i);prev_prev = prev;prev = temp;// 循环结束时,prev 表示 dp[k],prev_prev 表示 dp[k-1]}return prev;}
}

3 复杂性分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

区域和检索 - 数组不可变

1 题目描述

给定一个整数数组 nums,求出数组从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点。

实现 NumArray 类:

  • NumArray(int[] nums) 使用数组 nums 初始化对象;
  • int sumRange(int i, int j) 返回数组 nums 从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点(也就是 sum(nums[i], nums[i + 1], … , nums[j]));

示例

输入:
[“NumArray”, “sumRange”, “sumRange”, “sumRange”]
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
输出:
[null, 1, -1, -3]

解释:
NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3)
numArray.sumRange(2, 5); // return -1 (3 + (-5) + 2 + (-1))
numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1))

提示:

  • 0 <= nums.length <= 10 ^ 4
  • -10 ^ 5 <= nums[i] <= 10 ^ 5
  • 0 <= i <= j < nums.length
  • 最多调用 10 ^ 4 次 sumRange 方法

2 解题(Java)

前缀和

class NumArray {int[] preSum;public NumArray(int[] nums) {int n = nums.length;preSum = new int[n + 1];for (int i = 0; i < n; i++) {preSum[i + 1] = preSum[i] + nums[i];}}public int sumRange(int left, int right) {return preSum[right + 1] - preSum[left];}
}

3 复杂性分析

  • 时间复杂度:初始化 O(n),每次检索 O(1);
  • 空间复杂度:O(n);

乘积最大子数组

1 题目描述

给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

示例 1:

输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

示例 2:

输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

2 解题(Java)

class Solution {public int maxProduct(int[] nums) {int maxF = nums[0], minF = nums[0], ans = nums[0];for (int i = 1; i < nums.length; i++) {int mx = maxF, mn = minF;maxF = Math.max(mx * nums[i], Math.max(nums[i], mn * nums[i]));minF = Math.min(mn * nums[i], Math.min(nums[i], mx * nums[i]));ans = Math.max(maxF, ans);}return ans;}
}

3 复杂性分析

  • 时间复杂度O(N)
  • 空间复杂度O(1)

排序链表

1 题目描述

给你链表的头结点 head ,请将其按 升序 排列并返回排序后的链表 。

进阶

  • 你可以在 O(nlogn) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

示例 1

输入:head = [4,2,1,3]
输出:[1,2,3,4]

示例 2

输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

示例 3

输入:head = []
输出:[]

提示

  • 链表中节点的数目在范围 [0, 5 * 10 ^ 4] 内
  • -10 ^ 5 <= Node.val <= 10 ^ 5

2 解题(Java)

题目解析

  1. 最适合链表的排序算法是归并排序。如果采用自顶向下的递归实现,则空间复杂度为O(log n),如果要达到O(1)的空间复杂度,则需要使用自底向上的实现方式;
  2. 首先求得链表的长度length,然后将链表拆分成子链表进行合并;
  3. 用subLength表示每次需要排序的子链表的长度,初始subLength=1;
  4. 每次将链表拆分成若干个长度为subLength的子链表(最后一个子链表的长度可以小于subLength),按照每两个子链表一组进行合并,合并后即可得到若干个长度为subLength * 2的有序子链表(最后一个子链表的长度可以小于subLength * 2);
  5. 将subLength的值加倍,重复4,对更长的有序子链表进行合并操作,直到有序子链表的长度大于或等于length,整个链表排序完毕;

代码

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode() {}*     ListNode(int val) { this.val = val; }*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode sortList(ListNode head) {int length = 0;ListNode node = head;while (node != null) {node = node.next;length++;}ListNode dummyHead = new ListNode(0, head);for (int subLength = 1; subLength < length; subLength *= 2) {ListNode prev = dummyHead, curr = dummyHead.next;while (curr != null) {ListNode head1 = curr;for (int i = 1; i < subLength && curr.next != null; i++) {curr = curr.next;}ListNode head2 = curr.next;curr.next = null;curr = head2;for (int i = 1; i < subLength && curr != null && curr.next != null; i++) {curr = curr.next;}ListNode next = null;if (curr != null) {next = curr.next;curr.next = null;}ListNode merged = merge(head1, head2);prev.next = merged;while (prev.next != null) {prev = prev.next;}curr = next;}}return dummyHead.next;}public ListNode merge(ListNode head1, ListNode head2) {ListNode dummyHead = new ListNode();ListNode temp = dummyHead;while (head1 != null && head2 != null) {if (head1.val <= head2.val) {temp.next = head1;head1 = head1.next;} else {temp.next = head2;head2 = head2.next;}temp = temp.next;}temp.next = head1 == null ? head2 : head1;return dummyHead.next;}
}

3 复杂性分析

  • 时间复杂度O(nlogn):其中 n 是链表的长度;
  • 空间复杂度O(1)

单词拆分

1 题目描述

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明

  • 拆分时可以重复使用字典中的单词。
  • 你可以假设字典中没有重复的单词。

示例 1

输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。

示例 2

输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true
解释: 返回 true 因为 “applepenapple” 可以被拆分成 “apple pen apple”。 注意你可以重复使用字典中的单词。

示例 3

输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: false

2 解题(Java)

解题思路

  1. 初始化dp=[False,⋯,False],长度为 n+1。n 为字符串长度。dp[i] 表示 s 的前 i 位是否可以用 wordDict 中的单词表示;
  2. 初始化 dp[0]=True,假定空字符串可以被表示;
  3. 遍历字符串的所有子串,遍历开始索引 i,遍历区间 [0,n):
    • 遍历结束索引 j,遍历区间 [i+1,n+1):若 dp[i]=True 且 s[i,⋯,j) 在 wordlist 中:dp[j]=True。解释:dp[i]=True 说明 s 的前 i 位可以用 wordDict 表示,且 s[i,⋯,j) 出现在 wordDict中,说明 s 的前 j 位可以表示;
  4. 返回dp[n];

代码

public class Solution {public boolean wordBreak(String s, List<String> wordDict) {Set<String> wordDictSet = new HashSet<>(wordDict);// dp[i]表示s的前i个位是否可以用wordDict中的单词表示boolean[] dp = new boolean[s.length() + 1];// 设定边界条件,假定空字符串可以被表示dp[0] = true;// 遍历字符串的所有子串,遍历开始索引i,遍历区间[0,n)for (int i = 0; i < s.length(); i++) {// 遍历结束索引j,遍历区间[i+1,n+1)for (int j = i+1; j <= s.length(); j++) {if (dp[i] && wordDictSet.contains(s.substring(i, j))) {dp[j] = true;}}}return dp[s.length()];}
}

3 复杂性分析

  • 时间复杂度O(n ^ 2);
  • 空间复杂度O(n);

数字序列中某一位的数字

1 题目描述

数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。

请写一个函数,求任意第n位对应的数字。

示例 1:

输入:n = 3
输出:3

示例 2:

输入:n = 11
输出:0

限制:

0 <= n < 2 ^ 31

2 解题Java

2.1 解题思路

2.1.1 定义变量

  1. 将101112⋯ 中的每一位称为数位,记为n;
  2. 将10,11,12,⋯ 称为数字,记为num;
  3. 数字10是一个两位数,称此数字的位数为2,记为digit;
  4. 每digit位数的起始数字(即:1,10,100,⋯),记为 start;(为方便处理,暂忽略0)
  5. 各digit下的数位数量count = 9×start×digit;

根据以上分析,可将求解分为3步:

  1. 确定n所在数字的位数,记为digit;
  2. 确定n所在的数字,记为num;
  3. 确定n是num中的哪一数位,并返回结果;

2.1.2 确定n所在数字的位数digit

  1. 循环执行 n 减去 一位数、两位数、… 的数位数量 count ,直至n ≤ count 时跳出;
  2. 由于 n 已经减去了一位数、两位数、…、(digit−1) 位数的数位数量count,因而此时的 n 是从起始数字 start 开始计数的;
digit, start, count = 1, 1, 9
while n > count:n -= countstart *= 10 # 1, 10, 100, ...digit += 1  # 1,  2,  3, ...count = 9 * start * digit # 9, 180, 2700, ...

2.1.3 确定n所在的数字num

num = start + (n - 1) / digit

2.1.4 确定n是num中的哪一数位

res = Long.toString(num).charAt((n - 1) % digit) - '0';

2.2 代码

class Solution {public int findNthDigit(int n) {int digit = 1;long start = 1;long count = 9;while (n > count) { n -= count;digit += 1;start *= 10;count = digit * start * 9;}long num = start + (n - 1) / digit; return String.valueOf(num).charAt((n - 1) % digit) - '0'; }
}

3 复杂性分析

  • 时间复杂度 O(log N):第1步最多循环 O(log N) 次,第3步中将 num 转化为字符串使用 O(log N) 时间,因此总体为 O(log N);
  • 空间复杂度 O(log N) : 将数字 num 转化为字符串占用 O(log N) 的额外空间;

不用加减乘除做加法

1 题目描述

写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。

示例:

输入: a = 1, b = 1
输出: 2

提示:

a, b 均可能是负数或 0
结果不会溢出 32 位整数

2 解题(Java)

2.1 解题思路

  1. 设两数字的二进制形式 a, b,其求和 s = a + b,a(i)代表 a 的二进制第 i 位,则分为以下四种情况:
a(i) b(i) 无进位和n(i) 进位c(i+1)
0 0 0 0
0 1 1 0
1 0 1 0
1 1 0 1
  1. 观察发现,无进位和异或运算规律相同,进位与运算规律相同(并需左移一位)。因此,无进位和n与进位c的计算公式如下:

    1. n = a ^ b(无进位和,异或运算)
    2. c = a&b<<1(进位,与运算 + 左移一位)
  2. (和s)=(无进位和n) + (进位c)。即可将s = a + b转化为:s = n + c;
  3. 循环求n和c,直至进位c=0;此时s = n,返回n即可;


:若数字a和b中有负数,则变成了减法,如何处理?

:在计算机系统中,数值一律用补码来表示和存储。补码的优势:加法、减法可以统一处理(CPU只有加法器)。因此,以上方法同时适用于正数和负数的加法。

2.2 代码

class Solution {public int add(int a, int b) {while(b != 0) { // 当进位为 0 时跳出int c = (a & b) << 1;  // c = 进位a ^= b; // a = 非进位和b = c;}return a;}
}

3 复杂性分析

  • 时间复杂度 O(1): 最差情况下(例如 a= 0x7fffffff , b=1 时),需循环 32 次,使用 O(1) 时间;每轮中的常数次位操作使用 O(1) 时间;
  • 空间复杂度 O(1) : 辅助变量c使用常数大小的额外空间;

二进制中1的个数

1 题目描述

请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。

示例 1

输入:00000000000000000000000000001011
输出:3

示例 2

输入:11111111111111111111111111111101
输出:31

2 解题(Java)

方法:逐位判断

根据 与运算 定义,设二进制数字 n ,则有:

  • 若 n&1=0 ,则 n 二进制 最右一位 为 0 ;
  • 若 n&1=1 ,则 n 二进制 最右一位 为 1 。

算法流程

  1. 初始化数量统计变量 res = 0。
  2. 循环逐位判断: 当 n = 0 时跳出。
  3. res += n & 1 : 若 n&1=1 ,则统计数 res 加1。
  4. n >>>= 1 : 将二进制数字 n 无符号右移一位( Java 中无符号右移为 “>>>” ) 。
  5. 返回统计数量 res。

代码

public class Solution {public int hammingWeight(int n) {int res = 0;while(n != 0) {res += n & 1;n >>>= 1;}return res;}
}

3 复杂性分析

  • 时间复杂度 O(log2n): 此算法循环内部仅有移位、与、加等基本运算,占用 O(1) ;逐位判断需循环log2 n 次,其中log2 n 代表数字n最高位1的所在位数(例如 log 2 4=2, log2 16=4);
  • 空间复杂度 O(1) : 变量 res 使用常数大小额外空间;

不同的二叉搜索树

1 题目描述

给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?

示例:

输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

2 解题(Java)

动态规划:

class Solution {public int numTrees(int n) {int[] dp = new int[n+1];//dp[n]代表节点数为n所能构成的二叉搜索树的数目dp[0] = 1; dp[1] = 1;for (int i=2; i<=n; i++) {for (int j=1; j<=i; j++) {dp[i] += dp[j-1] * dp[i-j]; // 以j作为根节点}}return dp[n];}
}

3 复杂性分析

  • 时间复杂度O(n ^ 2):n 为二叉搜索树的节点个数,共 n 个值需要求解,每个值求解平均需要 O(n) 时间复杂度,因此总时间复杂度为 O(n ^ 2);
  • 空间复杂度O(n):需要 O(n) 空间存储 dp 数组;

编辑距离

1 题目描述

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

示例 1

输入:word1 = “horse”, word2 = “ros”
输出:3
解释:
horse -> rorse (将 ‘h’ 替换为’r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)

示例 2

输入:word1 = “intention”, word2 = “execution”
输出:5
解释:
intention ->inention (删除 ‘t’)
inention -> enention (将 ‘i’ 替换为 ‘e’)
enention ->exention (将 ‘n’ 替换为 ‘x’)
exention -> exection (将 ‘n’ 替换为 ‘c’)
exection -> execution (插入 ‘u’)

提示

  • 0 <= word1.length, word2.length <= 500
  • word1 和 word2 由小写英文字母组成

2 解题(Java)

解题思路

  1. dp[i][j] 代表 word1 前i个字符转换成 word2 前j个字符需要的最少步数;
  2. 当 word1[i] == word2[j],dp[i][j] = dp[i-1][j-1];
  3. 当 word1[i] != word2[j],dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1(其中,dp[i-1][j-1] 表示替换操作,dp[i-1][j] 表示删除操作,dp[i][j-1] 表示插入操作(插入j抵消j,于是j-1));
  4. 针对第一行,第一列要单独考虑;
  5. 第一行,是 word1 为空变成 word2 最少步数,即插入操作;
  6. 第一列,是 word2 为空,需要的最少步数,即删除操作;

代码

class Solution {public int minDistance(String word1, String word2) {int m = word1.length();int n = word2.length();int[][] dp = new int[m+1][n+1];// 计算边界值第一行for (int i = 1; i <= n; i++) dp[0][i] = dp[0][i - 1] + 1;// 计算边界值第一列for (int i = 1; i <= m; i++) dp[i][0] = dp[i - 1][0] + 1;// 计算其他位置for (int i = 1; i <= m; i++) {for (int j = 1; j <= n; j++) {if (word1.charAt(i - 1) == word2.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1];else dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]), dp[i - 1][j]) + 1;}}return dp[m][n];}
}

3 复杂性分析

  • 时间复杂度O(mn) :其中 m 为 word1 长度,n 为 word2 长度,双层for循环时间复杂度O(mn);
  • 空间复杂度O(mn) :需要大小为 O(mn)数组来记录状态值;

爬楼梯

1 题目描述

假设你正在爬楼梯,需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1

输入: 2 输出: 2 解释: 有两种方法可以爬到楼顶。

  1. 1 阶 + 1 阶
  2. 2 阶

示例 2

输入: 3 输出: 3 解释: 有三种方法可以爬到楼顶。

  1. 1 阶 + 1 阶 + 1 阶
  2. 1 阶 + 2 阶
  3. 2 阶 + 1 阶

2 解题(Java)

动态规划:

f(x)=f(x−1)+f(x−2)

class Solution {public int climbStairs(int n) {int pre = 1, cur = 1;for (int i=2; i<=n; i++) {int temp = pre + cur;pre = cur;cur = temp;}return cur;}
}

3 复杂性分析

  • 时间复杂度O(n):循环执行 O(n) 次,每次常数时间;
  • 空间复杂度O(1):几个变量占用常数大小的额外空间;

最小路径和

1 题目描述

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例 1

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。

示例 2

输入:grid = [[1,2,3],[4,5,6]]
输出:12

提示

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 200
  • 0 <= grid[i][j] <= 100

2 解题(Java)

动态规划:

class Solution {public int minPathSum(int[][] grid) {for(int i=0; i<grid.length; i++) {for (int j=0; j<grid[0].length; j++) {if (i == 0 && j ==0) continue;else if (i == 0) grid[i][j] = grid[i][j-1] + grid[i][j];else if (j == 0) grid[i][j] = grid[i-1][j] + grid[i][j];else grid[i][j] = Math.min(grid[i-1][j], grid[i][j-1]) + grid[i][j];}}return grid[grid.length-1][grid[0].length-1];}
}

3 复杂性分析

  • 时间复杂度O(mn):其中 m 和 n 分别是网格的行数和列数,需要对整个网格遍历一次;
  • 空间复杂度O(1):原地修改;

跳跃游戏

1 题目描述

给定一个非负整数数组 nums ,你最初位于数组的第一个下标。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标。

示例 1

输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

示例 2

输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。

提示

  • 1 <= nums.length <= 3 * 10 ^ 4
  • 0 <= nums[i] <= 10 ^ 5

2 解题(Java)

动态规划

  1. 依次遍历数组中的每一个位置,并实时维护 最远可以到达的位置rightmost,rightmost代表当前位置下可以到达的最远位置;
  2. 对于当前遍历到的位置 x,如果它在 rightmost 的范围内,可通过Math.max(rightmost, i + nums[i]) 更新 rightmost(动规思想);如果不在rightmost的范围内,那么x之后的位置都不可达,可提前返回false;
  3. 在遍历的过程中,如果 rightmost 大于等于数组的最后一个位置,那就说明最后一个位置可达,可提前返回 true ;
class Solution {public boolean canJump(int[] nums) {int rightmost = 0;for (int i = 0; i < nums.length; i++) {if (i <= rightmost) {rightmost = Math.max(rightmost, i + nums[i]);if (rightmost >= nums.length - 1) {return true;}}else return false;}return false;}
}

3 复杂性分析

  • 时间复杂度O(n):其中 n 为数组长度,最多遍历一遍数组即可求得答案;
  • 空间复杂度O(1):只需常数空间存放若干变量;

最大子序和

1 题目描述

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例 1

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2

输入:nums = [1]
输出:1

示例 3

输入:nums = [0]
输出:0

示例 4

输入:nums = [-1]
输出:-1

示例 5

输入:nums = [-100000]
输出:-100000

提示

  • 1 <= nums.length <= 3 * 10 ^ 4
  • -10 ^ 5 <= nums[i] <= 10 ^ 5

2 解题(Java)

动态规划

  1. 我们用 f(i) 代表以第 i 个数结尾的「连续子数组的最大和」,可以给出一个时间复杂度 O(n)、空间复杂度 O(n) 的实现,即用一个 f 数组来保存 f(i) 的值,用一个循环求出所有 f(i),f(n-1)即为最终解;
  2. 考虑到 f(i)只和 f(i-1) 相关,于是我们可以只用一个变量 pre 来维护对于当前 f(i) 来说的 f(i−1)的值,从而让空间复杂度降低到 O(1),这有点类似「滚动数组」的思想;
class Solution {public int maxSubArray(int[] nums) {int pre = 0, maxAns = Integer.MIN_VALUE;for (int x : nums) {pre = Math.max(pre + x, x);maxAns = Math.max(maxAns, pre);}return maxAns;}
}

3 复杂性分析

  • 时间复杂度O(n):其中 n 为 nums 数组的长度,只需遍历一遍数组即可求得答案;
  • 空间复杂度O(1):只需常数空间存放若干变量;

最长有效括号

1 题目描述

给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

示例 1

输入:s = “(()”
输出:2
解释:最长有效括号子串是 “()”

示例 2

输入:s = “)()())”
输出:4
解释:最长有效括号子串是 “()()”

示例 3

输入:s = “”
输出:0

提示

0 <= s.length <= 3 * 104
s[i] 为 ‘(’ 或 ‘)’

2 解题(Java)

动态规划:定义dp[i] 表示以下标 i 字符结尾的最长有效括号的长度.

class Solution {public int longestValidParentheses(String s) {int len = s.length();int[] dp = new int[len];int max = 0;for (int i = 1; i < len; i++) {if (s.charAt(i) == ')') {if (s.charAt(i-1) == '(') {dp[i] = i < 2 ? 2 : dp[i-2] + 2;} else if (i - dp[i-1] - 1 >= 0 && s.charAt(i - dp[i -1] - 1) == '(') {dp[i] = 2 + dp[i - 1] + ((i - dp[i - 1] - 2) >= 0 ? dp[i - dp[i - 1] - 2] : 0);}max = Math.max(max, dp[i]);}}return max;}
}

3 复杂性分析

  • 时间复杂度O(N):只需遍历整个字符串一次,即可将 dp 数组求出来;
  • 空间复杂度O(N):需要一个大小为 n 的 dp 数组;

礼物的最大价值

1 题目描述

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

示例 1:

提示:

  • 0 < grid.length <= 200
  • 0 < grid[0].length <= 200

2 解题(Java)

动态规划:

class Solution {public int maxValue(int[][] grid) {int m = grid.length, n = grid[0].length;// 动态规划填表for (int i=0; i<m; i++) {for (int j=0; j<n; j++) {// 左上角跳过循环if (i == 0 && j == 0) continue;// 第一行填表规则else if (i == 0) grid[i][j] += grid[i][j-1];// 第一列填表规则else if (j == 0) grid[i][j] += grid[i-1][j];// 其他情况的填表规则else grid[i][j] += Math.max(grid[i][j-1], grid[i-1][j]);}}// 返回右下角的结果return grid[m-1][n-1];}
}

3 复杂性分析

  • 时间复杂度 O(MN) : M,N 分别为矩阵行高、列宽;动态规划需遍历整个 grid 矩阵,使用 O(MN) 时间;
  • 空间复杂度 O(1) : 原地修改使用常数大小的额外空间;

最长公共子串(牛客网)

1 题目描述

给定两个字符串str1和str2,输出两个字符串的最长公共子串。
题目保证str1和str2的最长公共子串存在且唯一。

示例1

输入

“1AB2345CD”,“12345EF”

返回值

“2345”

备注:

1 <= len(str1),len(str2) <= 5000

2 解题(Java)

import java.util.*;
class Solution {/*** longest common substring* @param str1 string字符串 the string* @param str2 string字符串 the string* @return string字符串*/public String LCS(String str1, String str2) {int m = str1.length();int n = str2.length();// dp[i][j] str1前i个字符和str2前j个字符(子字符串以str1[i-1]和str2[j-1]结尾)的最长公共子串长度int[][] dp = new int[m+1][n+1];int maxLen = 0, end = 0;//开始填表for(int i = 1; i <= m; i++) {for(int j = 1; j <= n; j++) {if(str1.charAt(i-1) == str2.charAt(j-1)) dp[i][j] = dp[i-1][j-1] + 1;else dp[i][j] = 0;if(dp[i][j] > maxLen) {maxLen = dp[i][j];end = i - 1;}}}return str1.substring(end-maxLen+1, end+1);}
}

3 复杂性分析

  • 时间复杂度O(MN):M、N分别为str1和str2的长度,双层for循环遍历使用O(MN)的时间复杂度;
  • 空间复杂度O(MN):需建立一个M*N大小的数组;

股票的最大利润

1 题目描述

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

限制:

0 <= 数组长度 <= 10^5

2 解题(Java)

动态规划:

class Solution {public int maxProfit(int[] prices) {int cost = Integer.MAX_VALUE, profit = 0;for (int price : prices) {cost = Math.min(cost, price);profit = Math.max(profit, price - cost);}return profit;}
}

3 复杂性分析

  • 时间复杂度 O(N) : 其中 N 为数组prices的长度,动态规划需遍历prices ;
  • 空间复杂度 O(1) : 变量cost和profit占用常数大小的空间;

构建乘积数组

1 题目描述

给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B 中的元素 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

示例:

输入: [1,2,3,4,5]
输出: [120,60,40,30,24]

提示:

  • 所有元素乘积之和不会溢出 32 位整数
  • a.length <= 100000

2 解题(Java)

  1. 定义数组b,b[0] = 1;
  2. 计算b[i]正三角各元素的乘积;
  3. 定义赋值变量tmp = 1,计算b[i]倒三角各元素的乘积;
  4. 返回b;
class Solution {public int[] constructArr(int[] a) {if(a.length == 0) return new int[0];int[] b = new int[a.length];//正三角b[0] = 1;for(int i = 1; i < a.length; i++) {b[i] = b[i - 1] * a[i - 1];}//倒三角int tmp = 1;for(int i = a.length - 2; i >= 0; i--) {tmp *= a[i + 1];b[i] *= tmp;}return b;}
}

3 复杂性分析

  • 时间复杂度 O(N): 其中 N 为数组长度,两轮遍历数组 a ,使用 O(N) 时间;
  • 空间复杂度 O(1) : 变量 tmp 使用常数大小额外空间(数组 b 作为返回值,不计入复杂度考虑);

把数字翻译成字符串

1 题目描述

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

示例 1:

输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", “bwfi”, “bczi”, “mcfi"和"mzi”

提示:

0 <= num < 2^31

2 解题(Java)

动态规划:

class Solution {public int translateNum(int num) {int cur_res = 1, pre_res = 1, pre_digit = num % 10;while(num != 0) {num /= 10;int cur_digit = num % 10;int judge = 10 * cur_digit + pre_digit;int res = (judge >= 10 && judge <= 25) ? cur_res + pre_res : cur_res;pre_res = cur_res;cur_res = res;pre_digit = cur_digit;}return cur_res;}
}

3 复杂性分析

  • 时间复杂度 O(lgN) : 即数字 num 的位数,决定了循环次数;
  • 空间复杂度 O(1) : 几个变量占用常数大小的额外空间;

最长不含重复字符的子字符串

1 题目描述

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

示例 1:

输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

示例 2:

输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。

示例 3:

输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

提示:

s.length <= 40000

2 解题(Java)

class Solution {public int lengthOfLongestSubstring(String s) {Map<Character, Integer> dic = new HashMap<>();int res = 0, tmp = 0;for(int right = 0; right < s.length(); right++) {int left = dic.getOrDefault(s.charAt(right), -1); // 获取索引leftdic.put(s.charAt(right), right); // 更新哈希表tmp = tmp < right - left ? tmp + 1 : right - left; // dp[right-1] -> dp[right]res = Math.max(res,tmp); }return res;}
}

3 复杂性分析

  • 时间复杂度 O(N) : 其中 N 为字符串长度,动态规划需遍历计算字符串各字符;
  • 空间复杂度 O(1) : 字符的 ASCII 码范围为 0 ~ 127 ,哈希表 dic 最多使用 O(128)=O(1)大小的额外空间;

接雨水

1 题目描述

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]

输出:6

解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

2 解题(Java)

public int trap(int[] height) {if (height == null || height.length == 0) {return 0;}int ans = 0;int size = height.length;int[] left_max = new int[size];int[] right_max = new int[size];left_max[0] = height[0];for (int i = 1; i < size; i++) {left_max[i] = Math.max(height[i], left_max[i - 1]);}right_max[size - 1] = height[size - 1];for (int i = size - 2; i >= 0; i--) {right_max[i] = Math.max(height[i], right_max[i + 1]);}for (int i = 1; i < size - 1; i++) {ans += Math.min(left_max[i], right_max[i]) - height[i];}return ans;
}

3 复杂性分析

  • 时间复杂度O(N):存储最大高度数组,需要两次遍历,每次 O(n) ;最终使用存储的数据更新ans,O(n);
  • 空间复杂度O(N) :使用了额外的 O(n)空间用来放置 left_max 和 right_max 数组;

斐波那契数列

1 题目描述

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:

F(0) = 0, F(1) = 1

F(N) = F(N - 1) + F(N - 2), 其中 N > 1.

斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:1

示例 2:

输入:n = 5
输出:5

2 解题(Java)

class Solution {public int fib(int n) {if(n == 0) return 0;if(n == 1) return 1;int pre = 0, cur = 1;for (int i = 2; i <= n; i++) {int temp = (pre + cur) % 1000000007;pre = cur;cur = temp;}return cur;}
}

3 复杂性分析

  • 时间复杂度O(N):需循环n次,每轮循环内计算操作使用 O(1)。
  • 空间复杂度O(1):几个标志变量占用常数大小的额外空间。

青蛙跳台阶问题

1 题目描述

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:2

示例 2:

输入:n = 7
输出:21

示例 3:

输入:n = 0
输出:1

2 解题(Java)

解题思路无限接近斐波那契数列,唯一不同是初始值不一样。

当n >= 2时:

n级有两类跳法:n-1级跳1级,以及n-2级跳2级;

因此n级跳法数 = n-1级跳法数 + n-2级跳法数。

class Solution {public int numWays(int n) {if (n==0 || n==1) return 1;int pre = 1, cur = 1;for (int i = 2; i <= n; i++) {int temp = (pre + cur) % 1000000007;pre = cur;cur = temp;}return cur;}
}

3 复杂性分析

  • 时间复杂度O(N):需循环n次,每轮循环内计算操作使用 O(1);
  • 空间复杂度O(1): 几个变量占用常数大小的额外空间;

剪绳子

1 题目描述

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

示例 1

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1

示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

提示:

2 <= n <= 58

2 解题(动态规划)

2.1 解题思路

  1. 当n >= 2时,可以拆分成至少两个正整数;
  2. 设k是拆分出的第一个正整数,剩下的部分是n-k,n-k可以拆分,也可以不拆分;
  3. 创建数组dp,dp[i]表示将正整数i拆分成至少两个正整数后,这些正整数的最大乘积;
  4. i >= 2,假设对i拆分出的第一个正整数是j(1<=j<i),有以下两种方案:
    1. i - j不再拆分,乘积为j * (i - j);
    2. i - j继续拆分,乘积为j * dp[i - j];
  5. 因此当j固定时,有dp[i] = max(j * (i - j),j * dp[i - j])。由于j的范围是[1, i-1],因此需遍历所有的j才能得到dp[i]的最大值,故而得到状态转移方程如下:
  6. 最终得到dp[n]的值即为n拆分成至少两个正整数的和之后,这些正整数的最大乘积;

2.2 代码

class Solution {public int cuttingRope(int n) {int[] dp = new int[n+1];for (int i=2; i<=n; i++) {int curMax = 0;for (int j=1; j<i; j++) {curMax = Math.max(curMax, Math.max(j * (i - j), j * dp[i - j]));}dp[i] = curMax;}return dp[n];}
}

2.3 复杂性分析

  • 时间复杂度O(N ^ 2):双层for循环使用O(N ^ 2)时间;
  • 空间复杂度O(N):数组dp占用O(N)空间;

3 解题(数学)

3.1 解题思路

  1. 数学推论一:将绳子以相等的长度等分,得到的乘积最大;
  2. 数学推论二:尽可能将绳子以长度3等分时,乘积最大;
  3. 切分规则:当把绳子尽可能切分成多个长度为3的片段时,可能剩余一段长度为1或2的情况:如果最后一段为2,保留,不再拆分为1+1;如果最后一段为1,把一份3+1替换为2+2,因为2 * 2 > 3 * 1;

算法流程

  1. 当n <= 3时,按照规则应不切分,但由于题目要求至少剪成两段,因此剪出一段长度为1的绳子,返回n-1;
  2. 当n > 3时,求n除以3的整数部分a和余数部分b,共有三种情况:
    1. b=0,直接返回3 ^ a;
    2. b=1,将一个1+3转换为2+2,因此返回3 a-1 * 4;
    3. b=2,返回3 a * 2;

3.2 代码

class Solution {public int cuttingRope(int n) {if(n <= 3) return n - 1;int a = n / 3, b = n % 3;if(b == 0) return (int)Math.pow(3, a);if(b == 1) return (int)Math.pow(3, a - 1) * 4;return (int)Math.pow(3, a) * 2;}
}

3.3 复杂性分析

  • 时间复杂度O(1):仅有求整、求余、次方运算;
  • 空间复杂度O(1):a和b占用常数大小额外空间;

统计字典序元音字符串的数目

1 题目描述

给你一个整数 n,请返回长度为 n 、仅由元音 (a, e, i, o, u) 组成且按 字典序排列 的字符串数量。

字符串 s 按 字典序排列 需要满足:对于所有有效的 i,s[i] 在字母表中的位置总是与 s[i+1] 相同或在 s[i+1] 之前。

示例 1:

输入:n = 1
输出:5
解释:仅由元音组成的 5 个字典序字符串为 [“a”,“e”,“i”,“o”,“u”]

示例 2:

输入:n = 2
输出:15
解释:仅由元音组成的 15 个字典序字符串为[“aa”,“ae”,“ai”,“ao”,“au”,“ee”,“ei”,“eo”,“eu”,“ii”,“io”,“iu”,“oo”,“ou”,“uu”]
注意,“ea” 不是符合题意的字符串,因为 ‘e’ 在字母表中的位置比 ‘a’ 靠后

示例 3:

输入:n = 33
输出:66045

提示:

1 <= n <= 50

2 解题(Java)

动态规划:定义dp[n+1][5]数组,其中dp[i][0-4]表示长度为i的以a-u结尾的字符串的个数。

class Solution {public int countVowelStrings(int n) {int[][] dp = new int[n+1][5];//初始化n=1的情况for (int i = 0; i < 5; i++){dp[1][i] = 1;}for (int i = 2; i <= n; i++){dp[i][0] = dp[i-1][0];dp[i][1] = dp[i-1][0] + dp[i-1][1];dp[i][2] = dp[i-1][0] + dp[i-1][1] + dp[i-1][2];dp[i][3] = dp[i-1][0] + dp[i-1][1] + dp[i-1][2] + dp[i-1][3];dp[i][4] = dp[i-1][0] + dp[i-1][1] + dp[i-1][2] + dp[i-1][3] + dp[i-1][4];}//最终答案求和return dp[n][0] + dp[n][1] + dp[n][2] + dp[n][3] + dp[n][4];}
}

3 复杂性分析

  • 时间复杂度O(N):从1遍历到n;
  • 空间复杂度O(N):需要借助一个int[n+1][5]的二维数组;

获取生成数组中的最大值

1 题目描述

给你一个整数 n 。按下述规则生成一个长度为 n + 1 的数组 nums :

  • nums[0] = 0
  • nums[1] = 1
  • 当 2 <= 2 * i <= n 时,nums[2 * i] = nums[i]
  • 当 2<= 2 * i + 1 <= n 时,nums[2 * i + 1] = nums[i] + nums[i + 1]

返回生成数组 nums 中的 最大值。

示例

输入:n = 7
输出:3
解释:根据规则: nums[0] = 0 nums[1] = 1
nums[(1 * 2) = 2] = nums[1] = 1
nums[(1 * 2) + 1 = 3] = nums[1] + nums[2] = 1 + 1 = 2
nums[(2 * 2) = 4] = nums[2] = 1
nums[(2 * 2) + 1 = 5] = nums[2] + nums[3] = 1 + 2 = 3
nums[(3 * 2) = 6] = nums[3] = 2
nums[(3 * 2) + 1 = 7] = nums[3] + nums[4] = 2 + 1 = 3
因此,nums = [0,1,1,2,1,3,2,3],最大值 3

2 解题(Java)

class Solution {public int getMaximumGenerated(int n) {if(n == 0) return 0;if(n == 1) return 1;int[] nums = new int[n+1];nums[0] = 0;nums[1] = 1;int ans = 1;for(int i = 2; i < n+1; i++){if(i % 2 == 0) {nums[i] = nums[i/2];} else {nums[i] = nums[i/2] + nums[i/2 + 1];}ans = Math.max(ans, nums[i]);}return ans;}
}

3 复杂性分析

  • 时间复杂度O(N):顺序遍历到N;
  • 空间复杂度O(N):数组nums占用O(N)空间;

丑数

1 题目描述

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

示例:

输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。

说明:

  • 1 是丑数。
  • n 不超过1690。

2 解题(Java)

动态规划:

设已知长度为n的丑数序列x1,x2,…xn,求第n+1个丑数xn+1。根据递推性质,丑数xn+1只可能是以下三种情况之一(索引a,b,c为未知数):


由于xn+1是最接近xn的丑数,因此索引a,b,c需满足以下条件:

因此,可设置指针 a,b,c 指向首个丑数,循环根据递推公式得到下个丑数,并每轮将对应指针执行 +1 即可。

class Solution {public int nthUglyNumber(int n) {int pre_index2 = 0, pre_index3 = 0, pre_index5 = 0;int[] dp = new int[n];dp[0] = 1;for (int i=1; i<n; i++) {int res_2 = dp[pre_index2] * 2, res_3 = dp[pre_index3] * 3, res_5 = dp[pre_index5] * 5;dp[i] = Math.min(Math.min(res_2, res_3), res_5);if (dp[i] == res_2) pre_index2++;if (dp[i] == res_3) pre_index3++;if (dp[i] == res_5) pre_index5++;}return dp[n-1];}
}

3 复杂性分析

  • 时间复杂度 O(N) : 其中 N=n ,动态规划需遍历计算 dp 数组;
  • 空间复杂度 O(N) : 长度为 N 的 dp 列表使用 O(N) 的额外空间;

n个骰子的点数

1 题目描述

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。

你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。

示例 1:

输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]

示例 2:

输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]

限制:

1 <= n <= 11

2 解题(Java)

class Solution {public double[] dicesProbability(int n) {//根据动态规划的思想分解子问题//把n个骰子的点数分解为n-1个骰子的点数加上一个骰子的点数double[] pre = {1/6d, 1/6d, 1/6d, 1/6d, 1/6d, 1/6d};for(int i = 2; i <= n; i++) {double[] temp = new double[5*i+1];for (int j = 0; j < pre.length; j++) {for(int x = 0; x < 6; x++) {//构造状态转移方程:tmp[x+y]+=pre[x]*num[y],num[y]在本题恒等于1/6temp[j+x] += pre[j] / 6;}}pre = temp;}return pre;}
}

3 复杂性分析

  • 时间复杂度O(N^2):外层for循环O(N),中层for循环O(N),内层for循环O(1),总的时间复杂度O(N ^ 2);
  • 空间复杂度O(N):辅助数组占用O(N)空间;

最长回文子串

1 题目描述

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad" 输出: "bab" 注意: "aba" 也是一个有效答案。

示例 2:

输入: "cbbd"
输出: "bb"

"回文串”是一个正读和反读都一样的字符串

2 解题(Java)

2.1 动态规划法

1 定义状态

dp[i][j] 表示子串 s[i…j] 是否为回文子串,这里子串 s[i…j] 定义为左闭右闭区间,可以取到 s[i] 和 s[j];

2 状态转移方程

dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1];

3 边界条件

表达式 [i + 1, j - 1] 不构成区间,即长度严格小于 2,即 j - 1 - (i + 1) + 1 < 2 ,整理得 j - i < 3;

4 输出

只要得到 dp[i][j] = true且长度大于之前得到的最大长度,就记录子串的长度和起始位置,没有必要截取,这是因为截取字符串也要消耗性能,记录此时的回文子串的「起始位置」和「回文长度」即可;

public class Solution {public String longestPalindrome(String s) {int n = s.length();int maxLen = 1;int begin = 0;// dp[i][j]表示s[i..j]是否是回文串,左闭右闭boolean[][] dp = new boolean[n][n];for (int j=1; j<n; j++) {for (int i=0; i<j; i++) {if (s.charAt(i) == s.charAt(j) && (j-i < 3 || dp[i+1][j-1])) {dp[i][j] = true;if (j-i+1 > maxLen) {maxLen = j - i + 1;begin = i;}}      }}return s.substring(begin, begin + maxLen);}
}

复杂性分析

时间复杂度为 O(N2),空间复杂度为 O(N2)。

2.2 中心扩展法

两种情况:

  1. 以1个点作为中心点向两端扩展;
  2. 以2个点作为中心点向两端扩展;
public class Solution {public String longestPalindrome(String s) {int n = s.length();int maxLen = 1;int begin = 0;for (int i = 0; i < n * 2 - 1; i++) {int left = i / 2;int right = (i + 1) / 2;while (left >= 0 && right < n && s.charAt(left) == s.charAt(right)) {if (right - left + 1 > maxLen) {maxLen = right - left + 1;begin = left;}left--;right++;}}return s.substring(begin, begin + maxLen);}
}

复杂性分析

时间复杂度为 O(N2),空间复杂度为 O(1)。

正则表达式匹配

1 题目描述

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。

‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖整个字符串s的,而不是部分字符串。

示例 1:

输入:s = "aa" p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。

示例 2:

输入:s = "aa" p = "a*"
输出:true
解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。

示例 3:

输入:s = "ab" p = ".*"
输出:true
解释:".*" 表示可匹配零个或多个('*')任意字符('.')。

示例 4:

输入:s = "aab" p = "c*a*b"
输出:true
解释:因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。

示例 5:

输入:s = "mississippi" p = "mis*is*p*."
输出:false

提示:

  • 0 <= s.length <= 20
  • 0 <= p.length <= 30
  • s 可能为空,且只包含从 a-z 的小写字母。
  • p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。
  • 保证每次出现字符 * 时,前面都匹配到有效的字符

2 解题(Java)

class Solution {public boolean isMatch(String s, String p) {int m = s.length(), n = p.length();boolean[][] dp = new boolean[m+1][n+1];// 空匹配空dp[0][0] = true;// 初始化首行for (int i=2; i<=n; i+=2) {if (p.charAt(i-1) == '*') dp[0][i] = dp[0][i-2]; // a*代表0个a}// 状态转移:一步步填充表中其他的空格,dp[i][j]表示s的前i个字符与p的前j个字符是否能够匹配for (int i=1; i<=m; i++) {for (int j=1; j<=n; j++) {if (s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) == '.') {// 题目保证每次出现字符*时,前面都匹配到有效的字符,因此不会出现j-2<0的越界情况dp[i][j] = dp[i-1][j-1];} else if (p.charAt(j-1) == '*') {if (p.charAt(j-2) == s.charAt(i-1) || p.charAt(j-2) == '.') {dp[i][j] |= dp[i][j-2]; // a*代表0个adp[i][j] |= dp[i][j-1]; // a*代表1个adp[i][j] |= dp[i-1][j-1];// a*代表2个adp[i][j] |= dp[i-1][j]; // a*代表大于2个a} else {dp[i][j] = dp[i][j-2]; // a*代表0个a}} }}return dp[m][n];}
}

3 复杂性分析

  • 时间复杂度O(MN):其中 M 和 N 分别是字符串 s 和 p 的长度。我们需要计算出所有的状态,并且每个状态在进行转移时的时间复杂度为 O(1);
  • 空间复杂度O(MN):即为存储所有状态使用的空间;

Leetcode_动态规划、迭代相关推荐

  1. leetcode_动态规划

    leetcode_动态规划 基础题目 509.斐波那契数 70.爬楼梯 62.不同路径 63.不同路径II 343.整数拆分 96. 不同的二叉搜索树 01背包 分割等和子集 1049.最后一块石头的 ...

  2. python 斐波拉契递归 尾递归 备忘录 动态规划 迭代

    1 递归 #coding=utf-8 '''@author: 182 ''' from time import time def fab(n):if n==0:return 0if n==1:retu ...

  3. c++矩阵连乘的动态规划算法并输出_算法面试必修课,动态规划基础题型归纳(三)

    动态规划(Dynamic Programming,简称DP),是大家都觉得比较难以掌握的算法.为了应付面试,我们经常会背诵一下DP问题的源码,其实,只要理解了思想,掌握基本的模型,然后再来点写代码的套 ...

  4. python实现迭代计算_带你读《强化学习:原理与Python实现》之三:有模型数值迭代-阿里云开发者社区...

    第3章 有模型数值迭代 在实际问题中,直接求解Bellman期望方程和Bellman最优方程往往有困难.其中的一大困难在于直接求解Bellman方程需要极多的计算资源.本章在假设动力系统完全已知的情况 ...

  5. 动态规划---例题1.矩阵连乘问题

    一.问题描述 矩阵A和B可乘的条件是矩阵A的列数等于矩阵B的行数.若A是一个p×q的矩阵,B是一个q×r的矩阵,则其乘积C=AB是一个p×r的矩阵.其标准计算公式为: 计算C=AB总共需要pqr次的数 ...

  6. 动态规划5:动态规划与记忆化搜索

    本期题目: 单词拆分 单词拆分II 大礼包 记忆化搜索与狭义的动态规划 前面我们讲的动态规划很广义的思想,从起始时刻到终止时刻,每一步都要按照最优原则走,按照这个原则会产生一个迭代式,称为动态规划迭代 ...

  7. 算法笔记:递归、动态规划

    目录 递归.分治和动态规划 德罗斯特效应 递归.分治和动态规划 迭代和递归的区别 分治 动态规划 解法 斐波那契数 解法1:暴力递归 解法2:记忆化存储 解法3:动态规划 解法4:自底向上迭代 动态规 ...

  8. 跳跃游戏—leetcode55

    给定一个非负整数数组,你最初位于数组的第一个位置. 数组中的每个元素代表你在该位置可以跳跃的最大长度. 判断你是否能够到达最后一个位置. 示例 1: 输入: [2,3,1,1,4] 输出: true ...

  9. 矩阵连乘问题的算法分析

           问题描述:给定n个矩阵:A1,A2,...,An,其中Ai与Ai+1是可乘的,i=1,2...,n-1.确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少.输入数 ...

最新文章

  1. VC下设置Excel单元格的边框
  2. 软件工程——HelloWorld
  3. 体验:Office SharePoint foundation 2010 Beta版安装使用
  4. HTTP、Asp.net管道与IIS
  5. win7+vs2015+pcl1.8.0配置
  6. sqlserver 当月、 时间_SQLServer取系统当前时间
  7. 中国中小管理咨询公司现状
  8. 全国计算机二级考试vb考点,计算机二级考试VB考点:通用对话框控件
  9. initializationerror错误的解决
  10. 移动设备软件开发测试
  11. 使用madVR或mpv软件转换HDR视频至非HDR设备播放
  12. 编码器-解码器架构-读书笔记
  13. 国开电大 管理会计 形考任务
  14. photoshop抠图后如何使边缘模糊圆滑
  15. Flash Play 闪玩
  16. c语言程序设计试题汇编第三版勘误,c语言程序设计基础教程----勘误记录.pdf
  17. c语言malloc函数程序,c语言 malloc函数详解
  18. 【2022.1.3】手脱压缩壳练习(含练习exe)
  19. 国内不错的破解软件网站!
  20. python的简单程序代码_怎么样都要学几个python的简单小程序

热门文章

  1. 详解GMT CST UTC DST PDT PST几个时间概念
  2. 中船嘉年华邮轮揭幕全新企业品牌标识;美国运通全球商务旅行完成对Expedia集团旗下易信达的收购 | 全球旅报...
  3. VBA:获取工作簿中所有表的名称、地址
  4. Python报错ModuleNotFoundError: No module named ‘pyqtgraph‘
  5. 怎么把腾讯视频的qlv文件转成mp3格式 【已解决】
  6. 图片上传_RuoYi
  7. 什么是域名?什么网站名?
  8. JAVA模拟win7记事本
  9. python学习之自动化运维(一):shell的使用
  10. Linux内核性能剖析的方法学和主要工具