一、题目
思路分析:
- 如果说在w维度上,wi是互不相同的,那么只需要对排序后的二维数组的h维度求一个最长递增子序列的长度即可;
- 需要特别考虑wi相同的这部分:宽度相同的一组信封,不可能相互嵌套,也就是只能从中选取一个信封;
- 如何保证这一点?当宽度相同时,高度降序排序。比如说一组信封[1,2],[1,3],[1,4],如果按照升序排序,会出现[1,2]->[1,3]->[1,4]相互嵌套的问题,但是按照降序排序,4,3,2就不可能相互组成一个递增的序列。
代码实现
class Solution {
public int maxEnvelopes(int[][] envelopes) {
// 排序
Arrays.sort(envelopes, new Comparator<int[]>(){
@Override
public int compare(int[] o1, int[] o2){
return o1[0]==o2[0]?o2[1]-o1[1]:o1[0]-o2[0];
}
});
// 选取hi作为求最长递增子序列的数组
int[] heigth = new int[envelopes.length];
for(int i=0;i<envelopes.length;i++){
heigth[i] = envelopes[i][1];
}
return LISLength(heigth);
}
public int LISLength(int[] heigth){
int[] dp = new int[heigth.length];
dp[0]=heigth[0];
int len = 0;
for(int i=0;i<heigth.length;i++){
// 大于最后一个堆顶元素 直接插入
if(heigth[i]>dp[len]){
len++;
dp[len]=heigth[i];
}
else{
int left = 0;
int right = len;
// 二分法
while(left<right){
int mid = left+(right-left)/2;
if(dp[mid]<heigth[i]){
left = mid+1;
}else{
// 最左边界
right = mid;
}
}
// 可以保证height[i]<=dp[len] 一定会有一个插入位置
dp[left] = heigth[i];
}
}
// 返回dp数组的长度:当前索引下标+1
len++;
return len;
}
}
- 时间复杂度:O(nlogn),数组排序的时间复杂度为O(nlogn),遍历数组并对其进行二分查找的时间复杂度也是O(nlogn)
- 空间复杂度:O(n) dp数组和height数组
二、匿名内部类
- 内部类:定义在类中的类,主要作用是将逻辑相关的类放在其中
- 匿名内部类:一种特殊的内部类,没有类名,在继承一个父类或实现一个接口的同时生成该类的一个对象(由于不用在其他地方用到,不需要类名)
匿名内部类的一些使用场景
- 为Thread传入一个Runnable对象
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
}
});
查看Runnable接口的源码:
可以看到Runnable被标记为函数式接口,所谓函数式接口,指的是只有一个抽象方法的接口。而lambda表达式的使用场景是:“任何有函数式接口的地方”。
- 在Arrays.sort()方法中传入一个Comparator对象
Arrays.sort(envelopes, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
}
});
三、lambda表达式
假设有一个类Item,现在需要根据Item的价格、评分筛选出符合条件的对象:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Item {
// 商品名称
private String title;
// 商品价格
private Double price;
// 商品评分
private Integer level;
}
1、写两个根据条件过滤Item的方法
public class Test {
public static void main(String[] args) {
List<Item> itemList = new ArrayList<>();
itemList.add(new Item("茶叶",118.50,5));
itemList.add(new Item("纸巾",39.99,4));
itemList.add(new Item("鼠标",59.00,3));
// 筛选条件:价格低于100
filterItemByPrice(itemList,100.00);
System.out.println("---------------");
filterItemByLevel(itemList,4);
}
/**
*
* @param itemList 对商品列表进行筛选
* @param condition 筛选条件
* @return 符合条件的商品列表
*/
public static void filterItemByPrice(List<Item> itemList, double condition){
List<Item> item = new ArrayList<>();
for (Item item1:itemList){
if (item1.getPrice()<condition){
item.add(item1);
}
}
printItem(item);
}
public static void filterItemByLevel(List<Item> itemList, Integer condition){
List<Item> item = new ArrayList<>();
for (Item item1:itemList){
if (item1.getLevel()>condition){
item.add(item1);
}
}
printItem(item);
}
public static void printItem(List<Item> items){
items.forEach(System.out::println);
}
}
可以发现,这两个方法的逻辑非常相似,可以纵向抽取出来定义一个接口,让子类各自实现具体的逻辑:
2、定义接口
// 接口中定义一个抽象方法实现筛选
public interface ItemFilter<T> {
public boolean filter(Item item,T condition);
}
// 实现根据价格筛选
public class ItemFilterByPrice implements ItemFilter{
@Override
public boolean filter(Item item, Object condition) {
return item.getPrice()<(double)condition;
}
}
// 实现根据评分筛选
public class ItemFilterByLevel implements ItemFilter{
@Override
public boolean filter(Item item, Object condition) {
return item.getLevel()>(Integer) condition;
}
}
/**
* 封装一个传入过滤器和条件返回符合条件的商品列表函数
* @param itemList 传入商品列表
* @param itemFilter 过滤器
* @param condition 筛选条件
* @return
*/
public static List<Item> getByFilter(List<Item> itemList,ItemFilter itemFilter,Object condition){
List<Item> ans = new ArrayList<>();
for (Item item:itemList){
if (itemFilter.filter(item,condition)){
ans.add(item);
}
}
return ans;
}
但是,接口ItemFilter中的filter方法的逻辑非常简单,我们是不是可以在调用的时候直接实现这个接口?并且只需要使用一次,也就是不需要持有这个对象的引用,不需要类名,这个场景就适合使用匿名内部类了。
3、匿名内部类
public static void main(String[] args) {
List<Item> itemList = new ArrayList<>();
itemList.add(new Item("茶叶",118.50,5));
itemList.add(new Item("纸巾",39.99,4));
itemList.add(new Item("鼠标",59.00,3));
// 筛选条件:价格低于100
// 使用时直接实现该抽象类
List<Item> itemList1 = getByFilter(itemList, new ItemFilter() {
@Override
public boolean filter(Item item, Object condition) {
return item.getPrice()<(Double) condition;
}
},100.00);
List<Item> itemList2 = getByFilter(itemList, new ItemFilter() {
@Override
public boolean filter(Item item, Object condition) {
return item.getLevel()<(Integer) condition;
}
},4);
printItem(itemList1);
System.out.println("-----------");
printItem(itemList2);
}
接口ItemFilter中只有一个抽象方法,是不是很符合函数式接口的定义?而lambda表达式的使用场景就是“一切有函数式接口的地方”。
4、函数式接口和lambda表达式
public static void main(String[] args) {
List<Item> itemList = new ArrayList<>();
itemList.add(new Item("茶叶",118.50,5));
itemList.add(new Item("纸巾",39.99,4));
itemList.add(new Item("鼠标",59.00,3));
// 筛选条件:价格低于100
// 实现函数式接口可以使用lambda表达式
List<Item> itemList1 = getByFilter(itemList,((item, condition) -> {
return item.getPrice()<(Double) condition;
}),100.00);
List<Item> itemList2 = getByFilter(itemList,((item, condition) -> {
return item.getLevel()>(Integer) condition;
}),4);
printItem(itemList1);
System.out.println("-----------");
printItem(itemList2);
}
四、总结
匿名内部类、函数式接口、lambda表达式是Java中很重要的语言特性,以前缺少编码经验,对这些的理解都停留在概念上。从一个比较器中突然明白,这不就是一个匿名内部类吗?匿名内部类不需要类名,在调用时直接实现一个接口或继承一个父类。而函数式接口是只有一个抽象方法的接口,因为只有一个,所以使用lambda表达式那样简略的写法也可以确定调用的是哪一个方法。