TopCoder: SmartWordToy BFS


https://community.topcoder.com/stat?c=problem_statement&pm=3935&rd=6532

Problem Statement

    The toy company "I Can't Believe It Works!" has hired you to help develop educational toys. The current project is a word toy that displays four letters at all times. Below each letter are two buttons that cause the letter above to change to the previous or next letter in alphabetical order. So, with one click of a button the letter 'c' can be changed to a 'b' or a 'd'. The alphabet is circular, so for example an 'a' can become a 'z' or a 'b' with one click.



In order to test the toy, you would like to know if a word can be reached from some starting word, given one or more constraints. A constraint defines a set of forbidden words that can never be displayed by the toy. Each constraint is formatted like "X X X X", where each X is a string of lowercase letters. A word is defined by a constraint if the ith letter of the word is contained in the ith X of the contraint. For example, the constraint "lf a tc e" defines the words "late", "fate", "lace" and "face".



You will be given a String start, a String finish, and a String[] forbid. Calculate and return the minimum number of button presses required for the toy to show the word finish if the toy was originally showing the word start. Remember, the toy must never show a forbidden word. If it is impossible for the toy to ever show the desired word, return -1.
 

Definition

    
Class:SmartWordToy
Method:minPresses
Parameters:String, String, String[]
Returns:int
Method signature:int minPresses(String start, String finish, String[] forbid)
(be sure your method is public)
    
 

Constraints

-start and finish will contain exactly four characters.
-start and finish will contain only lowercase letters.
-forbid will contain between 0 and 50 elements, inclusive.
-Each element of forbid will contain between 1 and 50 characters.
-Each element of forbid will contain lowercase letters and exactly three spaces.
-Each element of forbid will not contain leading, trailing or double spaces.
-Each letter within a group of letters in each element of forbid will be distinct. Thus "aa a a a" is not allowed.
-start will not be a forbidden word.
 

Examples

0)
    
"aaaa"
"zzzz"
{"a a a z", "a a z a", "a z a a", "z a a a", "a z z z", "z a z z", "z z a z", "z z z a"}
Returns: 8
1)
    
"aaaa"
"bbbb"
{}
Returns: 4
Simply change each letter one by one to the following letter in the alphabet.
2)
    
"aaaa"
"mmnn"
{}
Returns: 50
Just as in the previous example, we have no forbidden words. Simply apply the correct number of button presses for each letter and you're there.
3)
    
"aaaa"
"zzzz"
{"bz a a a", "a bz a a", "a a bz a", "a a a bz"}
Returns: -1
Here is an example where it is impossible to go to any word from "aaaa".
4)
    
"aaaa"
"zzzz"
{"cdefghijklmnopqrstuvwxyz a a a", 
 "a cdefghijklmnopqrstuvwxyz a a", 
 "a a cdefghijklmnopqrstuvwxyz a", 
 "a a a cdefghijklmnopqrstuvwxyz"}
Returns: 6
5)
    
