/// <summary>
        /// Leetcode 721
        /// https://leetcode.com/problems/accounts-merge/
        /// </summary>
        /// <param name="accounts"></param>
        /// <returns></returns>
        public static IList <IList <string> > AccountsMerge(IList <IList <string> > accounts)
        {
            var emailSet = getAllEmails(accounts);

            // https://leetcode.com/problems/accounts-merge/discuss/164699/C-Solution-(Union-Find)-beats-91.49
            var emailList = emailSet.ToList();

            var ordCmp = StringComparer.Ordinal;

            emailList.Sort(ordCmp);

            var emailNameMap = getEmailNameMap(accounts);
            var emailIdmap   = new Dictionary <string, int>();

            int index       = 0;
            var emailLookup = new List <string>();

            foreach (var item in emailList)
            {
                emailIdmap.Add(item, index);
                index++;
                emailLookup.Add(item);
            }

            var unionFind = new QuickUnion(emailSet.Count);

            unionFind.EmailIdMap = emailIdmap;

            foreach (var list in accounts)
            {
                for (int i = 1; i < list.Count - 1; i++)
                {
                    var email1 = list[i];
                    var email2 = list[i + 1];

                    var connected = unionFind.Connected(email1, email2);
                    if (!connected)
                    {
                        unionFind.Union(email1, email2);
                    }
                }
            }

            // flat the tree
            for (int i = 0; i < emailSet.Count; i++)
            {
                unionFind.QuickFindAndPathCompression(i);
            }

            var dict   = new Dictionary <int, List <string> >();
            var parent = unionFind.GetParent();

            for (int i = 0; i < parent.Length; i++)
            {
                var key = parent[i];
                if (!dict.ContainsKey(key))
                {
                    dict.Add(key, new List <string>());
                }

                dict[key].Add(emailLookup[i]);
            }

            // output a list
            var result = new List <IList <string> >();

            foreach (var item in dict.Keys)
            {
                var list  = new List <string>();
                var email = emailLookup[item];
                var name  = emailNameMap[email];

                list.Add(name);

                var values = dict[item];
                list.AddRange(values);

                result.Add(list);
            }

            return(result);
        }