1.

While loop version:

def linear_search(lst, value):
    """ (list, object) -> int

    Return the index of the last occurrence of value in lst, or return
    -1 if value is not in lst.

    >>> linear_search([2, 5, 1, -3], 5)
    1
    >>> linear_search([2, 4, 2], 2)
    2
    >>> linear_search([2, 5, 1, -3], 4)
    -1
    >>> linear_search([], 5)
    -1
    """ 

    i = len(lst) - 1  # The index of the next item in lst to examine.

    # Keep going until we reach the end of lst or until we find value.
    while i != -1 and lst[i] != value:
        i = i - 1

    # If we fell off the end of the list, we didn't find value.
    if i == -1:
        return -1
    else:
        return i

For loop version:

def linear_search(lst, value):
    """ (list, object) -> int

    Return the index of the last occurrence of value in lst, or return
    -1 if value is not in lst.

    >>> linear_search([2, 5, 1, -3], 5)
    1
    >>> linear_search([2, 4, 2], 2)
    2
    >>> linear_search([2, 5, 1, -3], 4)
    -1
    >>> linear_search([], 5)
    -1
    """ 

    # The first index is included, the second is not, and the third is the
    # increment.
    for i in range(len(lst) - 1, -1, -1):
        if lst[i] == value:
            return i

    return -1

Sentinal version:

def linear_search(lst, value):
    """ (list, object) -> int

    Return the index of the last occurrence of value in lst, or return
    -1 if value is not in lst.

    >>> linear_search([2, 5, 1, -3], 5)
    1
    >>> linear_search([2, 4, 2], 2)
    2
    >>> linear_search([2, 5, 1, -3], 4)
    -1
    >>> linear_search([], 5)
    -1
    """ 

    # Add the sentinel at the beginning.
    lst.insert(0, value)

    i = len(lst) - 1

    # Keep going until we find value.
    while lst[i] != value:
        i = i - 1

    # Remove the sentinel.
    lst.pop(0)

    # If we reached the beginning of the list we didn't find value.
    if i == 0:
        return -1
    else:
        # When we inserted, we shifted everything one to the right. Subtract 1
        # to account for that.
        return i - 1

2.

If there are duplicates, the one at the highest index is found.

3.

The question asks roughly how many times we need to search in order to make sorting the list first (on the order of N log_2 N steps) and then using binary search (on the order of log_2 N steps) faster than than just using linear search (on the order of N steps).

If there are k searches, then using linear search takes on the order of k * N steps.

It takes N log_2 N steps to sort a list with N items. In addition to the sorting time, there are all the binary searches to account for, each taking log_2 N steps, so this approach takes on the order of N log_2 N + k * N log_2 N steps.

N log_2 N + k * log_2 N < k * N
  [subtract k * log_2 N from both sides]
N log_2 N < k * N -  k * log_2 N
  [simplify]
N log_2 N < k * (N - log_2 N)
  [divide both sides by (N - log_2 N)]
(N log_2 N) / (N - log_2 N) < k
  [N is much larger than log_2 N, so we simplify the denominator and multiply the numerator by 2 -- remember, we want a rough answer:]
(2 * N log_2 N) / N < k
  [simplify]
2 * log_2 N < k
The actual threshold is slightly smaller, but this is a fine rough estimate.