"aaaa"
"bbbb"
{"b b b b"}
Returns: -1
http://code.antonio081014.com/2014/03/topcoder-srm-233-div1-level2.html
 * For this problem, it's easy to find out each 4-digit string is a state. And
 * it's easy to move to next state from current state by going to next
 * alpha-letter or previous alpha-letter. So here, using BFS to track the state
 * to state process, and find out the shortest distance from start to finish
 * state.
 * 
 * PS: initially, I used Map interface to keep the record of state's distance
 * from start, it will Exceed Time Limit. Then, I used a 4-dim array to keep
 * track of the state's distance. It works like a charm now. I think it's the
 * getter of Map interface costs too much time since I instantized with a
 * linkedlist, each time the iterator will go through every element in the map
 * to check the value of the key.
 // Map<String, Integer> map;
 int[][][][] count;

 public int minPresses(String start, String finish, String[] forbid) {
  // map = new HashMap<String, Integer>();
  count = new int[26][26][26][26];
  // map.put(start, 0);
  for (int i = 0; i < 26; i++)
   for (int j = 0; j < 26; j++)
    for (int m = 0; m < 26; m++)
     for (int n = 0; n < 26; n++)
      count[i][j][m][n] = -1;
  preprocess(forbid);
  setCounter(start, 0);
  Queue<String> q = new LinkedList<String>();
  q.add(start);
  while (!q.isEmpty()) {
   start = q.poll();
   if (start.compareTo(finish) == 0)
    return getCounter(start);
   int value = getCounter(start);
   for (int i = 0; i < 4; i++) {
    String next = nextString(start, i);
    if (getCounter(next) == -1) {
     setCounter(next, value + 1);
     // map.put(next, map.get(start) + 1);
     q.add(next);
    }
    String prev = prevString(start, i);
    if (getCounter(prev) == -1) {
     setCounter(prev, value + 1);
     q.add(prev);
    }
   }
  }
  return -1;
 }

 private void setCounter(String s, int value) {
  count[s.charAt(0) - 'a'][s.charAt(1) - 'a'][s.charAt(2) - 'a'][s
    .charAt(3) - 'a'] = value;
 }

 private int getCounter(String s) {
  return count[s.charAt(0) - 'a'][s.charAt(1) - 'a'][s.charAt(2) - 'a'][s
    .charAt(3) - 'a'];
 }

 private void preprocess(String[] forbid) {
  for (String s : forbid) {
   String[] str = s.split("\\s");
   for (int i = 0; i < str[0].length(); i++) {
    for (int j = 0; j < str[1].length(); j++) {
     for (int m = 0; m < str[2].length(); m++) {
      for (int n = 0; n < str[3].length(); n++) {
       // String tmp = "" + str[0].charAt(i)
       // + str[1].charAt(j) + str[2].charAt(m)
       // + str[3].charAt(n);
       // map.put(tmp, -1);
       count[str[0].charAt(i) - 'a'][str[1].charAt(j) - 'a'][str[2]
         .charAt(m) - 'a'][str[3].charAt(n) - 'a'] = -10;
      }
     }
    }
   }
  }
 }

 private String nextString(String s, int index) {
  return s.substring(0, index)
    + (char) ('a' + (s.charAt(index) - 'a' + 1) % 26)
    + s.substring(index + 1);
 }

 private String prevString(String s, int index) {
  return s.substring(0, index)
    + (char) ('a' + (s.charAt(index) - 'a' + 25) % 26)
    + s.substring(index + 1);
 }
