To put you on the right track, we must first take you out of the wrong track. In your case:
If you think "268 + 268 + 108", then you are thinking wrong. That would be the count of possible passwords, assuming that a password is either a sequence of eight uppercase letters, OR a sequence of eight lowercase letters, OR a sequence of eight digits. But that's not what the text is telling; it says that passwords contain 8 characters, and that each character is an uppercase letter, a lowercase letter, or a digit. A password like "G8hw45zA" is valid, as per the question rules. Therefore, the number of possible passwords is 628 = 218340105584896 (whereas your computation yields 417754129152).
Numbers of possible passwords relate to time through speed. If the attacker has N passwords to try, and can try T passwords per second, then he will need N/T seconds to try them all. Since, on average, the attacker will get lucky after trying out half of the possible passwords, answer to question (a) is N/(2T). With N = 628 (see above) and T = 4 millions (read the question ! It is written there). Convert the seconds to hours, since the questions asks for "how many hours...?"
Question (b) is the same as (a), but with a larger attacker. Attacker now has k machines instead of just one, and can then try kT passwords per second. Average cracking time has become N/(2kT). Compute k such that this value is less than one hour (since you expressed the answer to (a) in hours, this is going to be simple).
Question (c) is more interesting. Well, the interesting part is the one that is not asked. First, consider that SHA-256 is called SHA-256 for a reason, the reason being that it outputs 256 bits. If it produced 512-bit results they would not have called it SHA-256 but SHA-512. Actually, they also defined another hash function that produces 512-bit outputs, and they called it SHA-512. But here we talk about SHA-256, not SHA-512.
As stated, the table is supposed to contain N entries, each with a hash value (256 bits, i.e. 32 bytes) and the corresponding password (8 characters, which are classically encoded over 8 bytes). So 40 bytes per entry, N*40 bytes in total. This yields a rather huge result of about 8.7 petabytes. You can store that on about 2000 off-the-shelf hard disks (4 TB each).
The really interesting part comes after. At that point, all the exercise was mostly about reading: read the question, understand the words, apply some basic mathematic operations. Now we can discuss optimizations and practical issues.
(If you try to write any of the following in your homework then your teacher will likely challenge you about it, so you'd better understand it thoroughly.)
The hash values will be sorted so that lookups are fast. But how fast ? With a classic binary search, you can find an entry among N with log N operations; in this case, there are a bit less than 248 entries, so you are in for 48 reads. With mechanical hard disks, each read access takes about 10 ms, and you need its result to know where to read next, so a lookup will take half a second. That's fast, unless the attacker has one million lookups to do (because he stole a million hashed passwords), in which case it will take all week.
A smarter attacker will parallelize things: though each disk takes 10 ms for each read operation, there are 2000 disks here, so, conceptually, 2000 accesses can be done within these 10 ms, provided that they all occur in distinct disks. This may require some more or less elaborate sorting step on the hash values to lookup.
An even smarter attacker will notice that a binary search is far from being the best that can be expected:
The attacker can build an in-RAM index, that records the exact position of, say, one entry in one million. The index will contain 228 such entries, where an entry contains the first few bytes of the hash and an offset (a 48-bit offset, since there are 248 entries in the table in all). This should fit in 3 gigabytes or so. Finding the right one-million chunks will be done in a few microseconds by doing a RAM-only binary search in the index. The number of read accesses has been reduced from 48 to about 20.
Hash values are quite uniformly distributed among possible 256-bit results. So, based on the first bytes of the hash value, one can compute the approximate position of the entry without doing any lookup. In a one-million entry chunk, realistically, the approximation will fall, on average, within one or two thousands of entries of the right one (because the square root of "one million" is "one thousand" -- I am lightheartedly talking about gaussian distributions here). The number of read accesses has been further reduced to about 10.
Mechanical hard disks don't read bytes; they read sectors. And, in fact, several sectors in one go. The hard part is moving the disk heads; reading sequential data is fast. The industry measures I/O by assuming that individual accesses are for a 4 kB page. This maps to what disks do. Whenever we read "one byte" we actually read at least 4096 bytes for the same cost. This means that the last read accesses in the binary search can be coalesced in a single access, because a 4096 page will contain hundreds of entries. Number of actual read accesses is down to 2 or 3 -- more than 10 times faster than the non-smart attacker.
We said that an entry was 40 bytes: 32 for the hash, and 8 for the password. But that's a lot more than actually needed.
First, there are "only" 248 entries in the table, so, morally, only the first 48 bits (6 bytes) of each hash value should be needed. In particular, the attacker does not really need true uniqueness: what he wants is to narrow down the number of candidates to a few dozens, that his computer can then rehash (his computer can do 4 millions hashes per second !). So entries are now 14 bytes long (6 for the truncated hash, 8 for the password) instead of 40. Total size has shrunk from 8.7 petabytes to about 3 petabytes.
Then, the hash values are sorted. So if an entry contains the (truncated) hash value "d41d8cd98f" then the next entry is likely to begin with "d41d8cd9" as well. The average difference between two successive values should be one bit. We don't need to keep all 48 bits for all entries. By grouping entries into pages, and some header for handling the few extreme cases (long "gaps" in the sequence of 48-bit values), one can achieve, say, an average of 10 bytes per entry. The original 8.7 petabytes have become 2.2 petabytes. Or, in a more vivid image, the 2000 hard disks are now 500 hard disks. Note, too, that using less bytes per entry means that a single 4096-byte I/O operation reads more entries, so not only you are using less disks, but you are also speeding up lookups.
Yet a further optimization is that there are only 62 possible characters in the passwords, and you do not need a full byte to store one character out of 62. 6 bits suffice. So the "8 bytes" for a password can be replaced with "6 bytes", and the average size of each entry is now 8 bytes. The 500 disks are now 400.
Of course, really efficient table compression would be a rainbow table. With rainbow tables, you walk possible passwords in a smart way which allows you to store them all in a table where you only keep, say, one in a thousand hashed passwords. The 500 hard disks have become a single hard disk. Of course, you have to pay for it: each lookup now involves substantial CPU. If rainbowisation shrinks the table by a factor x, then the lookup will involve about x2 hash computations. But, as said above, the attacker can do 4 millions of hashes per second. So a rainbow shrinking of x = 1000 is probably a good idea (from the point of view of the attacker).
Plus, the really really smart attacker won't implement any of this; he will just download an existing, already computed rainbow table.
Sorting 248 elements is not that easy. They don't fit in RAM. You can read them, but it takes quite some time to read and rewrite 2000 full disks. Moreover, reading or writing in non-sequential order will be atrociously slow.
This points at the merge sort as the right sort for that job. But while the basic merge sort is binary in nature (you split the data in two chunks, sorts them independently, and then merge them), you can get substantial speedups by extending the merge sort into larger splits: to sort N elements, split them into, say, 50 lists of size N/50, sort them independently (and recursively with a merge sort...), and then merge them. Merging 50 lists, obtained from 50 disks, with optimal bandwidth, is a nice programming exercise... (hint 1: use a heap for the merge; hint 2: use O_DIRECT, if the Linux people have not broken it).