Last week, I wrote an editorial for UVa 12192: Grapevine. I thought some more about the experience of solving it, and came up with four lessons that apply to competitive programming problem-solving.
Last week, I wrote about the benefits of carefully studying even simple algorithms like binary search, which provides a fast way to find an element in a sorted array. Once you understand how that algorithm works, you can adapt it to solve other problems. For example:
Given an array of integers and an integer L, find the position of the first element of the array that is greater than or equal to L.
The standard binary search algorithm looks for an exact match. If the target key is not in the array, the search fails (returns a “not found” value). So if we want to handle an inequality, we’ll have to make a few changes. Here are the key points of the revised algorithm.
uHunt Chapter 3 has six starred problems, and many more problems in total, on the topic of binary search. If you want to solve them, it helps to have a firm grasp of how that algorithm works.
The binary search algorithm is conceptually simple. In ancient times, like the early 1990’s, people used it instinctively when looking up words in a paper dictionary or finding phone numbers in the thick book that arrived on their doorstep. The process:
- Open the book to the middle.
- If your target entry is alphabetically lower than the current entry, split the left half of the book. Otherwise, split the right half.
- Repeat until done.
When I’m solving competitive programming problems for practice, I follow a specific series of steps to help me get the most out of the process. My current process ends with submitting an accepted solution and recording my stop time (for measurement purposes). But there really should be another step: find someone else’s solution and compare it with mine.
When you’re solving competitive programming problems, it’s tempting to code as fast as possible. Once you have some idea about what the problem is asking for, just start coding and hack away until your solution passes. After all, these problems are written for timed contests, so why not solve them as if you’re racing the clock?
At some point in your competitive programming career, the “full speed ahead” approach might be the right one. If you want to do well in contests, you have to practice under contest-like conditions. But it may be a while before that’s the optimal practice strategy.
The problem with coding quickly is that you’ll end up with messy code that’s hard to debug. There’s nothing morally wrong with writing messy code that you’re going to throw away in an hour. If you’re getting correct solutions quickly, go for it. But if you’re spending a lot of time debugging code that’s incomprehensible a minute after you write it, maybe it would be more efficient to slow down.
Now, I’m not saying you should go to the other extreme and polish your code as if you were going to turn it into a product. Even if you have a lot of practice time, there’s a limit to how much learning benefit you can get out of one contest problem. Save your best coding style for code that you’re going to keep around.
[T]here are two things traditionally taught in universities as a part of a computer science curriculum which many people just never really fully comprehend: pointers and recursion. — Joel Spolsky
Let’s try to comprehend the basics of recursion using an example that comes up frequently in programming puzzles: generating all permutations of a set.
Last week I wrote about solving UVa 11085 using the recursive backtracking search technique. This week, I’m going to outline a more general approach to recursive backtracking problems. Then I’ll show how it can be adapted to solve UVa 574.
UVa 574 asks us to solve this problem:
Given a multiset $S$ of integers and a target sum $t$, find all subsets of $S$ that sum to $t$.
Some programming puzzles can be solved by simulating a game or process. For example:
- UVa 978 is based on simulating a conflict between warring lemmings.
- For UVa 732, you have to simulate something more abstract: a stack that pushes and pops characters.
UVa 11553 may seem like a classic simulation problem: a board game between two players. But it soon becomes clear that this problem can’t be solved by simulating the players’ moves.
When they select their tools, programmers have a choice between command-line and GUI options. Most people use both. For example, I prefer to use GUI tools for diffing a set of files I’m preparing to commit to a source repository. I also like my editor to be graphical, though I use a lot of keyboard shortcuts. But command-line tools are preferable when I’ll be repeating the same action multiple times. It’s a lot more efficient to retrieve a typed command from the shell history than to do the same steps repeatedly using mouse clicks or even keyboard shortcuts. Today I’ll be explaining some commands that are useful for programming puzzles.