【Day10】每天三算法
题目
NC91 最长上升子序列(三)
描述
给定数组 arr ,设长度为 n ,输出 arr 的最长上升子序列。(如果有多个答案,请输出其中 按数值(注:区别于按单个字符的ASCII码值)进行比较的 字典序最小的那个)
示例1
输入:
[2,1,5,3,6,4,8,9,7]
返回值:
[1,3,4,8,9]
示例2
输入:
[1,2,8,6,4]
返回值:
[1,2,4]
说明:
其最长递增子序列有3个,(1,2,8)、(1,2,6)、(1,2,4)其中第三个 按数值进行比较的字典序 最小,故答案为(1,2,4)
思考
最长递增子串这道题我们之前做过,使用动态规划即可轻松得出答案
但是对于这道题,有三个问题
**1、**我们要获取这个子串,而不是最长递增子串的长度
**2、**要想办法获取字典序最小的那个
对与问题1、2,我们可以在 dp 数组上动手脚,在dp[i]计算完成后,为了得到最长递增子序列,需要从后往前对dp数组进行一次遍历,找到符合条件的数填入答案中。由于 dp[i] 的值绝对能在左侧找到更小值 dp[i] ,故只需要选取第一次遇见的满足条件的数即可
public class NC91_LIS {
public static void main(String[] args) {
int[] arr = {2,1,5,3,6,4,8,9,7};
NC91_LIS solu = new NC91_LIS();
System.out.println(Arrays.toString(solu.LIS(arr)));
}
// 方法1:动态规划
public int[] LIS (int[] arr) {
int[] dp = new int[arr.length];
for (int i = 0; i < dp.length; i++) {
dp[i]=1;
}
// 获取最长递增子序列的 dp 数组
int maxLen = Integer.MIN_VALUE;
for (int i = 1; i < arr.length; i++) {
int preMax = 0;
for (int j = 0; j < i; j++) {
if (arr[j]<=arr[i]) preMax=Math.max(preMax,dp[j]);
}
dp[i]=preMax+1;
maxLen=Math.max(maxLen,dp[i]);
}
// 借助 dp 数组获取最长上升子序列
int[] res = new int[maxLen];
for (int i = dp.length-1,index = maxLen-1; index >=0 ; i--) {
if (dp[i]==maxLen) {
res[index--]=arr[i];
maxLen--;
}
}
return res;
}
}
熟悉我的读者知道,一旦我的题解没有写在题解里,说明这个解法跑不通,事实也确实如此,这里动规的时间复杂度为 O(n^2),会超时
所以我才要介绍下面使用二分法和贪心优化的方法:
这里我直接借助力扣上的思路
题解
二分的写法这里就作为作业,交个各位课后去做吧
NC32 求平方根
描述
求出一个数的平方根(如果是小数的话,只需要取整数位即可)
思考
方法一:暴力解法
这是最容易想到的解法,遍历从1-n的数,取其平方并与目标数比较即可
但是这个解法的问题也十分明显
1)时间复杂度相对较高(其实时间复杂度为 n,不是很高,但是与我们接下来要介绍的方法相比,就比较逊色了)
2)平方计算可能会溢出
方法二:二分法
其实就是暴力解法的改版
但是一样要注意溢出问题
这里可以运用除法,处理二分:
while (l<=r) {
int mid = (l + r) / 2;
if (mid<=x/mid && (mid+1)>x/(mid+1)) return mid;
if (mid>x/mid) r=mid-1;
else l=mid+1;
}
当然,如果这个写法一下子写不起来的,可以先写乘法溢出的版本,然后再调换成除法的版本
题解
import java.util.*;
public class Solution {
/**
*
* @param x int整型
* @return int整型
*/
public int sqrt (int x) {
if (x<=1) return x;
int l=0,r=x;
while (l<=r) {
int mid = (l + r) / 2;
if (mid<=x/mid && (mid+1)>x/(mid+1)) return mid;
if (mid>x/mid) r=mid-1;
else l=mid+1;
}
return -1;
}
}
NC121 字符串的排列
描述
输入一个长度为 n 字符串,打印出该字符串中字符的所有排列,你可以以任意顺序返回这个字符串数组。
例如输入字符串ABC,则输出由字符A,B,C所能排列出来的所有字符串ABC,ACB,BAC,BCA,CBA和CAB。
思考
这道题本身就是经典的回溯问题,由于篇幅限制,这里对回溯的思想就不做过多的讲解了,想要了解的同学可以自行搜索
题解
import java.util.ArrayList;
import java.util.HashSet;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String> res = new ArrayList<>();
boolean[] visited = new boolean[str.length()];
backTrack(str,res,new StringBuilder(),visited);
return res;
}
private void backTrack(String str,ArrayList<String> res,StringBuilder builder,boolean[] visited) {
// 递归终止条件
if (builder.toString().length()==str.length()) {
res.add(builder.toString());
return;
}
// 存储已经用过的 char,防止出现重复的字符
HashSet<Character> usedCharSet = new HashSet<>();
for (int i = 0; i < visited.length; i++) {
if (!visited[i] && !usedCharSet.contains(str.charAt(i))) {
usedCharSet.add(str.charAt(i));
builder.append(str.charAt(i));
visited[i]=true;
backTrack(str,res,builder,visited);
// 回溯
builder.replace(builder.length()-1,builder.length(),"");
visited[i]=false;
}
}
}
}