leetcode 332: Reconstruct Itinerary:最小欧拉路径 & java8新特性

332. Reconstruct Itinerary

My Submissions
Total Accepted: 4958  Total Submissions: 21506  Difficulty: Medium

Given a list of airline tickets represented by pairs of departure and arrival airports [from, to], reconstruct the itinerary in order. All of the tickets belong to a man who departs from JFK. Thus, the itinerary must begin with JFK.

Note:

  1. If there are multiple valid itineraries, you should return the itinerary that has the smallest lexical order when read as a single string. For example, the itinerary ["JFK", "LGA"] has a smaller lexical order than ["JFK", "LGB"].
  2. All airports are represented by three capital letters (IATA code).
  3. You may assume all tickets form at least one valid itinerary.

Example 1:
tickets = [["MUC", "LHR"], ["JFK", "MUC"], ["SFO", "SJC"], ["LHR", "SFO"]]
Return ["JFK", "MUC", "LHR", "SFO", "SJC"].

Example 2:
tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
Return ["JFK","ATL","JFK","SFO","ATL","SFO"].
Another possible reconstruction is ["JFK","SFO","ATL","JFK","ATL","SFO"]. But it is larger in lexical order.

这道题目是一道图论题,直观的想法是:对路径排序(这样保证起点相同时,每次取出的路径最小),然后进行从起点进行DFS搜索,一定会搜索完成(这道题目保证了这条路径一定存在)。思路清晰但是 没有思考量,代码繁琐。
public class Solution {
    public String[][] sort(String[][] tickets){
		String min0,min1;
		int index;
		for(int i=0;i<tickets.length-1;i++){
			min0=tickets[i][0];
			min1=tickets[i][1];
			index=i;
			for(int j=i+1;j<tickets.length;j++){
				if(tickets[j][0].compareTo(min0)<0){
					min0=tickets[j][0];
					min1=tickets[j][1];
					index=j;
				}
				else {
					if(tickets[j][0].compareTo(min0)==0){
						if(tickets[j][1].compareTo(min1)<0){
							min1=tickets[j][1];
							index=j;
						}
					}
				}
			}
			if(index!=i){
				tickets[index][0]=tickets[i][0];
				tickets[index][1]=tickets[i][1];
				tickets[i][0]=min0;
				tickets[i][1]=min1;
			}
		}
		return tickets;
	}
	public boolean search(int count,boolean[] use,String[][] tickets,ArrayList<String> path){
		if(count==0){
			int i;
			for(i=0;i<tickets.length;i++)
				if(tickets[i][0].compareTo("JFK")==0)
					break;
			for(int j=i;j<tickets.length;j++){
				if(tickets[j][0].compareTo("JFK")!=0)
					break;
				count++;
				use[j]=true;
				path.add(tickets[j][0]);
				path.add(tickets[j][1]);
				if(search(count, use, tickets, path))
					return true;
				count--;
				use[j]=false;
				path.remove(path.size()-1);
				path.remove(path.size()-1);
			}
		}
		else {
			if(count==tickets.length){
				return true;
			}
			else {
				int i;
				for(i=0;i<tickets.length;i++){
					if(tickets[i][0].compareTo(path.get(path.size()-1))==0)
						break;
				}
				for(int j=i;j<tickets.length;j++){
					if(tickets[j][0].compareTo(tickets[i][0])!=0)
						break;
					if(use[j]==false){
						use[j]=true;
						count++;
						path.add(tickets[j][1]);
						if(search(count, use, tickets, path))
							return true;
						count--;
						use[j]=false;
						path.remove(path.size()-1);
					}
				}
			}
		}
		return false;
	}
	public ArrayList<String> findItinerary(String[][] tickets) {
		ArrayList<String> path=new ArrayList<>();
		if(tickets.length==0)
			return path;
		if(tickets.length==1){
			path.add(tickets[0][0]);
			path.add(tickets[0][1]);
			return path;
		}
		tickets=sort(tickets);
		boolean[] use=new boolean[tickets.length];
		for(int i=0;i<tickets.length;i++)
			use[i]=false;
		int count=0;
		search(count,use,tickets,path);
		return path;
	}
}

