How to get a list of all the years that have posts

I am not sure why you prefer the longer method over wp_get_archives(). I recommend letting WordPress do the work for you. It is more efficient, and easier to read.

However … since you presented a particular preference:

It would appear that you are using an array for post_type, when none is required. Try modifying your WP_Query args to:

$terms_year = array(
    'post_type' => 'post',
    'orderby'   => 'modified',
    'order' => 'DESC',
    'post_status' => 'publish',
);

This will pre-order your entries, so all posts of a particular year appear at one time. Also, adding post_status ensures you don’t end up with revisions,drafts, or trash that the public would never see.

Then, for readability, I might build your <li> output like this:

if (!array_key_exists($year, $years)) {
       $years[$year] = '<li><a href="';
       $years[$year] .=  home_url();
       $years[$year] .=  '/news-and-media/?year=";
       $years[$year] .=  $year . "">';
       $years[$year] .=  $year;
       $years[$year] .=  '</a></li>';
       echo $years[$year];
}

Echo statements are costly to performance. So, you are much better off building your full string, then echo it. And .. this is just much easier to read.

One final (unsolicited) critique … you should consider using either “home_url()” or “get_site_url()”, not both. You have mixed them in this code sample. It may work, but it could cause trouble for you.

Hope this is helpful!