LeetCode 659 - Split Array into Consecutive Subsequences


https://leetcode.com/problems/split-array-into-consecutive-subsequences/
You are given an integer array sorted in ascending order (may contain duplicates), you need to split them into several subsequences, where each subsequences consist of at least 3 consecutive integers. Return whether you can make such a split.
Example 1:
Input: [1,2,3,3,4,5]
Output: True
Explanation:
You can split them into two consecutive subsequences : 
1, 2, 3
3, 4, 5
Example 2:
Input: [1,2,3,3,4,4,5,5]
Output: True
Explanation:
You can split them into two consecutive subsequences : 
1, 2, 3, 4, 5
3, 4, 5
Example 3:
Input: [1,2,3,4,4,5]
Output: False
Note:
  1. The length of the input is in range of [1, 10000]
https://leetcode.com/problems/split-array-into-consecutive-subsequences/solution/
For each element, 1. It could append to an existing subsequence OR 2. It could be the start of a new subsequence. Otherwise, return false.

To all who are confused why we should put 'append existed split' part before 'create a new split' part. Generally, this is followed by a strict mathematics proof but intuitively you can think in this way. Given example '1 2 3 3 4 4 5 5' the greedy way to handle this is by appending '1 2 3' with '4 5' and what's left is '3 4 5' then we're done. However, if you stop by '1 2 3' and when you see the second '3' you create a new split that is '3 4 5' then we fail ('4 5' are left). This example illustrates why you should adopt 'append' strategy first. Is there any example we should stop immediately when we have three numbers in a split? Yes, given '1 2 3 3 4 5', you might not scan till the end to format '1 2 3 4 5', otherwise '3' will be alone. However, our strategy also works in some way. Once it reaches the second '3' it will automatically add '4' to apendfreq which means we've already finished '1 2 3' and let's start a new split.


public boolean isPossible(int[] nums) {
    Map<Integer, Integer> freq = new HashMap<>(), appendfreq = new HashMap<>();
    for (int i : nums) freq.put(i, freq.getOrDefault(i,0) + 1);
    for (int i : nums) {
        if (freq.get(i) == 0) continue;
        else if (appendfreq.getOrDefault(i,0) > 0) {
            appendfreq.put(i, appendfreq.get(i) - 1);
            appendfreq.put(i+1, appendfreq.getOrDefault(i+1,0) + 1);
        }   
        else if (freq.getOrDefault(i+1,0) > 0 && freq.getOrDefault(i+2,0) > 0) {
            freq.put(i+1, freq.get(i+1) - 1);
            freq.put(i+2, freq.get(i+2) - 1);
            appendfreq.put(i+3, appendfreq.getOrDefault(i+3,0) + 1);
        }
        else return false;
        freq.put(i, freq.get(i) - 1);
    }
    return true;
}
https://leetcode.com/problems/split-array-into-consecutive-subsequences/discuss/106496/Java-O(n)-Time-O(n)-Space
  1. We iterate through the array once to get the frequency of all the elements in the array
  2. We iterate through the array once more and for each element we either see if it can be appended to a previously constructed consecutive sequence or if it can be the start of a new consecutive sequence. If neither are true, then we return false.
Approach #2: Greedy [Accepted]
Call a chain a sequence of 3 or more consecutive numbers.
Considering numbers x from left to right, if x can be added to a current chain, it's at least as good to add x to that chain first, rather than to start a new chain.
Why? If we started with numbers x and greater from the beginning, the shorter chains starting from x could be concatenated with the chains ending before x, possibly helping us if there was a "chain" from x that was only length 1 or 2.
Algorithm
Say we have a count of each number, and let tails[x] be the number of chains ending right before x.
Now let's process each number. If there's a chain ending before x, then add it to that chain. Otherwise, if we can start a new chain, do so.
It's worth noting that our solution can be amended to take only O(1) additional space, since we could do our counts similar to Approach #1, and we only need to know the last 3 counts at a time.
  • Time Complexity: O(N), where N is the length of nums. We iterate over the array.
  • Space Complexity: O(N), the size of count and tails.
  public boolean isPossible(int[] nums) {
    Counter count = new Counter();
    Counter tails = new Counter();
    for (int x : nums)
      count.add(x, 1);

    for (int x : nums) {
      if (count.get(x) == 0) {
        continue;
      } else if (tails.get(x) > 0) {
        tails.add(x, -1);
        tails.add(x + 1, 1);
      } else if (count.get(x + 1) > 0 && count.get(x + 2) > 0) {
        count.add(x + 1, -1);
        count.add(x + 2, -1);
        tails.add(x + 3, 1);
      } else {
        return false;
      }
      count.add(x, -1);
    }
    return true;
  }
}

