动态规划
1143.最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
char c1 = text1.charAt(i - 1);
for (int j = 1; j <= n; j++) {
char c2 = text2.charAt(j - 1);
if (c1 == c2) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
}
剑指Offer42.连续子数组的最大和
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
- dp[i-1]<0,时,dp[i-1]+nums[i] < nums[i],故dp[i]=nums[i]
class Solution {
public int maxSubArray(int[] nums) {
int[] dp = new int[nums.length+1];
dp[0] =nums[0];
int ans =nums[0];
for(int i=1; i<nums.length;i++){
dp[i]= Math.max(dp[i-1]+nums[i],nums[i]);
ans =Math.max(dp[i],ans);
}
return ans;
}
}
class Solution {
public int maxSubArray(int[] nums) {
int res= nums[0];
for(int i=1;i<nums.length;i++){
nums[i] +=Math.max(nums[i-1],0);
res =Math.max(res,nums[i]);
}
return res;
}
}
剑指Offer10-I.斐波那契数列
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
数列0-n,共有n+1个数,所以初始化为
int[] dp = new int[n+1];
class Solution {
public int fib(int n) {
if(n==0) return 0;
int[] dp = new int[n+1];
dp[0]=0;
dp[1]=1;
for(int i=2; i<=n; i++){
dp[i]=dp[i-1]+dp[i-2];
dp[i] %=1000000007;
}
return dp[n];
}
}
//空间优化
class Solution {
public int fib(int n) {
int a = 0, b = 1, sum;
for(int i = 0; i < n; i++){
sum = (a + b) % 1000000007;
a = b;
b = sum;
}
return a;
}
}
丑数
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。1 是丑数。
class Solution {
public int nthUglyNumber(int n) {
int[] dp = new int[n];
dp[0]=1;
int a=0,b=0,c=0;
for(int i=1; i<n;i++){
int n2=dp[a]*2;
int n3=dp[b]*3;
int n5=dp[c]*5;
dp[i]=Math.min(Math.min(n2,n3),n5);
if(dp[i]==n2) a++;
if(dp[i]==n3) b++;
if(dp[i]==n5) c++;
}
return dp[n-1];
}
}
剑指Offer62.圆圈中最后剩下的数字
0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
输入: n = 5, m = 3
输出: 3
class Solution {
public int lastRemaining(int n, int m) {
int x = 0;
for (int i = 2; i <= n; i++) {
x = (x + m) % i;
}
return x;
}
}
class Solution {
public int lastRemaining(int n, int m) {
ListNode head = new ListNode(0);
ListNode tail =head;
for(int i=1;i<n;i++){
tail.next=new ListNode(i);
tail=tail.next;
}
tail.next = head;
ListNode pre = tail;
ListNode cur = head;
int count = 0;
ListNode temp = null;
while(cur.next != cur){
count++;
temp = cur.next;
if(count == m){
pre.next = temp;
count = 0;
}else{
pre= cur;
}
cur = temp;
}
return cur.val;
}
}
class ListNode{
int val;
ListNode next;
ListNode(){}
ListNode(int val){this.val=val;}
}
最佳答案
class Solution {
public int lastRemaining(int n, int m) {
List<Integer> list = new ArrayList<>(n);
for(int i = 0; i < n; i++){
list.add(i);
}
int index=0;
while(n>1){
index=(index+m-1) % n;
list.remove(index);
n--;
}
return list.get(0);
}
}
leetcode爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
本问题其实常规解法可以分成多个子问题,爬第n阶楼梯的方法数量,等于 2 部分之和
- 爬上 n-1阶楼梯的方法数量。因为再爬1阶就能到第n阶
- 爬上 n-2 阶楼梯的方法数量,因为再爬2阶就能到第n阶
所以我们得到公式 dp[n] = dp[n-1] + dp[n-2]
同时需要初始化 dp[1]=1 和 dp[2]=2
当n小于等于2时,返回n。
class Solution {
public int climbStairs(int n) {
if (n <= 2) {
return n;
}
int[] dp = new int[n + 1];
dp[1] =1;
dp[2] =2;
for(int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
class Solution {
public int climbStairs(int n) {
if(n<=2){
return n;
}
int a = 1,b = 2,c = 0;
for(int i = 3; i <= n; i++){
c=a+b;
a=b;
b=c;
}
return c;
}
}
121.买卖股票的最佳时机
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
状态定义:
dp[i][j]:下标为 i 这一天结束的时候,手上持股状态为 j 时,我们持有的现金数。
- j=0,表示当前不持有股票
- j=1,表示当前持股
推导状态转移方程:
dp[i][0]:今天不持股,分两种情况:
- 昨天不持股。今天什么都不做
- 昨天持股,今天卖出股票(利润增加)
dp[i][1]:规定今天持股,分两种情况:
- 昨天持股,今天什么都不做
- 昨天不持股,今天买入股票。(因为只允许交易一次,所以当前利润是 当天股价的相反数)
class Solution {
public int maxProfit(int[] prices) {
int len =prices.length;
if(len < 2){
return 0;
}
int[][] dp =new int[len][2];
// dp[i][0] 下标为 i 这天结束的时候,不持股,手上拥有的现金数
// dp[i][1] 下标为 i 这天结束的时候,持股,手上拥有的现金数
//初始化,不持股为0;持股则减去前一天的股价
dp[0][0] = 0;
dp[0][1] = -prices[0];
for(int i=1; i <len; i++){
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
dp[i][1]=Math.max(dp[i-1][1],-prices[i]);
}
return dp[len-1][0];
}
}
122.买卖股票的最佳时机II
给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
if(len<2){
return 0;
}
int[][] dp = new int[len][2];
dp[0][0] =0;
dp[0][1] =-prices[0];
for(int i=1; i<len; i++){
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);
}
return dp[len-1][0];
}
}
方法二:贪心算法
由于不限制交易次数,只要今天股价比昨天高,就交易。
这道题 「贪心」 的地方在于,对于 「今天的股价 - 昨天的股价」,得到的结果有 3 种可能:① 正数,② 0,③负数。贪心算法的决策是: 只加正数 。
class Solution {
public int maxProfit(int[] prices) {
int len =prices.length;
if(len < 2){
return 0;
}
int res= 0;
for(int i=1; i<len; i++){
int curres= prices[i]-prices[i-1];
if(curres>0){
res+=curres;
}
}
return res;
}
}
剑指Offer63.股票的最大利润
假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
class Solution {
public int maxProfit(int[] prices) {
int maxprofit=0;
int minprice=Integer.MAX_VALUE;
for(int i=0; i<prices.length; i++){
if(prices[i]<minprice){
minprice=prices[i];
}else if(prices[i]-minprice>maxprofit){
maxprofit=prices[i]-minprice;
}
}
return maxprofit;
}
}
53.最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
假设nums 数组的长度是 n,下标从 0 到 n-1。
1、定义dp[i]表示数组中前i+1(注意这里的i是从0开始的)个元素构成的连续子数组的最大和。
2、如果要计算前i+1个元素构成的连续子数组的最大和,也就是计算dp[i],我们可以考虑 nums[i] 单独成为一段还是加入dp[i-1]对应的那一段,这取决于nums[i]和dp[i-1]+nums[i]的大小
dp[i]=max{dp[i-1]+nums[i],nums[i]}
3、边界条件判断,当i等于0的时候,也就是前1个元素,他能构成的最大和也就是他自己,所以dp[0]=num[0];
class Solution {
public int maxSubArray(int[] nums) {
int len = nums.length;
int[] dp = new int[len];
//边界条件
dp[0] = nums[0];
int max = dp[0];
for(int i=1; i<len;i++){
dp[i]=Math.max(dp[i-1]+nums[i],nums[i]);
max=Math.max(dp[i],max);
}
return max;
}
}
//优化
class Solution {
public int maxSubArray(int[] nums) {
int pre = 0, maxAns = nums[0];
for (int x : nums) {
pre = Math.max(pre + x, x);
maxAns = Math.max(maxAns, pre);
}
return maxAns;
}
}
198.打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
1、如果只有一间房屋,则偷窃该房屋得到最高总金额
2、如果只有两间,只能偷窃其中一间,因此选择金额较高的进行偷窃。
3、如果房屋大于两间,对于第K间房屋有两个选项:
- 1、偷窃第k间,那么就不能偷窃第k-1间,偷窃总金额则为前k-2间房屋的最高总金额与第k间房屋的金额之和。(dp[i-2]+nums[i])
- 2、不偷窃第k间,偷窃总金额为前k-1间房屋的最高总金额 (dp[i-1]))
在这两个选项中,选择金额较大的选项,即为前k间房屋偷窃到的最高总金额
用dp[i]表示前i间房屋能偷窃到的最高总金额,那么就有如下的状态转移方程:
dp[i]=max(dp[i-2]+nums[i],dp[i-1])
边界条件为:
dp[0]=nums[0] 只有一间房屋
dp[1]=max(nums[0],nums[1]) 只有两间房屋,选较大
最终答案为: dp[n-1], n为数组长度
class Solution {
public int rob(int[] nums) {
int len =nums.length;
if(nums == null || len == 0){
return 0;
}
if(len == 1){
return nums[0];
}
int[] dp =new int[len];
dp[0]=nums[0];
dp[1]=Math.max(nums[0],nums[1]);
for(int i=2; i < len; i++){
//对于第K间房屋,选择偷第k间和不偷第k间两者的较大值
dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[len-1];
}
}
剑指Offer46.把数字翻译成字符串
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
class Solution {
public int translateNum(int num) {
String s = String.valueOf(num);
int[] dp = new int[s.length()+1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i <= s.length(); i ++){
String temp = s.substring(i-2, i);
if(temp.compareTo("10") >= 0 && temp.compareTo("25") <= 0)
dp[i] = dp[i-1] + dp[i-2];
else
dp[i] = dp[i-1];
}
return dp[s.length()];
}
}
//优化
class Solution {
public int translateNum(int num) {
String s = String.valueOf(num);
int a = 1, b = 1;
for(int i = 2; i <= s.length(); i++) {
String tmp = s.substring(i - 2, i);
int c = tmp.compareTo("10") >= 0 && tmp.compareTo("25") <= 0 ? a + b : a;
b = a;
a = c;
}
return a;
}
}
剑指Offer47.礼物的最大价值
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
设 f(i, j)为从棋盘左上角走至单元格 (i ,j) 的礼物最大累计价值,易得到以下递推关系:f(i,j)等于 f(i,j-1)和f(i−1,j) 中的较大值加上当前单元格礼物价值grid(i,j) 。
f(i,j)=max[f(i,j−1),f(i−1,j)]+grid(i,j)
class Solution {
public int getMaxValue1(int[][] arr) {
if(arr==null || arr.length==0){
return 0;
}
int rows = arr.length; //行
int cols = arr[0].length; //列
int[][] maxValue = new int[rows][cols];
for(int i=0;i<rows;i++) {
for(int j=0;j<cols;j++) {
//初始化边界
int left = 0;
int up = 0;
if(i>0){
up = maxValue[i-1][j];
}
if(j>0){
left = maxValue[i][j-1];
}
maxValue[i][j] = Math.max(up, left) + arr[i][j];
}
}
return maxValue[rows-1][cols-1];
}
}
优化边界
int[][] dp = new int[row + 1][column + 1];
- 增加一行一列,数组初始化值全为0;
- 最左边一列和最上边一列全为0值,表示边界;
- 循环从dp[1][1]开始,返回dp[row][column];
- dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i-1][j-1];
- dp[1][1] = Math.max(dp[0][1], dp[1][0]) + grid[0][0];=0+grid[0][0];
class Solution {
public int maxValue(int[][] grid) {
int row = grid.length;
int column = grid[0].length;
//dp[i][j]表示从grid[0][0]到grid[i - 1][j - 1]时的最大价值
//数组初始化值全为0
int[][] dp = new int[row + 1][column + 1];
for (int i = 1; i <= row; i++) {
for (int j = 1; j <= column; j++) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1];
}
}
return dp[row][column];
}
}