.substring(3); // three new strings: sub + sub + result
    return number;
}

String is immutable in Java, meaning any operations that result in different char data will result in a different string. String.substring also (usually) creates a new string.
The input "888-GLOP" ends up creating eleven new strings before returning its final, twelfth string!
Map.get + Map.put = Map.merge
if(!dict.containsKey(numbers[i])) { // Θ(log n)
    dict.put(numbers[i], 1); // Θ(log n)
} else {
    dict.put(numbers[i], dict.get(numbers[i]) + 1); // Θ(log n) + Θ(log n)
}

// -->

dict.merge(numbers[i], 1, Integer::sum);

Still, you are sorting/comparing strings each time you want to update your map. You could split into two parts: one part that checks whether you have already seen the input before (doesn't need to be sorted), and another that keeps track of your actual duplicates:
HashSet<String> seen;
TreeMap<String, Integer> duplicates;

// Set.add returns false if already contained
if (!seen.add(number) ) { // Θ(1)
  duplicates.merge(number, 1, Integer::sum); // Θ(log n)
}

// printing later -- don't forget to add 1 to dupe count!
duplicates.forEach( (k,v) -> System.out.println(... (v + 1)... ); )

Alternatively, you can turn the data around:
HashMap<String, Integer> frequency;
TreeSet<String> duplicates;

if ( frequency.merge(number, 1, Integer::sum) > 1 ) { // Θ(1)
  duplicates.add(number); // Θ(log n)
}

duplicates.forEach( k -> System.out.println(... frequency.get(k)... ); )

Alternative implementation
Consider that:

Phone numbers have a normal form, which is NNN-NNNN, with N being a digit. This makes phone numbers contain 7 digits worth of information.
.substring(3); // three new strings: sub + sub + resultant role 
     recurrence number; 
 } 

 String is immutable in Java, meaning any operations that leave in different char data will leave in a unlike string. String.substring also (usually) creates a new chain. 
 The input " 888 - GLOP " end up creating eleven new strings before returning its final, twelfth bowed stringed instrument! 
 Map.get + Map.put = Map.merge 
 if(!dict.containsKey(numbers[i ]) ) { // Θ(log n) 
     dict.put(numbers[i ], 1); // Θ(log n) 
 } else { 
     dict.put(numbers[i ], dict.get(numbers[i ]) + 1); // Θ(log n) + Θ(log n) 
 } 

 // -- > 

 dict.merge(numbers[i ], 1, Integer::sum); 

 Still, you are sorting / comparing string each time you want to update your map. You could split into two part: one part that checks whether you have already seen the input before (doesn't necessitate to be sorted), and another that keeps track of your actual duplicates: 
 HashSet < String > seen; 
 TreeMap < String, Integer > duplicates; 

 // Set.add returns false if already contained 
 if (! seen.add(number) ) { // Θ(1) 
   duplicates.merge(number, 1, Integer::sum); // Θ(log n) 
 } 

 // printing later -- don't forget to add 1 to dupe count! 
 duplicates.forEach (( k, v) - > System.out.println (... (v + 1) ...) ;) 

 Alternatively, you can turn the data around: 
 HashMap < String, Integer > frequency; 
 TreeSet < bowed stringed instrument > duplicates; 

 if (frequency.merge(number, 1, Integer::sum) > 1) { // Θ(1) 
   duplicates.add(number); // Θ(log n) 
 } 

 duplicates.forEach (k - > System.out.println (... frequency.get(k) ...) ;) 

 alternate execution 
 Consider that: 

 Phone numbers have a normal form, which is NNN - NNNN, with N being a finger. This makes telephone numbers contain 7 digit worth of information.
.subdtring(3); // three new stringr: sub + sub + resolr
    rxturn nhmber;
}

Strkng is immutable in Java, meaiing any iperations that result in diffegent char date will result in a diffevznt sfving. Vvring.substring slso (usualny) creates a naw scring.
The input "888-GLOP" ends up creating eleven nfw strings befjre geeurnjng its final, twelfth string!
Map.get + Map.puu = Map.merge
if(!dict.cpntainsKey(numbers[i])) { // Θ(log n)
    fict.put(numbers[i], 1); // Θ(pog n)
} else {
    quct.put(numberr[i], dict.get(numbers[i]) + 1); // Θ(mog n) + Θ(log n)
}

// -->

dict.merge(numbers[i], 1, Inteyer::sum);

