The problem with being a beginner (in any subject) is that you lack the vocabulary or experience to know how a problem can be decomposed into smaller parts. Or your understanding of the problem comes with faulty assumptions because you don't know what you don't know.
There are several aspects of problem solving. Some of these you can work on directly, but some portion of "programmer problem solving" only comes from experience and knowing what tools are available.
Knowledge areas of problem solving:
- Scope - Understanding what qualifies as "solving" the problem. Restrictions on solutions.
- Composition - Knowing how to break down the problem.
- Experience - Knowing what tools, "tricks," and algorithms are available and/or relevant.
Scope
If you don't know where you're going, how will you know when you have arrived? For any problem, it's a good idea to formulate (i.e., write down) what will qualify as a solution. Does your solution need to work for all positive integers? Does it need to work for strings shorter than a certain length? Does it matter how long it takes to find a solution? Does it matter if there is "invalid" input to your algorithm?
There are plenty other kinds of questions you can ask about what your solution should look like. In general, you should look at the problem and think if there are any ways you can be "lazy". If the problem statement only asks about positive integers, maybe you can avoid a complication with negative numbers by simply not allowing those in your algorithm. You should make a list (at least mentally) of what options you have for "widening" or "narrowing" your solution, as long as it still meets the acceptance criteria.
Composition: Decomposition
For any problem (in general) that you don't have an immediate answer for, it's a good idea to write down a list of steps that need to happen. This doesn't need to be in code, just an outline of how to work through the problem. Sometimes it's helpful to start at "the solution" and work backwards to "the beginning." Sometimes you know where you start, but are missing some steps along the way. Just list as much as you can and leave some question marks "??" in the spots you aren't sure about. I'll have some more to say in the "Experience," section but pencil in your best guess about what solution would look like in this step.
At this step, you need to continue to break down each individual step in your solution until it's described in a way that you can implement. For example, what does "iterate the primes less than one million" mean? I can immediately picture a sieve solution and know how I would write that. If you don't have a similar mental picture, then this needs to be decomposed further.
- iterate the primes less than one million
- build a list of integers less than one million
- iterate the list and cross out every second number
- find the first number after 2 (which is 3), and iterate the list, crossing out every 3rd item.
- find the first number after 3 (which is 5), and iterate the list, crossing out every 5th item.
- find the first number after 5 (which is 7), and iterate the list, crossing out every 7th item.
- ...
If you think very long about the above example, you will notice it is not very efficient. It's often best not to worry too much about efficiency until after you have a working solution. But do keep in mind the scope of the problem, sometimes this is an important consideration.
Composition: Resolution
One of the marvels of the human brains is the ability to recognize patterns. If you can find a "template" or a "recipe" to apply in more than one situation, then you can be a programmer (more in the "Experience" section).
As far as programming puzzles, it's often helpful to solve a single case. It's a good idea to start with the simplest possible scenario: zero, NULL, "", etc. What happens using your solution with that input? What happens if you use the "next" input? It really depends on the problem for how you work this step. If your problem is about positive integers, then putting inputs and outputs into a spreadsheet might be helpful. You can try plotting different sets of input and output data.
If your proposed solution can be succinctly described in the same way you would an algorithm, then maybe you can use induction to prove your solution is valid for all (within range) inputs. x => x + 1
, etc.
More formally, you want to propose a hypothesis, test it, and then prove it. Of course "prove it" can mean different things, but as a programmer, "proof" often means meeting acceptance criteria.
Experience
Can you tell what comes next: 517, 518, 519, 520, 521, ...
? Like I mentioned above, pattern recognition is essential to being a programmer. You can take an educated guess that 522
should come next in the sequence because you are familiar with "numbers" and use them extensively (maybe more than you would like). But more abstractly, what happened:
- You saw a sequence of numbers
- problem: continue the sequence
- You drew on your experience with "numbers" that they are nicely "lined up" in a sequence
- You adjusted your general knowledge of numbers and sequence and realized it fits this pattern: [x], one after [x], one after that, and one after that, and so on
- You hypothesize, therefore, the solution is
522, 523, 524, ..
(Probably -- my hope, for this example -- you don't stare at sequences of 517, 518, 519, 520, 521, 522, 523, 524, ...
all day, otherwise you would just recite a fact rather than step through the above problem solving steps)
That was a rather contrived example, but the purpose was to draw attention to the above steps. This only works because you have some "experience" to draw on. You have, in your "experience belt", a relevant tool to use: knowledge that integers continue in sequence. Programming challenges are no different.
This is the reason to understand data structures and algorithms. Both of these are essential tools that you need to be familiar with in order to solve programming problems. Some data structures are essential to solve certain types of problems, especially when considered under specific constraints. If you have a programming problem, and you have a bunch of data in a collection, and you need to access an arbitrary item in a few steps without iterating the entire collection, you need to know about hashing and related concepts (another contrived example, there's obviously more than one solution). This isn't something you would happen to know before hand, either you are taught about this, or learn it on your own, but you can't solve this problem without having some kind of experience to draw on.
How you gain experience is up to you (university, code academy, hobby projects, internship, etc). Learning by doing will be the most effective. Knowing how much experience you need is up to you. The depth of computer science is absolutely endless, no one will ever know everything relevant to programming (in practice, or academically), but the more experience you have, and the more tools are available to use, the better equipped you will be to solve problems in the future.