Shorter than a Split Second!

APL (Dyalog 14) (31)

{1↓¨(1,(⍳⍴⍵)∊2⍳⍨¨↓+\∘.=⍨⍵)⊂0,⍵}

This is a function that takes an array and returns a nested array.

Test:

      +V← {1↓¨(1,(⍳⍴⍵)∊2⍳⍨¨↓+\∘.=⍨⍵)⊂0,⍵} 2 1 1 2 3 2 2 4 5 6 7 3 7 0 5
┌───┬┬─────────────┬┬─┬┐
│2 1││3 2 2 4 5 6 7││0││
└───┴┴─────────────┴┴─┴┘
      ⍝ this return value is a real nested array:
      ⎕←'Length: ',⍴V ⋄ (⍳⍴V){⎕←'Array #',⍺,': (', ⍵, ')'}¨V 
Length:  6
Array # 1 : ( 2 1 )
Array # 2 : ()
Array # 3 : ( 3 2 2 4 5 6 7 )
Array # 4 : ()
Array # 5 : ( 0 )
Array # 6 : ()

Explanation:

  • 0,⍵: Add a 0 to the front of , for easier processing. (It does not count as an occurrence.)
  • (...)⊂: Split the array according to the given bitmask. A new group starts at each 1 in the bitmask.
    • +\∘.=⍨⍵: for each value in (the original) , find all occurrences in . Then make a running sum for each value, giving a square matrix showing for each position in how many of each value have already occurred.
    • : Split the matrix by its rows, giving for each value an array showing the amount of times it has occurred by each position.
    • 2⍳⍨¨: In each of these arrays, find the index of the first 2.
    • (⍳⍴⍵)∊: For each possible index into , see if it is contained in the list of indices of second occurrences. (These start each group, except the first one.)
    • 1,: Add an 1 to the front, marking the start of the first group.
  • 1↓¨: Remove the first element from each group. (These are the added 0, and the second occurrence of each value.)

J, 28 24 char

Special thanks to randomra.

(1&,<;._1~1,2=+/@(={:)\)

It works like this. Over all prefixes (\) of the input array, we look at how many (+/@) elements of the prefix are equal to the last element (={:) of that prefix. When this number is 2, we know this is the second occurrence of that item in the array, so we split the array there using <;._1.

   a=.2 1 1 2 3 2 2 4 5 6 7 3 7 0 5
   (={:)\ a
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0 0 0 0 0 0
0 1 1 0 0 0 0 0 0 0 0 0 0 0 0
1 0 0 1 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0 0 0 0 0 0
1 0 0 1 0 1 0 0 0 0 0 0 0 0 0
1 0 0 1 0 1 1 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
0 0 0 0 1 0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0 0 0 1 0 1 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 1 0 0 0 0 0 1
   +/@(={:)\ a
1 1 2 2 1 3 4 1 1 1 1 2 2 1 2

Old thing using sort tricks: (1&,<;._1~1,1=i.~(]-{)/:@/:).


Mathematica, 58 51 49 bytes

Rest/@SplitBy[(f@#=0;#)&/@{a}~Join~#,++f[#]==3&]&

This is an unnamed function which takes a list like

Rest/@SplitBy[(f@#=0;#)&/@{a}~Join~#,++f[#]==3&]&[{2,1,1,2,3,2,2,4,5,6,7,3,7,0,5}]

and returns a nested list like

{{2, 1}, {}, {3, 2, 2, 4, 5, 6, 7}, {}, {0}, {}}

How it works

This uses some pretty obscure magic with SplitBy.

I'm keeping track of the occurrences of each number in a function f. In Mathematica, you can define the value of a function for each input separately, and you don't need to specify the value for all possible inputs (it's more like a hash table on steroids).

So I start out by initialising f to 0 for values that are present in the input with (f@#=0;#)&/@.

Now SplitBy takes a list and a function and "splits list into sublists consisting of runs of successive elements that give the same value when f is applied" (note that SplitBy does not remove any elements). But the (undocumented) catch is, that f is called twice on each element - when comparing it to its predecessor and its successor. So if we do

 SplitBy[{1,2,3,4},Print]

we don't just get each number once, but instead this prints

 1
 2
 2
 3
 3
 4

which is 6 calls for 3 comparisons.

We can split the list before each second occurrence, if we write a function which always returns False but returns True when a second occurrence is compared to the element before it. That is the third check on that element (two checks on the first occurrence, plus the first check on the second occurrence). Hence, we use ++f[#]==3&. The nice thing is that this already returns False again on the second check of the second occurrence, such that I can return True for consecutive second occurrences, but still split between them. Likewise, this won't split after second occurrences, because the function already returns False again on the second check.

Now, the question wants us to also remove those second occurrences, so we drop the first element from each list, with Rest/@. But of course, we don't want to remove the very first element in the input, so we actually start off, by adding an element a to the beginning of the list with {a}~Join~#. a is an undefined variable, which Mathematica just treats as an unknown, so it won't affect any other values of f. This also ensures that the first actual element in the input gets its two checks like every other element.