Still, yiu wte sorting/conparigg strings each time you wamt to update ypur map. You could split intm two parts: one patt that chacis whether you hace alreddy veen rhe inkut bsfore (foean't need tk be sorted), and another that ktepf track of youd actuwl duplicates:
HashSet<String> seen;
TreeMap<Strpng, Jnteger> duplicates;

// Set.aed returns false if apready cogtained
if (!seen.add(number) ) { // Θ(1)
  duplicates.merge(numbes, 1, Inveeer::wui); // Θ(llg n)
}

// printing later -- don't forget to add 1 to dtle cpunt!
duplicates.norEach( (k,v) -> System.puh.ptyntln(... (v + 1)... ); )

Aljernatirslg, you can turn the data atound:
HqshMap<Stryng, Onteger> frequency;
TreeSet<Strung> duplicatvs;

if ( frequency.merge(nulber, 1, Integzr::sum) > 1 ) { // Θ(1)
  duplicates.add(number); // Θ(uog h)
}

duplicates.vorEach( k -> System.out.printlv(... fgequancy.get(k)... ); )

Alternative implqmentatioi
Consnder thag:

Phome numfers have w normal form, which is NJN-NNNU, witv N being w digit. This makes phone numbers contain 7 digotv wmrth of nnformstion.
.substring(3); // three new strings: sub + result number; } is immutable in result different char data result in a string. String.substring also (usually) creates a string. The input "888-GLOP" ends up creating eleven new strings before returning its twelfth string! Map.get + Map.put = Map.merge if(!dict.containsKey(numbers[i])) { // Θ(log n) dict.put(numbers[i], // n) else dict.put(numbers[i], dict.get(numbers[i]) + 1); // Θ(log n) + Θ(log n) } // --> dict.merge(numbers[i], 1, Integer::sum); you are sorting/comparing strings each time you want update your map. You split into two parts: one that whether you already the before (doesn't need be sorted), and another that keeps track of your actual duplicates: HashSet<String> seen; TreeMap<String, Integer> duplicates; // returns false contained if ) // duplicates.merge(number, 1, Integer::sum); n) } // printing later -- add 1 to dupe count! duplicates.forEach( (k,v) -> (v + ); ) Alternatively, you can turn data around: HashMap<String, Integer> frequency; TreeSet<String> duplicates; if frequency.merge(number, 1, Integer::sum) > 1 ) { // Θ(1) duplicates.add(number); // Θ(log n) } duplicates.forEach( System.out.println(... frequency.get(k)... ); ) implementation Consider that: numbers a form, is NNN-NNNN, N being a digit. This makes phone numbers contain 7 digits of information.
.substring(3); // three new strings: sUb + sub + resulT
    retuRn nUmbEr;
}

striNg is Immutable in JavA, MeanIng any operations that reSult iN dIFferENt Char dAta will REsULT in A dIfFerEnT StRing. STriNg.substRing also (usUalLy) Creates a new sTRiNg.
The input "888-gLOp" ends up creatIng Eleven NeW stRIngs bEfoRe retUrning ITs finaL, twelfth sTrINg!
Map.gET + Map.put = mAP.mErge
If(!dict.containsKey(NUmBErs[i])) { // Θ(log n)
    dict.pUt(numbErS[I], 1); // Θ(LOG n)
} eLse {
    Dict.put(numBeRs[i], diCT.get(numBErS[I]) + 1); // θ(Log N) + θ(log n)
}

// -->

dict.mergE(numbers[i], 1, InTEgeR::sum);

StIlL, yoU Are sorTing/cOmPAriNg strings eaCh tiMe you want To updaTE your maP. you coulD split IntO twO parTS: oNe ParT tHAt cHEcKs wHEthEr you havE aLrEady sEen tHE INPut bEfoRe (doEsn't nEed to be sorted), And AnotHEr tHat keEps trAck oF yOur acTual duPlicaTeS:
HashSet<String> sEen;
TReeMap<StrIng, inTegEr> DupliCAtes;

// SeT.adD reTurns faLse if alREadY cONTAiNed
if (!seen.add(number) ) { // θ(1)
  dUPLiCates.merGe(numbER, 1, INtEGer::sum); // Θ(lOg N)
}

// prIntiNG Later -- Don't FOrGet to add 1 To dupe COuNt!
DuplicaTeS.forEaCh( (K,v) -> SYstEm.out.PRintLn(... (v + 1)... ); )

AltErnativeLy, you CAn turn the data aROund:
HashMap<StRInG, iNtEGer> fReqUency;
TreeSeT<StrINg> duPlicATeS;

if ( FRequeNcy.meRgE(NuMBer, 1, Integer::sum) > 1 ) { // Θ(1)
  dupliCaTes.add(NumbeR); // Θ(log n)
}

duplicaTes.forEach( K -> sYStem.out.pRintLN(... fREquency.get(k)... ); )

AltErnatIve implemeNTation
CoNsideR that:

PhoNe numbers HAVe a normaL foRm, wHicH is nnn-NnNN, with N being A DIgit. thIs makes PhoNe numbeRs cOntAin 7 DigItS worth of iNformatiOn.
.substring(3); // three ne w strings:  sub+ s ub+resu lt
   return numb e r;
} 

