剪绳子
给你一根长度为 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。
class Solution {
public int cuttingRope(int n) {
/*
dp五部曲:
1.状态定义:dp[i]为长度为i的绳子剪成m段最大乘积为dp[i]
2.状态转移:dp[i]有两种途径可以转移得到
2.1 由前一个dp[j]*(i-j)得到,即前面剪了>=2段,后面再剪一段,此时的乘积个数>=3个
2.2 前面单独成一段,后面剩下的单独成一段,乘积为j*(i-j),乘积个数为2
两种情况中取大的值作为dp[i]的值,同时应该遍历所有j,j∈[1,i-1],取最大值
3.初始化:初始化dp[1]=1即可
4.遍历顺序:显然为正序遍历
5.返回坐标:返回dp[n]
*/
// 定义dp数组
int[] dp = new int[n + 1];
// 初始化
dp[1] = 1; // 指长度为1的单独乘积为1
// 遍历[2,n]的每个状态
for(int i = 2; i <= n; i++) {
for(int j = 1; j <= i - 1; j++) {
// 求出两种转移情况(乘积个数为2和2以上)的最大值
int tmp = Math.max(dp[j] * (i - j), j * (i - j));
dp[i] = Math.max(tmp, dp[i]);
}
}
return dp[n];
}
}
买卖股票
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) return 0;
int min = prices[0];
int res = 0;
for (int i = 1; i < prices.length; i++) {
//计算最大差
res = Math.max(res, (prices[i]-min));
//记录最小值
min = Math.min(min, prices[i]);
}
return res;
}
}
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售
class Solution {
public int maxProfit(int[] prices) {
int[][] dp = new int[prices.length][2];
//下标为0时 不持有股票的收益为0
dp[0][0] = 0;
//下标为0时,持有股票的收益为-prices[0]
dp[0][1] = -prices[0];
for (int i = 1; i < prices.length; i++) {
//不持有股票的最大利润 = max(前一天不持有的最大利润,前一天持有+今天卖掉)
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);
//持有股票的最大利润 = max(前一天持有的最大利润,前一天不持有+今天买入)
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i]);
}
//最大利润一定是手中没有股票
return dp[prices.length-1][0];
}
}
硬币组合
给你一个整数数组 coins
,表示不同面额的硬币;以及一个整数 amount
,表示总金额。
每个硬币无限个, 求组合数。
class Solution {
public int change(int amount, int[] coins) {
//代表 总和为i的方法总数有dp[i]个
int[] dp = new int[amount+1];
dp[0] = 1;
for (int i = 0; i < coins.length; i++) {
for (int j = coins[i]; j <= amount; j++) {
//方法总数 = 每个硬币的方法数之和
dp[j] += dp[j - coins[i]];
}
}
return dp[amount];
}
}
最少硬币数
class Solution {
public int coinChange(int[] coins, int amount) {
//和为i的最少硬币数为dp[i]
int[] dp = new int[amount+1];
//因为递推公式是求min,因此初始化为最大值
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int i = 0; i < coins.length; i++) {
for (int j = coins[i]; j <= amount; j++) {
//因为+1会溢出 所以需要判断
if (dp[j-coins[i]] != Integer.MAX_VALUE)
dp[j] = Math.min(dp[j], dp[j-coins[i]]+1);
}
}
//如果是最大值说明没有组成成功,因此返回-1;
return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
}
}
最大子序和
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
解法: 因为要求最大和 所以用dp
1. dp[i] 表示前i个元素的最大连续子数组和 2. 进行边界定义 dp[0] = a[0], max = a[0]; 3. 最大和 = max(dp[i-1]+a[i], a[i]);
class Solution {
public int maxSubArray(int[] nums) {
int len = nums.length;
if (len == 0) return 0;
int[] dp = new int[len];
int max = nums[0];
dp[0] = nums[0];
for (int j = 1; j < len; j++) {
//max(前j-1的和不是负数,前j-1的和是负数) 负数直接丢弃
dp[j] = Math.max(dp[j-1] + nums[j], nums[j]);
max = Math.max(max, dp[j]);
}
return max;
}
}
求最值
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
1. 定义dp数组,到达i,j 位置的最小和为dp[i][j] 2. dp[0][0] 3 dp[0][i] dp[i][0] 4. 递推公式
public class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m][n];
dp[0][0] = grid[0][0];
for(int i = 1; i < n; i++) {
dp[0][i] = dp[0][i-1] + grid[0][i];
}
for (int i = 1; i < m; i++) {
dp[i][0] = dp[i-1][0] + grid[i][0];
}
for(int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
//最小值 = min(上边的最小值,左边的最小值) + 当前值
dp[i][j] = Math.min(dp[i][j-1], dp[i-1][j]) + grid[i][j];
}
}
return dp[m-1][n-1];
}
}
求总数
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if(i == 0 && j ==0 ) dp[i][j] = 1;
else if(i == 0 && j != 0) dp[i][j] = 1;
else if(i != 0 && j == 0) dp[i][j] = 1;
//总数 = 左边的总数+上边的总数
else dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
}
}
机器人走方格
有一个XxY的网格,一个机器人只能走格点且只能向右或向下走,要从左上角走到右下角。请设计一个算法,计算机器人有多少种走法。注意这次的网格中有些障碍点是不能走的。
给定一个int[][] map(C++ 中为vector >),表示网格图,若map[i][j]为1则说明该点不是障碍点,否则则为障碍。另外给定int x,int y,表示网格的大小。请返回机器人从(0,0)走到(x - 1,y - 1)的走法数,为了防止溢出,请将结果Mod 1000000007。保证x和y均小于等于50
int countWays(int[][] map, int x, int y) {
int[][] dp = new int[x][y];
for(int i = 0; i < x; i ++){
for(int j = 0; j < y; j ++){
//有障碍就跳过
if(map[i][j] != 1) continue;
if(i == 0 && j == 0) dp[0][0] = 1;
//当前(i,0)的值根据(i-1,0)的值判断
else if(i != 0 && j == 0) dp[i][0] = dp[i-1][0] ;
else if(i == 0 && j != 0) dp[0][j] = dp[0][j-1] ;
else{
dp[i][j] = (dp[i][j-1] + dp[i-1][j])%1000000007;
}
}
}
return dp[x-1][y-1];
}
求具体结果
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
if (candidates == null || candidates.length == 0) return res;
Stack<Integer> context = new Stack<>();
back(candidates, target, 0 ,context);
return res;
}
private void back(int[] candidates, int yushu, int s, Stack<Integer> context) {
if (yushu == 0) {
res.add(new ArrayList(context));
return;
}
for (int i = s; i < candidates.length && yushu >= candidates[i]; i++) {
context.push(candidates[i]);
//因为每个数字可以选择多次,因此传递i
back(candidates, (yushu - candidates[i]), i, context);
context.pop();
}
}
}
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次, 且有重复数据.
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
if (candidates == null || candidates.length == 0) return res;
//因为有重复数据 需要先排序
Arrays.sort(candidates);
back(candidates, target, 0, new Stack<Integer>());
return res;
}
private void back(int[] candidates, int yushu, int s, Stack<Integer> context){
if (yushu == 0){
res.add(new ArrayList(context));
return;
}
for (int i = s; i < candidates.length && yushu >= candidates[i]; i++){
//遇到重复数据 跳过
if (i > s && candidates[i] == candidates[i-1]) continue;
context.push(candidates[i]);
//i+1 是因为每个数字只能选择一次
back(candidates, (yushu-candidates[i]), i+1, context);
context.pop();
}
}
}
给定一个没有重复数字的序列,返回其所有可能的全排列。
class Solution1 {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
if (nums == null || nums.length == 0) return res;
int len = nums.length;
back(nums, new boolean[len], new Stack<Integer>());
return res;
}
private void back(int[] nums, boolean[] used, Stack<Integer> context) {
int len = nums.length;
//count == len 代表数字已经选择完了
if (context.size() == len){
res.add(new ArrayList(context));
return;
}
for (int i = 0; i < len; i++) {
//如果没有用过再进行使用
if (!used[i]) {
context.push(nums[i]);
used[i] = true;
back(nums, used, context);
context.pop();
used[i] = false;
}
}
}
}
给定一个可包含重复数字的序列,返回所有不重复的全排列。
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
if (nums == null || nums.length == 0) return res;
Arrays.sort(nums);
back(nums, 0, new boolean[nums.length], new Stack<Integer>());
return res;
}
private void back(int[] nums, int count, boolean[] used, Stack<Integer> context) {
int len = nums.length;
if (count == len) {
res.add(new ArrayList(context));
return;
}
for (int i = 0; i < len; i++) {
if (!used[i]) {
//i > 0 重复的数字不会出现在第一个
//当当前数和前一个数一样, 前一个数在本分支中还没有用过,则一定会出现和前一个分支相同的结果
if(i > 0 && nums[i] == nums[i-1] && !used[i-1])continue;
context.push(nums[i]);
used[i] = true;
back(nums, count+1, used,context);
context.pop();
used[i] = false;
}
}
}
}
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
if (nums.length == 0) return res;
back(nums, 0, new Stack<Integer>());
return res;
}
private void back(int[] nums, int s, Stack<Integer> context) {
//[], [1], [1,2], [1,2,3], [1,3], [2],[2,3],[3]
res.add(new ArrayList(context));
for (int i = s; i < nums.length; i++) {
context.push(nums[i]);
back(nums, i+1, context);
context.pop();
}
}
}
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
if (nums.length == 0) return res;
Arrays.sort(nums);
back(nums, 0, new Stack<Integer>());
return res;
}
private void back(int[] nums, int s, Stack<Integer> context) {
res.add(new ArrayList(context));
for (int i = s; i < nums.length; i++) {
if (i > s && nums[i] == nums[i-1]) continue;
context.push(nums[i]);
back(nums, i+1, context);
context.pop();
}
}
}
树形dp
给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?
class Solution {
public int numTrees(int n) {
int[] dp = new int[n+1];
dp[0] = 1;
if (n == 0) return 0;
// 总次数 = 每个数字为根的 (左子树的可能数 * 右子树的可能数) 之和
// dp[i] 表示 i 个数字组成二叉树的所有可能数
for (int i = 1; i <= n; i++){
// dp[j-1] : 以j为根左子树的组合数 dp[i-j]: 以j为根的右子树的组合数
for (int j = 1; j <= i; j++) {
dp[i] += dp[j-1] * dp[i-j];
}
}
return dp[n];
}
}
二叉树
给定一个非空二叉树,返回其最大路径和。
本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点
class Solution {
int max = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
help(root);
return max;
}
private int help(TreeNode root) {
if (root == null) return 0;
int left = Math.max(0, help(root.left));
int right = Math.max(0, help(root.right));
max = Math.max(max, root.val+left+right);
return root.val + Math.max(left,right);
}
}
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
思路: 4中情况 1. 本身就是公共祖先 2. 一个在左边一个在右边 3. 2个都在左边 4. 2个都在右边
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//情况1
if (root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
//情况2
if (left != null && right != null) return root;
//情况3, 4
return left != null ? left : right;
}
}
后继节点
后继节点:就是中序遍历排序中相对的后一个
思路: 左根右 1. 先判断右子树不为空, 则下一个是右子树的最左节点
2. 如果右子树为空, 如果当前节点是左节点 则下一个是当前节点的跟节点.
3. 当前节点是右节点, 则下一个是当前节点的根节点的根节点
4. 如果当前节点是右子树的最右节点, 则下一个节点是null
public TreeNode findNext(TreeNode node) {
if (node == null) return null;
TreeNode tmp = null;
if (node.right != null) {
tmp = tmp.right;
while (tmp.left != null) {
tmp = tmp.left;
}
return tmp;
}else {
tmp = node;
//tmp.parent.left != tmp 这里主要针对左子树最左侧节点
while (tmp.parent != null && tmp.parent.left != tmp) {
tmp = tmp.parent;
}
return tmp.parent;
}
}
前驱节点
1. 当前节点的左子树不为null, 则前一个是左子树的最右节点
2. 否则如果为null, 则是当前节点的根节点
3. 如果当前节点是左子树的最左节点,则前一个为null
public TreeNode findPre(TreeNode node) {
if (node == null) return null;
TreeNode tmp = null;
if (node.left != null) {
tmp = node.left;
while (tmp.right != null) {
tmp = tmp.right;
}
return tmp;
}else {
tmp = node;
//tmp.parent.right != tmp 主要针对右子树最右测节点
while (tmp.parent != null && tmp.parent.right != tmp) {
tmp = tmp.parent;
}
return tmp.parent;
}
}
判断是否是完全二叉树
1. 层次遍历
2. 如果右孩子不为null 左孩子为null 则返回false
3. 如果右孩子为null, 但是以后的左孩子或者右孩子有不为null 则返回false
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isCompleteTree(TreeNode node) {
if (node == null) return false;
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.add(node);
boolean flag = false;
while (!queue.isEmpty()) {
TreeNode cur = queue.poll();
//右孩子不为null 左孩子为null
//右孩子为null 以后的节点左孩子或右孩子必须为null
if ((cur.right != null && cur.left == null) || (flag && (cur.right != null || cur.left != null))) {
return false;
}
if (cur.left != null) {
queue.add(cur.left);
}
if (cur.right != null) {
queue.add(cur.right);
}else {
flag = true;
}
}
return true;
}
}
求二叉树从根节点到叶子节点的最大路径和对应的路径
1. 如果是叶子节点, 当前和 大于 最大和, 则更新最大和 及对应的路径
2. 否则将左节点递归, 回溯, 右节点递归回溯
int max = 0;
List<TreeNode> res = new ArrayList<TreeNode>();
public List<TreeNode> getMax(TreeNode node) {
if (node == null) return null;
help(node, 0, new Stack<TreeNode>());
return res;
}
private void help(TreeNode node, int sum, Stack<TreeNode> context) {
if (node.left == null && node.right == null) {
context.push(node);
sum += node.val;
if (sum > max) {
max = sum;
res = new ArrayList<TreeNode>(context);
}
return;
}
context.push(node);
help(node.left, sum+root.val, context);
context.pop();
help(node.right, sum+root.val, context);
context.pop();
}