实际上,这道题目是计算最小的欧拉路径。通过分析可以知道:
如果一个有向图,存在欧拉路径(不是欧拉回路),那么图中的点最多只可能有两个点:degree(入)!=degree(出),并且这两个点,一个入度>出度,一个入度<出度;也有可能所有点 degree(入)==degree(出),则存在欧拉回路
很明显,既然题目保证存在欧拉路径,那么JFK就是那个入度<出度的点,并且存在一个点 入度>出度;或者所有点入度==出度
贪心法:从JFK开始,每次选取最小路径走,如果走不下去,只有可能遇到了终结点PP(那个 入度>出度的点),这样就形成了从JFK到PP的主路径。剩下没走的边只有可能形成环,只要将环并入到主路径上就完成了最小欧拉路径的搜索!!
这就是最优解法:
(递归版)
public List<String> findItinerary(String[][] tickets) {
    for (String[] ticket : tickets)
        targets.computeIfAbsent(ticket[0], k -> new PriorityQueue()).add(ticket[1]);
    visit("JFK");
    return route;
}

Map<String, PriorityQueue<String>> targets = new HashMap<>();
List<String> route = new LinkedList();

void visit(String airport) {
    while(targets.containsKey(airport) && !targets.get(airport).isEmpty())
        visit(targets.get(airport).poll());
    route.add(0, airport);
}

(迭代版)
public List<String> findItinerary(String[][] tickets) {
    Map<String, PriorityQueue<String>> targets = new HashMap<>();
    for (String[] ticket : tickets)
        targets.computeIfAbsent(ticket[0], k -> new PriorityQueue()).add(ticket[1]);
    List<String> route = new LinkedList();
    Stack<String> stack = new Stack<>();
    stack.push("JFK");
    while (!stack.empty()) {
        while (targets.containsKey(stack.peek()) && !targets.get(stack.peek()).isEmpty())
            stack.push(targets.get(stack.peek()).poll());
        route.add(0, stack.pop());
    }
    return route;
}

上述代码中使用到了比较好的数据结构和较新的语言特性:
1.PriorityQueue<String> 优先权队列
队列中的元素按照自然顺序排列,如果没有实现Comparator接口数字由小到大,字符串按字典序。
为了方便使用,给出一个demo:
import java.util.Comparator;  
import java.util.PriorityQueue;  
import java.util.Queue;  
  
public class test {  
    private String name;  
    private int population;  
    public test(String name, int population)  
    {  
        this.name = name;  
        this.population = population;  
    }  
    public String getName()  
    {  
         return this.name;  
    }  
  
    public int getPopulation()  
    {  
         return this.population;  
    }  
    public String toString()  
    {  
         return getName() + " - " + getPopulation();  
    }  
    public static void main(String args[])  
    {  
        Comparator<test> OrderIsdn =  new Comparator<test>(){  
            public int compare(test o1, test o2) {  
                // TODO Auto-generated method stub  
                int numbera = o1.getPopulation();  
                int numberb = o2.getPopulation();  
                if(numberb > numbera)  
                {  
                    return 1;  
                }  
                else if(numberb<numbera)  
                {  
                    return -1;  
                }  
                else  
                {  
                    return 0;  
                }  
              
            }    
        };  
        Queue<test> priorityQueue =  new PriorityQueue<test>(11,OrderIsdn);  

        test t1 = new test("t1",1);  
        test t3 = new test("t3",3);  
        test t2 = new test("t2",2);  
        test t4 = new test("t4",0);  
        priorityQueue.add(t1);  
        priorityQueue.add(t3);  
        priorityQueue.add(t2);  
        priorityQueue.add(t4);  
        System.out.println(priorityQueue.poll().toString());  
    }  
}  