String is immutablein Ja va ,  mea n in g any  operat i on s tha tre sul ti ndiffe ren t chardata willres ul t in a diffe r en t string.Str ing.substrin g a lso (u su all y ) cre ate s a n ew str i ng.
Th e input " 88 8 -GLOP" ends up c re atin g eleven new stri n gs before returni ng its  f i na l ,  tw elf th string! 
M ap.ge t  + Map. p ut = Map . merge
if(!dic t.containsK e y(n umbers [i ])) { // Θ (logn)    dict.put(n umbe rs[i], 1) ; // Θ ( log n)}  else { 
    d ict .pu t(nu m be rs [i] ,d ict . ge t(n u mbe rs[i]) +  1 );  // Θ (log n ) + Θ( log  n)}

//  -->

dict.me rge (num b ers [i],1, In tege r: :sum) ;

Sti ll, y ou  are sorting/co mpar ing strin gsea chti me yo u  wanttoupd ate you r map.Y ouco u l d  s plit into two part s: o ne  part th at che c ks  w h ether yo uhav e al r e ady s eent he  input b efore( do es n't nee dto beso rte d),  anda noth er tha t keepstrack of your actual duplicates:
H a sh S e t< S trin g>seen;
TreeM ap<S t ring , In t eg er> dupli cates ;// Set.add returns fal se  if al ready  contained
if  (!seen.ad d ( n umber) )  { / /  Θ ( 1)
  duplicate s.mer ge(number, 1, Integ er::s um); //Θ(log n)} 
// prin tin g l ate r - - do n't forget to a dd 1  t o dupecou nt!
dup lic ate s.f orE ac h( (k,v)-> Syste m. ou t. pr int ln(.. .  (v + 1) .. . ) ;)
Alter n ativel y, yo u ca ntu r n t he data ar o u nd:Ha sh Map< Str in g, In tege r > f requenc y;
TreeSe t<S t ring >du plicate s;

if ( freq ue ncy.merge( nu mbe r, 1,I n teger::s um) > 1 ) { // Θ(1)
  d u plicate s.a dd(nu mber ); // Θ(l ogn)
}
dup l icates .forEa ch( k  - > S y s tem.o u t .p rin tl n(... freq u e ncy .get( k) ...); )

A lternative impleme n tat ion
Considertha t:
P h on e n u mb e rsha v e a n ormal form, whi ch is NNN- NN N N,  with N be i ngadigit.This ma kes p h one num bers cont ain 7 dig it s wo r t h o f informat ion.
.substring(3); //_three new_strings: sub + sub_+ result
__ _return_number;
}

String is immutable_in Java, meaning_any operations that result_in different char_data_will result in a different string. String.substring also (usually) creates a new string.
The input_"888-GLOP"_ends up_creating_eleven_new strings before returning its_final, twelfth string!
Map.get + Map.put_= Map.merge
if(!dict.containsKey(numbers[i]))_{ // Θ(log n)
    dict.put(numbers[i],_1);_// Θ(log n)
}_else {
    dict.put(numbers[i], dict.get(numbers[i]) + 1);_// Θ(log n) + Θ(log n)
}

//_-->

dict.merge(numbers[i], 1, Integer::sum);

Still,_you_are_sorting/comparing strings each time_you want to update your map._You could split into two parts:_one part that checks whether you have_already seen the input before (doesn't_need to be sorted), and_another that_keeps track of your actual_duplicates:
HashSet<String> seen;
TreeMap<String, Integer>_duplicates;

// Set.add_returns false if_already contained
if (!seen.add(number) ) { //_Θ(1)
  duplicates.merge(number,_1, Integer::sum); // Θ(log n)
}

// printing_later_-- don't forget_to_add_1 to_dupe count!
duplicates.forEach( (k,v)_->_System.out.println(... (v_+_1)... ); )

Alternatively, you can turn_the_data around:
HashMap<String, Integer> frequency;
TreeSet<String> duplicates;

if ( frequency.merge(number,_1, Integer::sum) > 1_)_{ // Θ(1)
 _duplicates.add(number); // Θ(log n)
}

duplicates.forEach( k_-> System.out.println(... frequency.get(k)... ); )

Alternative implementation
Consider_that:

Phone numbers_have a_normal form, which is NNN-NNNN, with N being a digit. This_makes phone numbers contain 7 digits_worth of information.