目录
- 1、动态规划
- 2、位运算
- 3、哈希
- 4、技巧类
- 5、 双指针
- 6、链表
- 7、二叉树
-
- 7.1 二叉树的中序遍历 (94)
- 7.2 二叉树的最大深度(104)
- 7.3 反转二叉树(226)
- 7.4 对称二叉树(101)
- 7.5 二叉树的直径(543)
- 7.6 二叉树最大路径和 (124)
- 7.7 二叉树层序遍历(102)
- 7.8 将有序数组转换为二叉搜索树(108)
- 7.9 验证二叉搜索树 (98)
- 7.10 二叉搜索树中的第k小元素(230)
- 7.11 二叉树的右视图(199)
- 7.12 二叉树展开为链表(114)
- 7.13 前序和中序序列构造二叉树(105)
- 7.14 路径总和III (437)
- 7.15 和为k的子数组(560)以及拓展(523)
- 7.16 二叉树的最近公共祖先(236)
1、动态规划
1.1 爬楼梯 (70)
题目:

分析:f[i] 表示从第1层爬到第i层阶梯有多少种方法,i = 1…n。
状态转移方程:f[i] = f[i-1] + f[i-2]
理解:最后一步有两种可能,可能爬 1 阶,也可能爬 2阶,如果爬1阶,子问题为 f[i-1];如果爬2阶,子问题为 f[i-2]; 都有可能,所以加起来。
初始化: f[0] = 1, f[1] = 1;要凑 f[2] = 2,所以 f[0] = 1
class Solution {
public int climbStairs(int n) {
int[] ans = new int[n+1];
ans[0] = 1;
ans[1] = 1;
for(int i = 2;i<= n;i++)
{
ans[i] = ans[i-1] +ans[i-2];
}
return ans[n];
}
}
1.2 杨辉三角 (118)
题目:

注意:numRows>=1;
分析:
List<List<Integer>> 这是返回类型,选取数据结构很重要,要遍历输出,选取HashMap<Integer,<List<Integer>>> 作为存储结构。其中List<Integer> 选取ArrayList<>()作为存储结构。然后,注意到边缘位置全是1,然后非边缘位置 l[i] 是上一层 l[i-1] + l[i]。
代码:
class Solution {
public List<List<Integer>> generate(int numRows) {
Map<Integer,List<Integer>> hp = new HashMap<>();
List<List<Integer>> ans = new ArrayList<>();
for(int i = 1;i<=numRows;i++)
{
List<Integer> l = new ArrayList<>();
for(int j = 0;j<i;j++)// 第i行恰好有i个元素,开始赋值
{
if(j == 0 || j == i-1)
l.add(1);
else
{
// 拿到上一层的list
List<Integer> le = hp.get(i-1);
// 例如:l[2] = le[1]+le[2]
l.add(le.get(j-1)+le.get(j));
}
}
hp.put(i,l);// 只是为记忆上一层
ans.add(l);
}
return ans;
}
}
1.3 打家劫舍 (198)
题目:

分析:
非负,这是一个很关键信息,因为这样表示状态时可以用前 i 个元素的最大目标值作为状态;不难分析到这是一个选与不选问题,状态转移方程如下:
d p [ i ] = m a x ( d p [ i − 2 ] + n u m s [ i ] , d p [ i − 1 ] ) \mathrm{ dp[i] = max(dp[i-2]+nums[i],dp[i-1])} dp[i]=max(dp[i−2]+nums[i],dp[i−1])
其中 d p [ i ] \mathrm{dp[i]} dp[i] 表示的是下标为 0 - i 元素的最大目标值; d p [ 0 ] = n u m s [ 0 ] \mathrm{dp[0] = nums[0]} dp[0]=nums[0]。
代码:
class Solution {
public int rob(int[] nums) {
int[] dp = new int[nums.length];
dp[0] = nums[0];
if(nums.length>1)// 原因 从2开始,防止越界
dp[1] = Math.max(nums[0],nums[1]);
for(int i =2;i<nums.length;i++)
{
dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]);
}
return dp[nums.length-1];
}
}
1.4 完全平方数 (279)
题目:

分析:关键是如何确定一个数可以被平方数+数来表示,进而转换为子问题,状态转移方程有:
d p [ i ] = m i n ( d p [ i ] , d p [ i − j ∗ j ] + 1 ) \mathrm{dp[i]=min(dp[i],dp[i-j*j]+1)} dp[i]=min(dp[i],dp[i−j∗j]+1)
其中 d p [ i ] \mathrm{dp[i]} dp[i]表示的是数字 i 最少可以用几个平方数来表示, i − j ∗ j ≥ 0 \mathrm{i-j*j\ge0} i−j∗j≥0, dp[0] = 0。
代码:
class Solution {
public int numSquares(int n) {
// dp[0] = 0,是因为如果一个数据本身就是平方数 dp[i-j*j]=0, 即 dp[i-j*j]+1 = 1
int[] dp = new int[n+1];
dp[0] = 0;
for(int i = 1;i<=n;i++ )
{
dp[i] = i;//最大值,尽量避免使用Integer.MAX_VALUE
for(int j = 0;j*j<=i;j++)
{
dp[i] = Math.min(dp[i],dp[i-j*j]+1);
}
}
return dp[n];
}
}
1.5 零钱兑换 (322)
题目:
分析:
如何定义状态变量,转换成子问题,才是关键,要想计算 dp[amount] ,不难想到其子问题是 dp[amount-coin] ,当然,对于每一个硬币coin都要进行相减,所以状态转移方程有:
d p [ i ] = m i n ( d p [ i ] , d p [ i − c o i n ] + 1 ) \mathrm{dp[i] = min(dp[i],dp[i-coin]+1)} dp[i]=min(dp[i],dp[i−coin]+1)
其中 dp[i] 表示能组成金额i的最小硬币数量,dp[0] = 0.
代码:
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount+1];
dp[0] = 0;// 表示金额0肯定是0 个硬币数量
for(int i =1;i<=amount;i++)
{
dp[i] = amount+1;// 这是一个不可能的最大值
for(int coin: coins)
{
if(i>=coin)
{
dp[i] = Math.min(dp[i],dp[i-coin]+1);
}
}
}
if(dp[amount] == amount+1)
{
return -1;
}
else
{
return dp[amount];
}
}
}
1.6 单词划分 (139)
题目:
分析:
要转换为子问题,先定义好一个状态变量(父问题),再找子问题,dp[i] 表示的是 s[0] - s[i-1] 能否被字典划分 , i = 1,2,3…s.length(), 对于每一个状态定义都要严丝合缝; 寻找子问题, 对于字典中的每一个单词, 都要看 s[i-word.length()] - s[i-1] 是否满足是该单词, 而且dp[i-word.length()]为true, 也就是说 s[0] - s[i-word.length()-1] 可以被划分; 都满足, OK 令 dp[i] = true. 状态转移方程为:
d p [ i ] = i − w o r d . l e n g t h ( ) ≥ 0 ∧ d p [ i − w o r d . l e n g t h ( ) ] ∧ w o r d = = s u b ( s ) \mathrm{dp[i] = i-word.length()\ge0 \wedge dp[i-word.length()] \wedge word == sub(s) } dp[i]=i−word.length()≥0∧dp[i−word.length()]∧word==sub(s)
其中 dp[ 0] = true, 原因就是如果sub(s)本来就是从头开始, dp[0] = true 可以不影响我们对该字串的判断.
代码:
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
// 从 i=1 开始判断 dp[i] 表示 s[0]-s[i-1] 能否 被划分
// dp[0] = true;
// dp[1] 表示 s[0] 能否划分
// dp[i] = [dp[i-word.length] && s(i-word.length: i-1) == word] or (nextword)
int s_len = s.length();
boolean[] dp = new boolean[s_len+1];
dp[0] = true;
for(int i = 1;i<=s_len;i++)
{
for(String word : wordDict)
{
// l e e t c ode dp[4] 为例
// 0 1 2 3 4 dp
int leftIndex = i - word.length();// 当前可能匹配单词的起始左下标
if(leftIndex>=0 && dp[leftIndex] && word.equals(s.substring(leftIndex,i)))
{
dp[i] = true;
break;// 只要找到一个就可以
}
}
}
return dp[s_len];
}
}
1.7 最长递增子序列 (300)
题目:

分析:
定义状态变量dp[i] 表示 以 下标 i 为结尾的最长子序列长度, 子问题就是,如果当前 nums[i] 大于 nums[j] , j = 0…i-1 , dp[i] = max( dp[i] , dp[j] + 1)
代码:
class Solution {
public int lengthOfLIS(int[] nums) {
//i 0 1 2 3 4 5
//num 4,10,4,3,8,9
//dp 1 2 2 2 3 4
// 如果dp[i] 0-i元素的最大值子序列长度,我们会发现会出错,dp[4] = 3,因为8虽然大于3,dp[3] = 2,
// 但是dp[4]显然等于2,因为dp[3] = 2,不是以3结尾的最长长度,所以不能用dp[i]表示0-i元素的最大值子序列长度
// dp[i] 表示 以 i 为结尾的最长子序列长度
int[] dp = new int[nums.length];
dp[0] = 1;
int maxlen = 1;
for(int i = 1;i<nums.length;i++)
{
dp[i] = 1; // 如果nums[i] 无法和之前元素构成更长子序列,那么dp[i] = 1
for(int j = 0;j<i;j++)// 因为它可能和所有0-----i-1 构成更长子序列
{
if(nums[i]>nums[j])
{
dp[i] = Math.max(dp[i],dp[j]+1);
}
}
if(dp[i]>maxlen)
maxlen = dp[i];
}
return maxlen;
}
}
1.8 乘积最大子数组 (152)
题目:

分析:
最大从哪里来,这是化解为子问题的关键,当然,题目变为最小也一样。审题:非空连续。 maxdp[i] 代表以下标i为结尾的最大值,只和nums[i],maxdp[i-1],mindp[i-1] 有关。
例如: -2 1 -3 dp[2] = 6 要用到mindp[1] = -2,所以要记录最小也要记录下来。
状态转移方程为:
m a x d p [ i ] = m a x ( d [ i ] , m a x d p [ i − 1 ] ∗ n u m s [ i ] , m i n d p [ i − 1 ] ∗ n u m s [ i ] ) \mathrm{maxdp[i] = max(d[i],maxdp[i-1]*nums[i],mindp[i-1]*nums[i])} maxdp[i]=max(d[i],maxdp[i−1]∗nums[i],mindp[i−1]∗nums[i])
代码:
class Solution {
public int maxProduct(int[] nums) {
int n = nums.length;
int[] maxdp = new int[n];
int[] mindp = new int[n];
maxdp[0] = nums[0];
mindp[0] = nums[0];
int max = nums[0];
for<

最低0.47元/天 解锁文章
8万+