2.Map的putIfAbsent避免我们将null写入。
targets.computeIfAbsent(ticket[0], k -> new PriorityQueue()).add(ticket[1]);
使用了java8加入的新特性:函数式表达式。


(1)关于Map的具体用法:

Map<Integer, String> map = new HashMap<>();
 
for (int i = 0; i < 10; i++) {
    map.putIfAbsent(i, "val" + i);
}
 
map.forEach((id, val) -> System.out.println(val));
上面的代码风格是完全自解释的:putIfAbsent避免我们将null写入;forEach接受一个消费者对象,从而将操作实施到每一个map中的值上。
下面的这个例子展示了如何使用函数来计算map的编码:
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3);             // val33
 
map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9);     // false
 
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23);    // true
 
map.computeIfAbsent(3, num -> "bam");
map.get(3);             // val33
接下来,我们将学习,当给定一个key值时,如何把一个实例从对应的key中移除:
map.remove(3, "val3");
map.get(3);             // val33
 
map.remove(3, "val33");
map.get(3);             // null
另一个有用的方法:
map.getOrDefault(42, "not found");  // not found
将map中的实例合并也是非常容易的:
map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9
 
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9concat
合并操作先看map中是否没有特定的key/value存在,如果是,则把key/value存入map,否则merging函数就会被调用,对现有的数值进行修改。


(2)Lambda表达式
让我们从最简单的例子开始,来学习如何对一个string列表进行排序。我们首先使用Java 8之前的方法来实现:
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
 
Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});
静态工具方法Collections.sort接受一个list,和一个Comparator接口作为输入参数,Comparator的实现类可以对输入的list中的元素进行比较。通常情况下,你可以直接用创建匿名Comparator对象,并把它作为参数传递给sort方法。
除了创建匿名对象以外,Java 8 还提供了一种更简洁的方式,Lambda表达式。
Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
});
你可以看到,这段代码就比之前的更加简短和易读。但是,它还可以更加简短
Collections.sort(names, (String a, String b) -> b.compareTo(a));
只要一行代码,包含了方法体。你甚至可以连大括号对{}和return关键字都省略不要。不过这还不是最短的写法:
Collections.sort(names, (a, b) -> b.compareTo(a));
Java编译器能够自动识别参数的类型,所以你就可以省略掉类型不写。让我们再深入地研究一下lambda表达式的威力吧。


(3)函数式接口

Lambda表达式如何匹配Java的类型系统?每一个lambda都能够通过一个特定的接口,与一个给定的类型进行匹配。一个所谓的函数式接口必须要有且仅有一个抽象方法声明。每个与之对应的lambda表达式必须要与抽象方法的声明相匹配。由于默认方法不是抽象的,因此你可以在你的函数式接口里任意添加默认方法。

任意只包含一个抽象方法的接口,我们都可以用来做成lambda表达式。为了让你定义的接口满足要求,你应当在接口前加上@FunctionalInterface 标注。编译器会注意到这个标注,如果你的接口中定义了第二个抽象方法的话,编译器会抛出异常。

@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}
 
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted);    // 123
上面的代码实例可以通过静态方法引用,使之更加简洁:
Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted);   // 123
Java 8 允许你通过::关键字获取方法或者构造函数的的引用。上面的例子就演示了如何引用一个静态方法。而且,我们还可以对一个对象的方法进行引用:

class Something {
    String startsWith(String s) {
        return String.valueOf(s.charAt(0));
    }
}
 
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted);    // "J"


java8中还有很多内置的函数式接口,上面使用的Map中的computeIfPresent()就是一个函数式接口。具体操作查阅详细资料。



以上教学内容来自:
http://www.importnew.com/10360.html
http://blog.csdn.net/hiphopmattshi/article/details/7334487




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值