1. 买卖股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
解法:
public class Solution {
//动态规划 前i天的最大收益 = max{前i-1天的最大收益,第i天的价格-前i-1天中的最小价格}
public int maxProfit(int[] prices) {
if(prices==null || prices.length==0){
return 0;
}
int maxProfit=0;
//前i-1天的最小值
int preMin=prices[0];
for(int i=1;i<prices.length;i++){
preMin=Math.min(prices[i-1],preMin);
maxProfit=Math.max(prices[i]-preMin,maxProfit);
}
return maxProfit;
}
}
2. 买卖股票的最佳时机 II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
解法:
[7, 1, 5, 6] 第二天买入,第四天卖出,收益最大(6-1),所以一般人可能会想,怎么判断不是第三天就卖出了呢? 这里就把问题复杂化了,根据题目的意思,当天卖出以后,当天还可以买入,所以其实可以第三天卖出,第三天买入,第四天又卖出((5-1)+ (6-5) === 6 - 1)。所以算法可以直接简化为只要今天比昨天大,就卖出。
public class Solution {
//判断相邻是否递增,因为连续递增可以合起来看为一次买入卖出操作,所以统计所有递增量即可
public int maxProfit(int[] prices) {
int maxProfit=0;
for(int i=1;i<prices.length;i++){
if(prices[i]>prices[i-1]){
maxProfit+=prices[i]-prices[i-1];
}
}
return maxProfit;
}
}
3. 最长回文子串
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd”
输出: “bb”
解法一:动态规划
考虑 “ababa” 这个示例。如果我们已经知道 “bab” 是回文,那么很明显,“ababa” 一定是回文,因为它的左首字母和右尾字母是相同的。
public String longestPalindrome(String s) {
int length = s.length();
boolean[][] P = new boolean[length][length];
int maxLen = 0;
String maxPal = "";
for (int len = 1; len <= length; len++) //遍历所有的长度
for (int start = 0; start < length; start++) {
int end = start + len - 1;
if (end >= length) //下标已经越界,结束本次循环
break;
P[start][end] = (len == 1 || len == 2 || P[start + 1][end - 1]) && s.charAt(start) == s.charAt(end); //长度为 1 和 2 的单独判断下
if (P[start][end] && len > maxLen) {
maxPal = s.substring(start, end + 1);
}
}
return maxPal;
}
解法二:
我们知道回文串一定是对称的,所以我们可以每次循环选择一个中心,进行左右扩展,判断左右字符是否相等即可。
由于存在奇数的字符串和偶数的字符串,所以我们需要从一个字符开始扩展,或者从两个字符之间开始扩展,所以总共有 n+n-1 个中心。
public String longestPalindrome(String s) {
if (s == null || s.length() < 1) return "";
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = expandAroundCenter(s, i, i);
int len2 = expandAroundCenter(s, i, i + 1);
int len = Math.max(len1, len2);
if (len > end - start) {
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);
}
private int expandAroundCenter(String s, int left, int right) {
int L = left, R = right;
while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) {
L--;
R++;
}
return R - L - 1;
}
4. 计算字符串距离(最小编辑代价)
Levenshtein 距离,又称编辑距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。编辑距离的算法是首先由俄国科学家Levenshtein提出的,故又叫Levenshtein Distance。
Ex:
字符串A:abcdefg
字符串B: abcdef
通过增加或是删掉字符”g”的方式达到目的。这两种方案都需要一次操作。把这个操作所需要的次数定义为两个字符串的距离。
要求:给定任意两个字符串,写出一个算法计算它们的编辑距离。
示例:
输入:
abcdefg
abcdef
输出:
1
解法: 例题 7.6
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while(in.hasNext()) {
String str1 = in.nextLine();
String str2 = in.nextLine();
System.out.println(calStringDistance(str1,str2));
}
}
private static int calStringDistance(String str1,String str2){
int M = str1.length();
int N = str2.length();
//dp[i][j] 代表 str1[0...i-1] 编辑成 str2[0...j-1] 的最小代价
int[][] dp = new int[M+1][N+1];
//将 "" 编辑成 ""
dp[0][0] = 0;
//将 str1[0...i-1] 编辑成 "" 的代价
for(int i = 1;i < M+1;i++){
dp[i][0] = i;
}
//将 "" 编辑成 str2[0...j-1] 的代价
for(int j = 1;j < N+1;j++){
dp[0][j] = j;
}
for(int i = 1;i < M+1;i++){
for(int j = 1;j < N+1;j++){
//str1[0...i-1] 先编辑成str1[0...i-2],然后由str1[0...i-2]编辑成str2[0...j-1]
int c1 = 1 + dp[i-1][j];
//str1[0...i-1] 先编辑成str2[0...j-2],然后由str2[0...j-2]编辑成str2[0...j-1]
int c2 = dp[i][j-1] + 1;
//如果str1[i-1] != str2[j-1]。先把str1[0...i-1]中str1[0...i-2]的部分变为str2[0...j-2]
//然后把字符str1[i-1]替换成str2[j-1]
int c3 = dp[i-1][j-1] + 1;
//如果str1[i-1] == str2[j-1]。把str1[0...i-1]中str1[0...i-2]的部分变为str2[0...j-2]即可
int c4 = dp[i-1][j-1];
if(str1.charAt(i-1) != str2.charAt(j-1)){
dp[i][j] = Math.min(Math.min(c1,c2),c3);
}else {
dp[i][j] = Math.min(Math.min(c1,c2),c4);
}
}
}
return dp[M][N];
}
}
5. 最长公共子串
查找两个字符串a,b中的最长公共子串。若有多个,输出在较短串中最先出现的那个。
示例:
输入:
abcdefghijklmnop
abcsafjklmnopqrstuvw
输出:
jklmnop
解法:
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while(in.hasNext()) {
String str1 = in.nextLine();
String str2 = in.nextLine();
System.out.println(maxCommonStr(str1,str2));
}
}
private static String maxCommonStr(String str1,String str2){
//str1保存较短的串
if(str1.length()>str2.length()){
String temp = str1;
str1 = str2;
str2 = temp;
}
int M = str1.length();
int N = str2.length();
//dp[i][j] 表示 str1[0~i-1] 和 str2[0~j-1]的公共后缀
int[][] dp = new int[M+1][N+1];
int maxLen = 0,start = 0;
for(int i = 1;i < M+1;i++){
for(int j = 1;j < N+1;j++){
if(str1.charAt(i-1) == str2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1] + 1;
if(dp[i][j] > maxLen){
maxLen = dp[i][j];
//记录最长公共子串的起始位置
start = i-1 - maxLen + 1;
}
}else{
dp[i][j] = 0;
}
}
}
return str1.substring(start,start + maxLen);
}
}
6. 最长公共子序列
给定两个字符串str1和str2,输出两个字符串的最长公共子序列。如过最长公共子序列为空,则输出-1。
输入描述:
输入包括两行,第一行代表字符串str1,第二行代表str2。
输出描述:
输出一行,代表他们最长公共子序列。如果公共子序列的长度为空,则输出-1。
示例1:
输入:
1A2C3D4B56
B1D23CA45B6A
输出:
123456
说明:
"123456"和“12C4B6”都是最长公共子序列,任意输出一个。
解法:
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while(in.hasNext()) {
String str1 = in.nextLine();
String str2 = in.nextLine();
System.out.println(maxSubSequence(str1,str2).length() > 0 ? maxSubSequence(str1,str2):-1);
}
}
public static String maxSubSequence(String str1,String str2) {
int M = str1.length();
int N = str2.length();
//多添加一行一列便于初始化
int[][] dp = new int[M+1][N+1];//dp[i][j] 表示 str1[0~i-1] 与 str2[0~j-1] 的最长公共子序列的长度
int maxLen = 0;
String maxSubSequence = "";
for(int i = 1;i < M+1;i++){
for(int j = 1;j < N+1;j++){
if(str1.charAt(i-1) == str2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1] + 1;
}else {
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
}
if(dp[i][j] > maxLen){
maxLen = dp[i][j];
maxSubSequence += str1.charAt(i-1);
}
}
}
return maxSubSequence;
}
}
7. 通配符匹配
给定一个字符串 (s) 和一个字符模式 § ,实现一个支持 ‘?’ 和 ‘*’ 的通配符匹配。
‘?’ 可以匹配任何单个字符。
‘*’ 可以匹配任意字符串(包括空字符串)。
示例 1:
输入:
s = “aa”
p = “a”
输出: false
解释: “a” 无法匹配 “aa” 整个字符串
示例 2:
输入:
s = “aa”
p = ""
输出: true
解释: '’ 可以匹配任意字符串。
示例 3:
输入:
s = “cb”
p = “?a”
输出: false
解释: ‘?’ 可以匹配 ‘c’, 但第二个 ‘a’ 无法匹配 ‘b’。
解法:
dp[i][j] 表示 s[0~i-1] 和 p[0~j-1] 是否匹配
如果s[i-1]和p[j-1]相同,或者p[j-1]为 ‘?’,则 dp[i][j] = dp[i-1][j-1]
如果p[j-1]为 ‘*’:
- 若 p[j-1] 匹配空字符,则 dp[i][j] = dp[i][j-1]
- 若 p[j-1] 匹配 s[i-1],则 dp[i][j] = dp[i-1][j]
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while(in.hasNext()) {
String p = in.nextLine();
String s = in.nextLine();
System.out.println(isMatch(s,p));
}
}
public static boolean isMatch(String s, String p) {
int M = s.length();
int N = p.length();
//dp[i][j] 表示 s[0~i-1] 和 p[0~j-1] 是否匹配,添加两个空字符用于初始化
boolean[][] dp = new boolean[M+1][N+1];
dp[0][0] = true;
for(int j = 1;j < N+1;j++){
dp[0][j] = dp[0][j-1] && p.charAt(j-1) == '*';
}
for(int i = 1;i < M+1;i++){
for(int j = 1;j < N+1;j++){
if(s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) == '?'){
dp[i][j] = dp[i-1][j-1];
}else if(p.charAt(j-1) == '*'){
dp[i][j] = dp[i][j-1] || dp[i-1][j];
}
}
}
return dp[M][N];
}
}
8. 最长上升子序列
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
- 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
- 你算法的时间复杂度应该为 O(n2) 。
解法:
动画
public class Solution {
public int lengthOfLIS(int[] nums) {
if (nums.length == 0) {
return 0;
}
int[] dp = new int[nums.length];
dp[0] = 1;
int maxans = 1;
for (int i = 1; i < dp.length; i++) {
int maxval = 0;
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
maxval = Math.max(maxval, dp[j]);
}
}
dp[i] = maxval + 1;
maxans = Math.max(maxans, dp[i]);
}
return maxans;
}
}