目录
- 什么是双指针?
- 双指针分类
- 1. i 和 i-1
- 1.1. 判断是否连续的迟到/早退 【找存在】
- 1.2. [简单] [leetcode674. 未排序的整数数组,找最长连续递增的子序列的长度](https://blog.csdn.net/weixin_37646636/article/details/129032921) [1,3,5,4,7]→[1,3,5]→3
- 1.3. [中等] [leetcode128 未排序的整数数组,找出数字连续的最长序列的长度(不要求序列元素在原数组中连续) ](https://blog.csdn.net/weixin_37646636/article/details/129052856) [100,4,200,1,3,2]→[1, 2, 3, 4]→4
- 1.4. [中等] [leetcode1839. 所有元音按顺序排布的最长子字符串](https://leetcode.cn/problems/longest-substring-of-all-vowels-in-order/description/)
- 1.5. [NC37 合并区间](https://blog.csdn.net/weixin_37646636/article/details/128926790)
- 2. i 和 i+1
- 3. for (int i = 0; i <= arr.length - 7; i++)
- 4. for (int i = 0; i < str.length(); )
- 6. 快慢指针
- 7. 逻辑
- 8. [0,i)回放
- 参考
什么是双指针?
双指针(Two Pointers):指的是在遍历元素的过程中,不是使用单个指针进行访问,而是使用两个指针进行访问,从而达到相应的目的。在数组的区间问题上,暴力算法的时间复杂度往往是
O(n^2)
。而双指针利用了区间「单调性」的性质,可以将时间复杂度降到O(n)
。
双指针分类
1、如果两个指针方向相反,则称为「对撞指针」;
2、如果两个指针方向相同,则称为「快慢指针」;
3、如果两个指针分别属于不同的数组 / 链表,则称为「分离双指针」;
对撞指针对撞指针求解步骤
第1步:使用两个指针 left,right
left 指向序列第一个元素,即:left = 0;
right 指向序列最后一个元素,即:right = len(nums) - 1。
第2步:在循环体中将左右指针 相向移动
当满足一定条件时,将左指针右移,left += 1;
当满足另外一定条件时,将右指针左移,right -= 1。
第3步:跳出循环体
直到两指针相撞(即 left == right);
或者满足其他要求的特殊条件时。
快慢指针快慢指针求解步骤
第1步:使用两个指针 slow、fast
slow 一般指向序列第一个元素,即:slow = 0;
fast 一般指向序列第二个元素,即:fast = 1。
第2步:在循环体中将两指针 向右移动 。
当满足一定条件时,将慢指针右移,即 slow += 1;
当满足另外一定条件时(也可能不需要满足条件),将快指针右移,即 fast += 1。
第3步:跳出循环体
快指针移动到数组尾端(即 fast == len(nums) - 1);
或者两指针相交,
或者满足其他特殊条件时。
分离双指针分离双指针求解步骤
第1步: 使用两个指针 left_1、left_2
left_1 指向第一个数组 / 链表的第一个元素,即:left_1 = 0,
left_2 指向第二个数组 / 链表的第一个元素,即:left_2 = 0。
第2步:在循环体中将两指针 向右移动。
当满足一定条件时,两个指针同时右移,即 left_1 += 1、left_2 += 1。
当满足另外一定条件时,将 left_1 指针右移,即 left_1 += 1。
当满足其他一定条件时,将 left_2 指针右移,即 left_2 += 1。
第3步:跳出循环体
当其中一个数组 / 链表遍历完时;
或者满足其他特殊条件时跳出循环体。
1. i 和 i-1
1.1. 判断是否连续的迟到/早退 【找存在】
/**
* 判断是否连续的迟到/早退
*/
private static boolean isBad(List<String> attendances) {
if (attendances.size() <= 1) {
return false;
}
List<String> absent = Arrays.asList("late", "leaveearly");
for (int i = 1; i < attendances.size(); i++) {
String current = attendances.get(i);
String previous = attendances.get(i - 1);
if (absent.contains(current) && absent.contains(previous)) {
// 存在有问题数据就直接终止判断并返回
return true;
}
}
return false;
}
1.2. [简单] leetcode674. 未排序的整数数组,找最长连续递增的子序列的长度 [1,3,5,4,7]→[1,3,5]→3
/**
* leetcode 674. 无序整数数组的最长且连续递增的子序列的长度
*/
public static int findLengthOfLCIS(int[] nums) {
if (nums.length == 0) {
return 0;
}
if (nums.length == 1) {
return 1;
}
int max = 1;
int start = 0;
for (int i = 1; i < nums.length; i++) {
if (nums[i - 1] < nums[i]) {
// 5-3=2表示5比3大2个,3到5一共有三个,故再加1
# 结束态 写法 故不需要收尾
max = Math.max(i - start + 1, max);
} else {
start = i;
}
}
return max;
}
/**
* leetcode 674. 无序整数数组的最长且连续递增的子序列的长度
*/
public static int findLengthOfLCIS2(int[] nums) {
if (nums.length == 0) {
return 0;
}
if (nums.length == 1) {
return 1;
}
int max = 1;
int tempMax = 1;
for (int i = 1; i < nums.length; i++) {
if (nums[i - 1] < nums[i]) {
# 中间态 写法 故需要最后收尾
tempMax++;
} else {
max = Math.max(tempMax, max);
tempMax = 1;
}
}
max = Math.max(tempMax, max);
return max;
}
/**
* leetcode 674. 无序整数数组的最长且连续递增的子序列
*/
public static int[] findLengthOfLCIS1(int[] nums) {
if (nums.length < 2) { // 2个一下不涉及排序
return nums;
}
int[] temp = {nums[0]}; // 下边从1开始和前面的值比较,起码有前面这个值,所以收集第一个元素应对[2,2,2,2,2]这种场景
int max = 1;
int start = 0;
for (int i = 1; i < nums.length; i++) {
if (nums[i - 1] < nums[i]) {
if (i - start + 1 > max) {
max = i - start + 1;
// 方法1:直接截取数组
temp = Arrays.copyOfRange(nums, start, i + 1);
// 方法2:记录开始结束坐标最后再截取
}
} else {
start = i;
}
}
return temp;
}
1.3. [中等] leetcode128 未排序的整数数组,找出数字连续的最长序列的长度(不要求序列元素在原数组中连续) [100,4,200,1,3,2]→[1, 2, 3, 4]→4
/**
* leetcode128 无序整数数组,找出数字连续的最长序列的长度(不要求序列元素在原数组中连续)
*/
public int longestConsecutive(int[] nums) {
if (nums.length == 0) {
return 0;
}
if (nums.length == 1) {
return 1;
}
Arrays.sort(nums);
int max = 1;
int tempMax = 1; // 下边从1开始和前面的值比较,起码有前面这个值,所以tempMax从1开始
for (int i = 1; i < nums.length; i++) {
if ((nums[i - 1]) == nums[i]) {
// 数字连续即要求严格递增,当前处理是针对如下场景
// [100,4,200,1,3,3,3,3,2] → [1,2,3,4] → 4
continue;
}
if ((nums[i - 1] + 1) == nums[i]) {
// 符合连续条件
tempMax++;
} else {
max = Math.max(tempMax, max); // 动态收集最大值
tempMax = 1; // 当前值字符为新一轮的开始,故tempMax其实为1
}
}
// 此处针对最后一位走的是(符合连续条件→然后循环完毕)这种场景
max = Math.max(tempMax, max);
return max;
}
1.4. [中等] leetcode1839. 所有元音按顺序排布的最长子字符串
这些元音字母的顺序都必须按照 字典序 升序排布
(也就是说所有的 'a' 都在 'e' 前面,所有的 'e' 都在 'i' 前面,以此类推)
/**
* 1839. 所有元音按顺序排布的最长子字符串
* 参考的题解→适合自己的题解
*/
public static int longestBeautifulSubstring(String word) {
Map<Character, Integer> valueMap = new HashMap();
valueMap.put('a', 0);
valueMap.put('e', 1);
valueMap.put('i', 2);
valueMap.put('o', 3);
valueMap.put('u', 4);
char[] chars = word.toCharArray();
int res = 0;
int start = 0;
for (int i = 1; i < word.length(); i++) {
// 'a'开始'u'结束且已保证递增,故可以保证('a' ,'e' ,'i' ,'o' ,'u')都必须 至少 出现一次
// 按照 字典序 升序排布
if (chars[start] == 'a' && (valueMap.get(chars[i - 1]) + 1 == valueMap.get(chars[i]) || valueMap.get(chars[i - 1]) == valueMap.get(chars[i]))) {
if (chars[i] == 'u') {
res = Math.max(i - start + 1, res);
}
} else {
start = i;
}
}
return res;
}
1.5. NC37 合并区间
public static ArrayList<Interval> merge(ArrayList<Interval> intervals) {
if (intervals.size() <= 1) {
return intervals;
}
// 两个指标都排序排序
List<Interval> orderedIntervals = intervals.stream().sorted((a, b) -> {
if (a.start == b.start) {
return a.end - b.end;
} else {
return a.start - b.start;
}
}).collect(Collectors.toList());
// 基底
ArrayList<Interval> res = new ArrayList<>();
Interval base = orderedIntervals.get(0);
for (int i = 1; i < orderedIntervals.size(); i++) {
if (isNotUnit(base, orderedIntervals.get(i))) {
res.add(base); // 不相交,收集基底
base = orderedIntervals.get(i); // 变基
} else {
base = getNew(base, orderedIntervals.get(i)); // 当前区间和Base区间取并集
}
if (i == orderedIntervals.size() - 1) { // 当前项为最后一项的话、直接收集起来
res.add(base);
}
}
return res;
}
/**
* 判断两个区间不相交
*/
public static boolean isNotUnit(Interval left, Interval right) {
return left.end < right.start;
}
/**
* 获取最大区间
*/
public static Interval getNew(Interval left, Interval right) {
int min = Math.min(left.start, right.start);
int max = Math.max(left.end, right.end);
return new Interval(min, max);
}
class Interval {
int start;
int end;
Interval() {
start = 0;
end = 0;
}
Interval(int s, int e) {
start = s;
end = e;
}
@Override
public String toString() {
return "[" + start + "," + end + ']';
}
}
2. i 和 i+1
2.1. NC17 最长回文子串
/**
* NC17 最长回文子串
*/
public static int getLongestPalindrome(String A) {
int max = 0;
int len = A.length();
//暴力解法
for (int i = 0; i < len; i++) {
for (int j = i + 1; j <= len; j++) {
String subStr = A.substring(i, j);//确定字符
if (isPalindrome(subStr) && subStr.length() > max) {
max = subStr.length();//最大长度
}
}
}
return max;
}
/**
* 判断子串是不是回文子串
*/
public static boolean isPalindrome(String s) {
// abcba 奇数时 5/2=2 范围for (int i = 0; i < 2; i++) [0 1] 2 3 4
// abccba 偶数时 6/2=3 范围for (int i = 0; i < 3; i++) [0 1 2] 3 4 5
int strLength = s.length();
for (int i = 0; i < strLength / 2; i++) {
// 数组折叠对应下标相加为strLength - 1
if (s.charAt(i) != s.charAt(strLength - 1 - i))//不相等
return false;
}
return true;
}
2.2. NC61 两数之和
private static int[] getIntegers(int[] numbers, int target) {
int[] res = new int[2];
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] > target) {
continue;
}
// 不存在回头的场景,有回头也是前面收集过的,所以j=i+1
for (int j = i + 1; j < numbers.length; j++) {
if (numbers[i] + numbers[j] == target) {
res[0] = i + 1;
res[1] = j + 1;
return res;
}
}
}
return res;
}
2.3. HJ65 查找两个字符串a,b中的最长公共子串
/**
* HJ65 查找两个字符串a,b中的最长公共子串
*/
public static String getLongestCommonSubstring(String s1, String s2) {
String shortStr = s1.length() < s2.length() ? s1 : s2;
String longStr = s1.length() > s2.length() ? s1 : s2;
int maxLen = 0;
int start = 0;
int end = 0;
for (int l = 0; l < shortStr.length(); l++) {
if (shortStr.length() - l <= maxLen) { // 剪枝,余下的子串长度已经不超过maxLen没必要计算,退出循环(性能优化)
break;
}
for (int r = shortStr.length(); r > l; r--) { // 左指针i,右指针right, 右指针逐渐向左逼近
String subStr = shortStr.substring(l, r);
if (longStr.contains(subStr) && subStr.length() > maxLen) {
maxLen = subStr.length();
start = l; // start 记录一下此时的下标为了后续截取字符串
end = r;
break;
}
}
}
// 方法1
// return shortStr.substring(start, start + maxLen);
// 方法2
return shortStr.substring(start, end);
}
3. for (int i = 0; i <= arr.length - 7; i++)
3.1. 判断是否任意连续7次考勤 缺勤/迟到/早退 超过3次
/**
* 判断是否任意连续7次考勤 缺勤/迟到/早退 超过3次
*/
private static boolean isBad2(List<String> list) {
if (list.isEmpty()) {
return false;
}
int[] arr = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
arr[i] = "present".equals(list.get(i)) ? 0 : 1;
}
if (arr.length <= 7) {
return Arrays.stream(arr).sum() > 3;
} else {
for (int i = 0; i <= arr.length - 7; i++) {
int[] ints = Arrays.copyOfRange(arr, i, i + 7); // 任意7天
int sum = Arrays.stream(ints).sum();
if (sum > 3) {
return true; // 存在有问题数据就直接终止判断并返回
}
}
}
return false;
}
3.2. leetcode239. 滑动窗口最大值
/**
* leetcode239. 滑动窗口最大值
*/
private static int[] getIntegers(int[] nums, int k) {
// 收集结果容器
List<Integer> res = new ArrayList<>();
// 业务逻辑处理
for (int i = 0; i <= nums.length - k; i++) {
int[] ints = Arrays.copyOfRange(nums, i, i + k);
Arrays.stream(ints).max().ifPresent(res::add);
}
return res.stream().mapToInt(Integer::intValue).toArray();
}
3.3. 字符串是否含有长度大于2的相同子串(注:其他符号不含空格或换行)
if (hasSameSubStr(str, 0, 3)) {
return "NG";
}
# 递归遍历
/**
* 是否有长度大于2的相同子串 (注:其他符号不含空格或换行)
*/
private static boolean hasSameSubStr(String str, int l, int r) {
// 最优化应该是如果截取3位的话应该是r>=str.length()-2,n位应该是r>=str.length()-(n-1)
if (r >= str.length()) {
return false;
}
// 母串
String sourceStr = str.substring(r);
// 子串
String targetStr = str.substring(l, r);
// 母串是否包含子串
if (sourceStr.contains(targetStr)) {
return true;
} else {
return hasSameSubStr(str, l + 1, r + 1);
}
}
---------------------------------------------------------------------------------
# 正常遍历
private static boolean hasSameSubStr2(String str) {
for (int i = 0; i < str.length() - 3; i++) {
String targetStr = str.substring(i, i + 3);
String sourceStr = str.substring(i + 3);
if (sourceStr.contains(targetStr)) {
return true;
}
}
return false;
}
4. for (int i = 0; i < str.length(); )
4.1. 字符串8位一截取,不够8位后面补0
/**
* 字符串8位一截取,不够8位后面补0
*/
private static List<String> splitSubStrings(String str) {
if (str.length() == 0) {
return new ArrayList<>();
}
List<String> res = new ArrayList<>();
for (int i = 0; i < str.length(); ) {
if (i + 8 < str.length()) {
String substring = str.substring(i, i + 8);
res.add(substring);
i = i + 8;
} else {
String substring = str.substring(i);
res.add(fillStr(substring));
i = i + 8;
}
}
return res;
}
public static String fillStr(String str) {
if (str.length() >= 8) {
return str;
}
return fillStr(str + "0");
}
4.2. 截取字符串使得负数正数相加和最小
private static int getSum(String str) {
// 结果收集容器
List<Integer> res = new ArrayList<>();
for (int i = 0; i < str.length(); ) {
if (Character.isDigit(str.charAt(i))) {
res.add(Character.getNumericValue(str.charAt(i))); // 数字直接收集,坐标跳到下一位
i = i + 1;
} else if ('-' == str.charAt(i)) {
// 遇到负号开始拼接最大负数
int start = i; // 记录开始位置
i = i + 1; // 跳过当前负号的位置,到下一坐标
while (i < str.length() && Character.isDigit(str.charAt(i))) { // 如果下一位在范围内,且是数字,继续蚕食
i = i + 1;
}
if (i - start > 1) { // 如果只有一个负号,这种数据舍弃,负号和数字组合长度必然大于1才收集
// i为下一个位置的坐标,也就可以视为当前子串的右边界
res.add(Integer.parseInt(str.substring(start, i)));
}
} else {
i = i + 1; // 非数字,非负号,皆跳过
}
}
System.out.println(res);
// 输出结果
int sum = res.stream().mapToInt(Integer::intValue).sum();
return sum;
}
4.3. [算法题] 连续字母长度
/**
* [算法题]连续字母长度
*/
private static Map<Character, Integer> getCharAndSizeMap(String str) {
Map<Character, Integer> map = new HashMap<>();
Deque<Character> dynamicChars;
for (int i = 0; i < str.length(); ) {
dynamicChars = new LinkedList<>();
while (i < str.length() && (dynamicChars.isEmpty() || dynamicChars.contains(str.charAt(i)))) { // ((有效范围内&&(第一字符时||还是这个字符时))收集此字符
dynamicChars.addLast(str.charAt(i));
i = i + 1; // 当前字符处理完毕,跳到下个字符坐标
}
Integer tempMax = map.getOrDefault(dynamicChars.getFirst(), 0); // 取出当前字符已知的最大长度
map.put(dynamicChars.getFirst(), Math.max(tempMax, dynamicChars.size())); // 动态算取最大长度
}
return map;
}
6. 快慢指针
自我潜规则:i表示快指针,j表示慢指针
6.1. leetcode392 判断字符串 s 是否为 t 的子序列
/**
* leetcode392 判断字符串 s 是否为 t 的子序列
*/
private static boolean isSubsequence(String s, String t) {
if (s.length() == 0) {
return true;
}
if (t.length() == 0) {
return false;
}
// 快慢指针
int j = 0;
for (int i = 0; i < t.length(); i++) {
# 一个有序公布,一个去对比,体会彩票公布对号检查场景
if (s.charAt(j) == t.charAt(i)) {
j++; // 相等的话指向慢指针的下个位置
if (j >= s.length()) {
return true;
}
}
}
return false;
}
6.2. NC28 最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。
public static String minWindow(String s, String t) {
// 没有用map统计每个字符的数量,而是用了int[](天然的Map)
# 天然Map
int[] tChars = new int[128];
for (int i = 0; i < t.length(); i++) {
tChars[t.charAt(i)] ++;
}
int l = -1;
int r = -1;
int minLen = Integer.MAX_VALUE;
# 毛毛虫式遍历
int j = 0;
for (int i = 0; i < s.length(); i++) {
tChars[s.charAt(i)]--; // >0表示t串此字符多,<0表示s串此字符多, =0表示字符数相等
while (allItemLE0(tChars)) { // 都小于0说明s串覆盖t串了
if (i - j + 1 < minLen) { // 动态求最小覆盖子串
minLen = i - j + 1;
l = j;
r = i;
}
// j因为要右移动,所以把对应位置的字符收集起来,重新去排除
tChars[s.charAt(j)]++;
j++;
}
}
if (l == -1) { //找不到的情况
return "";
}
return s.substring(l, r + 1);
}
# 30ms,43.5M
-------------------------
# forEach 用时:51ms 43.9M
int[] cnt = new int[128];
for (char ch : t.toCharArray()) {
cnt[ch]++;
}
-----------------------
public static boolean allItemLE0(int[] arr) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] > 0) {
return false;
}
}
return true;
}
6.3. HJ70 矩阵乘法计算量估算
矩阵行列
50 10
10 20
20 5
计算顺序
(A(BC))
/**
* HJ70 矩阵乘法计算量估算
*/
private static int matrixMultiplication(int[][] matrix, String actionStr) {
int result = 0;
int j = 0;
# 唐歌舞式的遍历数组
Deque<int[]> stack1 = new LinkedList<>(); // 存放矩阵行数和列数
for (int i = 0; i < actionStr.length(); i++) {
if (actionStr.charAt(i) == '(') {
// 啥也不做
} else if ('A' <= actionStr.charAt(i) && actionStr.charAt(i) <= 'Z') {
stack1.push(matrix[j]);
j++;
} else if (actionStr.charAt(i) == ')') {
int[] pop2 = stack1.pop();
int[] pop1 = stack1.pop();
result += pop1[0] * pop2[1] * pop1[1];
int[] newMatrix = {pop1[0], pop2[1]};
stack1.push(newMatrix);
}
}
return result;
}
7. 逻辑
7.1. HJ41 称砝码
现有n种砝码,重量互不相等,分别为 m1,m2,m3…mn ;每种砝码对应的数量为 x1,x2,x3...xn 。
现在要用这些砝码去称物体的重量(放在同一侧),问能称出多少种不同的重量。注:称重重量包括 0
// n种砝码
int n = parseInt(bf.readLine());
// 每种砝码重量
Integer[] w = Arrays.stream(bf.readLine().split(" ")).limit(n).map(Integer::parseInt).toArray(Integer[]::new);
// 每种几个
Integer[] c = Arrays.stream(bf.readLine().split(" ")).limit(n).map(Integer::parseInt).toArray(Integer[]::new);
/**
* @param n n种砝码
* @param w 每种砝码重量
* @param c 每种几个
*/
private static HashSet<Integer> getWeights(int n, Integer[] w, Integer[] c) {
HashSet<Integer> res = new HashSet<>();
res.add(0); // 由示例可知,0重量也属于一种情况
for (int i = 0; i < n; i++) { // 遍历种数
HashSet<Integer> resPer = new HashSet<>(); // 遍历res时再往里追加会有问题,所以引入一个中间变量
for (int k = 1; k <= c[i]; k++) { // 遍历个数
int stepWeight = w[i] * k; // 遍历重量
resPer.add(stepWeight);
for (Integer weight : res) {
resPer.add(weight + stepWeight);
}
}
res.addAll(resPer);
}
return res;
}
7.2. HJ48 从单向链表中删除指定值的节点
6 2 1 2 3 2 5 1 4 5 7 2 2
第一个参数6表示输入总共6个节点
第二个参数2表示头节点值为2
剩下的2个一组表示第1个节点值插入到第2个节点值后面
最后一个参数为2,表示要删掉节点为2的值
// 6 2 1 2 3 2 5 1 4 5 7 2 2
// 6 {2 [1 2 3 2 5 1 4 5 7 2]} 2
String[] split = str.split(" ");
# 构建链表
ListNode head = buildListNode(arr);
// 最后一个参数为2,表示要删掉节点为2的值
int del_number = Integer.parseInt(arr[arr.length - 1]);
StringBuilder sb = new StringBuilder();
ListNode temp = head;
while (temp != null) {
if (temp.val != del_number) { // 以跳过的方式模拟删除节点
sb.append(temp.val).append(" ");
}
temp = temp.next;
}
System.out.println(sb.toString()); // 注意要求每个数后面都加空格
----------------------------------------------------------------------------------
/**
* HJ48 从单向链表中删除指定值的节点
* 官方题解的基础上结合了好理解的方式的实现
*/
public static ListNode buildListNode(String[] arr) {
int cnt = Integer.parseInt(arr[0]); // 总共有多少个节点
ListNode head = new ListNode(Integer.parseInt(arr[1])); // 头结点
// 6 {2 [1 2 3 2 5 1 4 5 7 2]} 2
String[] strings = Arrays.copyOfRange(arr, 2, arr.length - 1); // 成对的节点
for (int i = 0; i < cnt - 1; i++) { // 头结点为2已确定,6组数据还剩5组,i表示组数
// 2个一组,表示第1个节点值插入到第2个节点值后面
int willInsert = Integer.parseInt(strings[2 * i]);
int afterWho = Integer.parseInt(strings[2 * i + 1]);
ListNode tmp = head;
while (tmp.val != afterWho) { // 找到插入的位置
tmp = tmp.next;
}
// 链表前后链接
ListNode newNode = new ListNode(willInsert);
newNode.next = tmp.next;
tmp.next = newNode;
}
return head;
}
/**
* 单向链表
*/
class ListNode {
ListNode next;
int val;
ListNode(int val) {
this.val = val;
next = null;
}
}
8. [0,i)回放
8.1. leetcode 300. 整数数组的最长严格递增子序列的长度
/**
* leetcode 300. 整数数组的最长严格递增子序列的长度
*/
private static int lengthOfLIS(int[] nums) {
if (nums.length == 0) {
return 0;
}
// 参数初始化
int[] dp = new int[nums.length];
// 方法1
for (int i = 0; i < dp.length; i++) {
dp[i] = 1; // 每个序列起码有自己所以长度最小为1
}
// 方法2
// Arrays.fill(dp, 1);
int max = 1; // 每个序列起码有自己所以长度最小为1
for (int i = 1; i < nums.length; i++) {
for (int j = 0; j < i; j++) { // 通过回放计算出dp[i]的最优值
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[j] + 1, dp[i]); // 状态转移方程
}
}
max = Math.max(dp[i], max); // dp[i]的最优值和max相比计算出max的最优值
}
return max;
}