http://www.cnblogs.com/lautsie/p/3263700.html
广度搜索BFS,要用Queue。还不是很熟,这道题帮助理清一些思绪了。其实这道题是求最短路径,所以BFS遇到第一个就可以返回了,所以后面有些现有大小和历史大小的判断可以省却。
过程中拿数组存step还是很有用的。其他的解法中我看到有把四位char编码返回整数的a*26*26*26+b*26*26+c*26+d,很不错,本质就是26进制的数。
    public int minPresses(String start, String finish, String[] forbid) {
        int[][][][] step = new int[26][26][26][26];
        int[][][][] forb = new int[26][26][26][26];
        for (int a = 0; a < 26; a++)
        for (int b = 0; b < 26; b++)
        for (int c = 0; c < 26; c++)
        for (int d = 0; d < 26; d++)
        {
            step[a][b][c][d] = -1;
            forb[a][b][c][d] = 0;
        }
         
        for (int i = 0; i < forbid.length; i++) {
            String[] lines = forbid[i].split(" ");
            for (int a = 0; a < lines[0].length(); a++) {
                for (int b = 0; b < lines[1].length(); b++) {
                    for (int c = 0; c < lines[2].length(); c++) {
                        for (int d = 0; d < lines[3].length(); d++) {
                            forb[lines[0].charAt(a)-'a'][lines[1].charAt(b)-'a'][lines[2].charAt(c)-'a'][lines[3].charAt(d)-'a'] = 1;
                        }
                    }
                }
            }
        }
         
        LinkedList<Word> queue = new LinkedList<Word>();
        Word sw = new Word(start.charAt(0), start.charAt(1), start.charAt(2), start.charAt(3));
        step[sw.a-'a'][sw.b-'a'][sw.c-'a'][sw.d-'a'] = 0;
        queue.offer(sw);
         
        Word fw = new Word(finish.charAt(0), finish.charAt(1), finish.charAt(2), finish.charAt(3));
        while (queue.size() != 0) {
            Word w = queue.poll();
            int cur_step = step[w.a-'a'][w.b-'a'][w.c-'a'][w.d-'a'];
            if (w.a == fw.a && w.b == fw.b && w.c == fw.c && w.d == fw.d) return cur_step;
             
            Word tmp = new Word();
            tmp.a = next(w.a); tmp.b = w.b; tmp.c = w.c; tmp.d = w.d;
            int tmp_step = step[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'];
            if (forb[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'] == 0 && (tmp_step == -1 || tmp_step > cur_step+1)) {
                step[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'] = cur_step + 1;
                queue.offer(tmp);
            }
            tmp = new Word();
            tmp.a = prev(w.a); tmp.b = w.b; tmp.c = w.c; tmp.d = w.d;
            tmp_step = step[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'];
            if (forb[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'] == 0 && (tmp_step == -1 || tmp_step > cur_step+1)) {
                step[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'] = cur_step + 1;
                queue.offer(tmp);
            }
            tmp = new Word();
            tmp.a = w.a; tmp.b = next(w.b); tmp.c = w.c; tmp.d = w.d;
            tmp_step = step[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'];
            if (forb[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'] == 0 && (tmp_step == -1 || tmp_step > cur_step+1)) {
                step[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'] = cur_step + 1;
                queue.offer(tmp);
            }
            tmp = new Word();
            tmp.a = w.a; tmp.b = prev(w.b); tmp.c = w.c; tmp.d = w.d;
            tmp_step = step[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'];
            if (forb[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'] == 0 && (tmp_step == -1 || tmp_step > cur_step+1)) {
                step[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'] = cur_step + 1;
                queue.offer(tmp);
            }
            tmp = new Word();
            tmp.a = w.a; tmp.b = w.b; tmp.c = next(w.c); tmp.d = w.d;
            tmp_step = step[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'];
            if (forb[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'] == 0 && (tmp_step == -1 || tmp_step > cur_step+1)) {
                step[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'] = cur_step + 1;
                queue.offer(tmp);
            }
            tmp = new Word();
            tmp.a = w.a; tmp.b = w.b; tmp.c = prev(w.c); tmp.d = w.d;
            tmp_step = step[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'];
            if (forb[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'] == 0 && (tmp_step == -1 || tmp_step > cur_step+1)) {
                step[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'] = cur_step + 1;
                queue.offer(tmp);
            }
            tmp = new Word();
            tmp.a = w.a; tmp.b = w.b; tmp.c = w.c; tmp.d = next(w.d);
            tmp_step = step[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'];
            if (forb[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'] == 0 && (tmp_step == -1 || tmp_step > cur_step+1)) {
                step[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'] = cur_step + 1;
                queue.offer(tmp);
            }
            tmp = new Word();
            tmp.a = w.a; tmp.b = w.b; tmp.c = w.c; tmp.d = prev(w.d);
            tmp_step = step[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'];
            if (forb[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'] == 0 && (tmp_step == -1 || tmp_step > cur_step+1)) {
                step[tmp.a-'a'][tmp.b-'a'][tmp.c-'a'][tmp.d-'a'] = cur_step + 1;
                queue.offer(tmp);
            }
        }
        return step[finish.charAt(0)-'a'][finish.charAt(1)-'a'][finish.charAt(2)-'a'][finish.charAt(3)-'a'];
    }
     
    private char next(char c) {
        if (c == 'z'return 'a';
        else return (char)(c+1);
    }
     
    private char prev(char c) {
        if (c == 'a'return 'z';
        else return (char)(c-1);
    }
}
class Word
{
    char a;
    char b;
    char c;
    char d;
     
    public Word(char _a, char _b, char _c, char _d) {
        a = _a; b = _b; c = _c; d = _d;
    }
     
    public Word() {}
}
https://github.com/irpap/TopCoder/blob/master/SmartWordToy/src/SmartWordToy.java
    public int minPresses(String start, String finish, String[] forbid) {
        Node.initForbidden(forbid);
        Node s = new Node(start.toCharArray());
        //        System.out.println(s.neighbors());
        return bfs(s, finish);
    }

    private int bfs(Node s, String finish) {
        HashSet<String> visited = new HashSet<String>();

        LinkedList<Node> q = new LinkedList<Node>();
        q.add(s);
        while (q.isEmpty() == false) {
            Node top = q.getFirst();
            q.removeFirst();
            //            System.out.println(top);
            visited.add(top.toString());
            if (top.toString().equals(finish)) return top.steps;
            for (Node neighbor : top.neighbors()) {
                if (!visited.contains(neighbor.toString())) {
                    neighbor.steps = top.steps + 1;
                    q.add(neighbor);
                }
            }
        }
        return -1;
    }

    static class Node {
        public static final char A = 'a';
        int steps = 0;
        static boolean[][][][] forbidden;

        private static void initForbidden(final String[] forbid) {
            boolean[][][][] forbidden = new boolean[26][26][26][26];

            for (String s : forbid) {
                String[] split = s.split(" ");
                for (int i = 0; i < split[0].length(); i++) {
                    for (int j = 0; j < split[1].length(); j++) {
                        for (int k = 0; k < split[2].length(); k++) {
                            for (int l = 0; l < split[3].length(); l++) {
                                forbidden[split[0].charAt(i) - A][split[1].charAt(j) - A][split[2].charAt(k) - A][split[3].charAt(l) - A] = true;
                            }
                        }
                    }
                }
            }


            Node.forbidden = forbidden;
        }

        Node(char[] word) {
            this.word = word;
        }

        char[] word = new char[4];

        public String toString() {
            return new String(word);
        }

        List<Node> neighbors() {
            HashSet<Node> neighbors = new HashSet<Node>();

            for (int i = 0; i < word.length; i++) {
                char[] prev = createWordWithNextLetter(i);
                addToNeighborsIfNotForbidden(neighbors, prev);
            }

            for (int i = 0; i < word.length; i++) {
                char[] next = createWordWithPrevLetter(i);

                addToNeighborsIfNotForbidden(neighbors, next);
            }

            return new LinkedList(neighbors);
        }

        private void addToNeighborsIfNotForbidden(HashSet<Node> neighbors, char[] next) {
            if (isForbidden(next) == false) {
                neighbors.add(new Node(next));
            }
        }

        private char[] createWordWithPrevLetter(int i) {
            char[] next = new char[4];

            for (int j = 0; j < word.length; j++) {
                next[j] = (i == j) ? next(word[j]) : word[j];
            }
            return next;
        }

        private char[] createWordWithNextLetter(int i) {
            char[] prev = new char[4];

            for (int j = 0; j < word.length; j++) {
                prev[j] = (i == j) ? prev(word[j]) : word[j];
            }
            return prev;
        }

        private boolean isForbidden(char[] word) {
            return forbidden[word[0] - A][word[1] - A][word[2] - A][word[3] - A];
        }


        private char next(char c) {
            if (c == 'z') return 'a';
            return (char) (c + 1);
        }

        private char prev(char c) {
            if (c == 'a') return 'z';
            return (char) (c - 1);
        }
    }
https://code.google.com/p/my-topcoder/source/browse/trunk/topcoder/SmartWordToy.java?spec=svn11&r=11

https://github.com/irpap/TopCoder/blob/master/SmartWordToy/src/SmartWordToy.java
https://github.com/ahmed-fathy-aly/TopCoder/blob/master/smartwordtoy-java/SmartWordToy.java

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