class Counter extends HashMap<Integer, Integer> {
  public int get(int k) {
    return containsKey(k) ? super.get(k) : 0;
  }

  public void add(int k, int v) {
    put(k, get(k) + v);

  }

http://www.cnblogs.com/grandyang/p/7525821.html
博主第一眼看到这题,心想,我去,这不就是打牌么,什么挖坑,拐3,红桃4啊,3个起连,有时候排组合的好,就不用划单儿。这道题让我们将数组分割成多个连续递增的子序列,注意这里可能会产生歧义,实际上应该是分割成一个或多个连续递增的子序列,因为[1,2,3,4,5]也是正确的解。这道题就用贪婪解法就可以了,我们使用两个哈希表map,第一个map用来建立数字和其出现次数之间的映射freq,第二个用来建立可以加在某个连续子序列后的数字及其可以出现的次数之间的映射need。对于第二个map,举个例子来说,就是假如有个连,[1,2,3],那么后面可以加上4,所以就建立4的映射。这样我们首先遍历一遍数组,统计每个数字出现的频率,然后我们开始遍历数组,对于每个遍历到的数字,首先看其当前出现的次数,如果为0,则继续循环;如果need中存在这个数字的非0映射,那么表示当前的数字可以加到某个连的末尾,我们将当前数字的映射值自减1,然后将下一个连续数字的映射值加1,因为当[1,2,3]连上4后变成[1,2,3,4]之后,就可以连上5了;如果不能连到其他子序列后面,我们来看其是否可以成为新的子序列的起点,可以通过看后面两个数字的映射值是否大于0,都大于0的话,说明可以组成3连儿,于是将后面两个数字的映射值都自减1,还有由于组成了3连儿,在need中将末尾的下一位数字的映射值自增1;如果上面情况都不满足,说明该数字是单牌,只能划单儿,直接返回false。最后别忘了将当前数字的freq映射值自减1。退出for循环后返回true

X. https://leetcode.com/problems/split-array-into-consecutive-subsequences/discuss/106495/java-on-time-o1-space-solution-greedily-extending-shorter-subsequence
The basic idea is that, for each distinct element ele in the input array, we only need to maintain three pieces of information: the number of consecutive subsequences ending at ele with length of 1, length of 2 and length >= 3.
The input array will be scanned linearly from left to right. Let cur be the element currently being examined and cnt as its number of appearance. pre is the element processed immediately before cur. The number of consecutive subsequences ending at pre with length of 1, length of 2 and length >= 3 are denoted as p1p2 and p3, respectively. There are two cases in general:
  1. cur != pre + 1: for this case, cur cannot be added to any consecutive subsequences ending at pre, therefore, we must have p1 == 0 && p2 == 0; otherwise the input array cannot be split into consecutive subsequences of length >= 3. Now let c1, c2, c3 be the number of consecutive subsequences ending at cur with length of 1, length of 2 and length >= 3, respectively, we will have c1 = cnt, c2 = 0, c3 = 0, which means we only have consecutive subsequence ending at cur with length of 1 and its number given by cnt.
  2. cur == pre + 1: for this case, cur can be added to consecutive subsequences ending at pre and thus extend those subsequences. But priorities should be given to those with length of 1 first, then length of 2 and lastly length >= 3. Also we must have cnt >= p1 + p2; otherwise the input array cannot be split into consecutive subsequences of length >= 3. Again let c1, c2, c3 be the number of consecutive subsequences ending at cur with length of 1, length of 2 and length >= 3, respectively, we will have: c2 = p1, c3 = p2 + min(p3, cnt - (p1 + p2)), c1 = max(cnt - (p1 + p2 + p3), 0). The meaning is as follows: first adding cur to the end of subsequences of length 1 will make them subsequences of length 2, and we have p1 such subsequences, therefore c2 = p1. Then adding cur to the end of subsequences of length 2 will make them subsequences of length 3, and we have p2 such subsequences, therefore c3 is at least p2. If cnt > p1 + p2, we can add the remaining cur to the end of subsequences of length >= 3 to make them even longer subsequences. The number of such subsequences is the smaller one of p3 and cnt - (p1 + p2). In total, c3 = p2 + min(p3, cnt - (p1 + p2)). If cnt > p1 + p2 + p3, then we still have remaining cur that cannot be added to any subsequences. These residue cur will form subsequences of length 1, hence c1 = max(cnt - (p1 + p2 + p3), 0).
For either case, we need to update: pre = cur, p1 = c1, p2 = c2, p3 = c3 after processing the current element. When all the elements are done, we check the values of p1 and p2. The input array can be split into consecutive subsequences of length >= 3 if and only if p1 == 0 && p2 == 0.
Here is the O(n) time and O(1) space Java solution:
public boolean isPossible(int[] nums) {
    int pre = Integer.MIN_VALUE, p1 = 0, p2 = 0, p3 = 0;
    int cur = 0, cnt = 0, c1 = 0, c2 = 0, c3 = 0;
        
    for (int i = 0; i < nums.length; pre = cur, p1 = c1, p2 = c2, p3 = c3) {
        for (cur = nums[i], cnt = 0; i < nums.length && cur == nums[i]; cnt++, i++);
            
        if (cur != pre + 1) {
            if (p1 != 0 || p2 != 0) return false;
            c1 = cnt; c2 = 0; c3 = 0;
            
        } else {
            if (cnt < p1 + p2) return false;
            c1 = Math.max(0, cnt - (p1 + p2 + p3));
            c2 = p1;
            c3 = p2 + Math.min(p3, cnt - (p1 + p2));
        }
    }
    
    return (p1 == 0 && p2 == 0);
}


PS: in case you're curious, here is how I come up with this solution.
First note that if the array can be split into consecutive subsequences of length >= 3, then every element in the array must belong to such a subsequence of length >= 3. So let's take any integer m in the array as an example. Apparently you can only add it to consecutive subsequences ending at m - 1. This tells you that we need information about consecutive subsequences ending at m - 1. But what kind of information about those subsequences are relevant here?
Think about what distinguishes one subsequence from another. Since all the subsequences are ending at m - 1, they can only differ by length, i.e., each subsequence will be uniquely identified by its length. Now what if two subsequences have the same length? This suggests that we also need to keep track of the number of appearance of each length. Therefore in summary, we need to maintain information of subsequences ending at m - 1 with various lengths and their corresponding number of appearance.
Of course I know we cannot keep track of all lengths, as this would require an infinite amount of memory. So the next question is to ponder more carefully the role that the length of each subsequence is playing here. From the point view of m - 1, we care more about subsequences of length 1 and 2. Since if there are no other elements that can extend these subsequences, we know the input array cannot be split into consecutive subsequences of length >= 3. On the other hand, we don't really care about subsequences of length >= 3, since they already meet the required condition. So I conclude that for subsequences ending at m - 1, there are really only three types are needed: those of length 1, of length 2 and of length >= 3.


Once I figure this out, the next thing is to extend the conclusion to subsequences ending at m, so we can have a working recurrence relation. The key here is how we add m to those subsequences ending at m - 1. As I mentioned above, priorities should be given to those of length 1 and 2, since if these subsequences cannot be extended, the input array cannot be split into consecutive subsequences of length >= 3. After those subsequences are extended, if we have more elements of m, they can be used to extend subsequences ending at m - 1 with length >= 3. At last, if we still have elements of m, then they will form subsequences of length 1 since there are no more subsequences ending at m - 1 available for them. Therefore for subsequences ending at m, we still just need subsequences of length 1, length 2 and length >= 3. We can continue in this fashion until all elements are processed, at which point we need to check the number of subsequences of length 1 and 2 that ends at the last element of the input array to see if the input array can be split into consecutive subsequences of length >= 3 (since these subsequences cannot be extended any longer as there are no more elements).
http://bookshadow.com/weblog/2017/08/13/leetcode-split-array-into-consecutive-subsequences/
Map + PriorityQueue
思路类似于排列扑克牌,优先将数字排在长度较小的扑克牌队列后面
Map<Integer, PriorityQueue<Integer>> dmap的key为扑克牌队列的末尾,value为优先队列,存储扑克牌队列的长度
private HashMap<Integer, PriorityQueue<Integer>> dmap; public boolean isPossible(int[] nums) { dmap = new HashMap<>(); for (int num : nums) { PriorityQueue<Integer> pq0 = getOrPut(num - 1); int len = pq0.isEmpty() ? 0 : pq0.poll(); PriorityQueue<Integer> pq1 = getOrPut(num); pq1.offer(len + 1); } for (int key : dmap.keySet()) { for (int len : dmap.get(key)) { if (len < 3) return false; } } return true; } public PriorityQueue<Integer> getOrPut(int num) { PriorityQueue<Integer> pq = dmap.get(num); if (pq == null) { pq = new PriorityQueue<Integer>(); dmap.put(num, pq); } return pq; }
http://blog.csdn.net/u014688145/article/details/77148515
思路总结:把每个独立的片段先求出来,接着考虑如何拼凑在一起,因为只能往前拼凑,所以解是唯一的,模拟就好了。
此题相对较难,要抓住一些性质不容易,采用模拟,模拟的策略很有意思,首先按要求求出至少三个连续序列组成的所有情况,接着就是【拼接】了。
很有意思的是,在写代码时,先考虑拼接,最后才考虑是否单独(即分桶摆放)摆放。
像此题,我的思路是从频次最大的那个数开始着手,先假设某个数num的频次最大,因此,一定会从num开始,找到num+1,和num+2,组成至少三个序列的情况,如果不存在则可以直接返回false。
那么问题来了,num+1,num+2的频次一定小于等于num,遇到不能分配的情况该怎么办?啥时候是不能分配的时候?
条件: num 还存在待分配的名额时,num+2的个数为0
所以不管num + 1是否被分配完毕,我们都需要将剩余的num 和 num + 1拼接到之前的某个桶的结尾处。
如果找不到这样的桶,同样返回false,否则拼接后,更新桶的最后一个元素。(map实现)
拼谁?一定是num,拼完num,该桶下一步就应该拼num+1的元素,此时再把num+1放进去,两步完成了num和num+1的拼接,完美。

public boolean isPossible(int[] nums) { TreeMap<Integer, Integer> map = new TreeMap<>(); Map<Integer, Integer> append = new HashMap<>(); for (int num : nums) map.put(num, map.getOrDefault(num, 0) + 1); for (int i : nums){ if (map.get(i) == 0) continue; if (append.getOrDefault(i, 0) > 0){ // 先拼接 append.put(i, append.get(i) - 1); append.put(i + 1, append.getOrDefault(i + 1, 0) + 1); } else if (map.getOrDefault(i + 1, 0) > 0 && map.getOrDefault(i + 2, 0) > 0){ // 再独立 map.put(i + 1, map.get(i + 1) - 1); map.put(i + 2, map.get(i + 2) - 1); append.put(i + 3, append.getOrDefault(i + 3, 0) + 1); } else return false; map.put(i, map.get(i) - 1); } return true; }

X. http://blog.jerkybible.com/2017/08/19/LeetCode-659-Split-Array-into-Consecutive-Subsequences/
首先进行参数的校验,只要数组为空或者数组的大小小于3直接返回false
然后我们进行拆分。新建一个ListA,里面包含所有拆分结果。从数组的第一个数字开始遍历:
  • 如果A为空,则新建一个列表B1将这个数字放进去,然后将B1放入A
  • 如果A不为空则遍历A中的每一个串,如果一个串的最后一个数字为当前数字减1并且这个串的大小小于3,则放入这个串中
  • 如果满足最后一个数字为当前数字减1的串的大小都是大于等于3,则将这个数字放入第一个满足条件的串后
  • 当这个串的最后一个数字小于当前数字减1并且串大小小于3直接返回false,这是因为给定的数组的是有序的,那么这个串永远不会满足条件
  • 如果以上都没有进行处理则新建一个列表B2将这个数字放进去,然后将B2放入A
最后轮询A中的每一个列表,是否满足条件。满足返回true,不满足返回false
public boolean isPossible(int[] nums) {
if (nums == null || nums.length < 3) {
return false;
}
List<List<Integer>> subsequences = new ArrayList<>();
for (int num : nums) {
if (subsequences.isEmpty()) {
List<Integer> subsequence = new ArrayList<>();
subsequence.add(num);
subsequences.add(subsequence);
} else {
boolean inserted = false;
List<Integer> firstSubsequence = null;
for (List<Integer> subsequence : subsequences) {
if (subsequence.get(subsequence.size() - 1) == num - 1) {
if(null == firstSubsequence){
firstSubsequence = subsequence;
}
if(subsequence.size() < 3) {
subsequence.add(num);
inserted = true;
break;
}
}
if (subsequence.get(subsequence.size() - 1) < num - 1 && subsequence.size() < 3){
return false;
}
}
if (!inserted && firstSubsequence != null) {
firstSubsequence.add(num);
inserted = true;
}
if (!inserted) {
List<Integer> subsequence = new ArrayList<>();
subsequence.add(num);
subsequences.add(subsequence);
}
}
}
for (List<Integer> subsequence : subsequences) {
if (subsequence.size() < 3) {
return false;
}
}
return true;
}


Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts