A greedy algorithm solves a problem using a greedy strategy. Its core idea is to define a clear and effective strategy that selects the currently optimal solution at each step, with the expectation of reaching the global optimum. In some cases, a greedy strategy produces the optimal solution; in others, it does not. A successful greedy strategy is one that reaches the global optimum.
Characteristics of Greedy Algorithms
- Greedy algorithms are among the most “natural and intelligent” algorithms. Just as things in nature evolve by choosing the most favorable conditions available at the moment, greedy algorithms make choices based on what is currently most advantageous.
- A greedy algorithm uses a locally most self-serving criterion, always making what appears to be the best choice at the moment.
- The difficulty of greedy algorithms lies in proving that a locally optimal criterion can produce the global optimum.
- Learning greedy algorithms primarily involves building knowledge and experience.
Starting with a Problem
Below, I will begin with a classic greedy algorithm problem, propose different greedy strategies, and prove that one of them leads to the global optimum.
Problem: Find the Lexicographically Smallest Permutation of a String Array
Given an array of strings strings, concatenate all the strings and return the lexicographically smallest result among all possible concatenations.
For example, given ["df", "abd", "d"], the result should be "abdddf".
What Is Lexicographic Order?
Lexicographic order determines the ordering of two strings in a program. For example, "abc" is smaller than "bbc". The comparison works as follows:
If two strings have the same length, compare them character by character from the beginning. The string with the larger character at the first differing position is larger, regardless of the characters that follow.
For example,
"azzz"is smaller than"bcde"becauseais smaller thanb. Although the followingzcharacters are larger thanc,d, ande, the former string is still smaller.If two strings have different lengths, imagine padding the shorter string with the smallest ASCII value until it has the same length as the longer string, and then compare them.
For example, when comparing
"abcd"and"ad", treat"ad"as"ad00"and compare"abcd"with"ad00"(the0here represents an imaginary minimum ASCII value, not the actual character0; every character is larger than it). Sincedis larger thanb,"ad"is larger than"abcd".The essence of lexicographic order is to treat strings as numbers and compare their values. For example, two strings consisting only of lowercase letters can be treated as two base-26 numbers. Given
"abc"and"kaj","kaj"is larger becausek, the higher-order digit, is larger. Similarly, if the character set contains 65,535 characters, a string can be viewed as a base-65,535 number. The bases differ, but the comparison method is the same: the number with the larger higher-order digit is larger.
A Greedy Approach to the Problem
The solution is straightforward: first sort the array according to a particular strategy, ensuring that concatenating the sorted strings in order produces the lexicographically smallest result among all possible concatenations. Sorting is based on comparisons. For example, when comparing arr[i] and arr[j], which one should come first? This comparison strategy is our greedy strategy. Each comparison uses this strategy to determine which string comes first and which comes later—the local optimum. Once all strings have been sorted, the resulting concatenation satisfies the problem’s requirement—the global optimum.
Therefore, the key question is: what should this comparison strategy be?
Greedy Strategy 1: Sort All Strings Lexicographically and Then Concatenate Them
For example:
| |
The code implementation is:
| |
This strategy ensures that the strings in the array are sorted in ascending lexicographic order, but does it guarantee that their complete concatenation is lexicographically smallest?
The answer is no.
We can easily construct a counterexample, such as the following:
| |
A single counterexample is enough to show that this greedy strategy fails.
Greedy Strategy 2: If a·b < b·a, Place a First; Otherwise, Place b First
Explanation: During sorting, when comparing strings a and b at positions i and j, concatenate a and b in both possible orders before comparing them.
If a·b < b·a, place a first; otherwise, place b first.
In the example above, because "ba·b" < "b·ba", "ba" will ultimately be placed before "b". The sorted array is:
| |
This produces the correct answer, "bab". It satisfies the requirement, and the overall solution is as expected.
The code implementation is:
| |
How can we prove that this strategy is indeed a correct greedy strategy?
Proving the Correctness of Greedy Strategy 2
1. Prove That the Sorting Strategy in Greedy Strategy 2 Is Transitive
Under the strategy above, suppose:
- a·b <= b·a
- b·c <= c·b
If we can derive a·c <= c·a, then the strategy is transitive. The conclusion is that we can indeed derive it. The proof follows:
As mentioned above, comparing strings lexicographically is essentially equivalent to converting them into numbers and comparing their values. For example:
Concatenating "123" and "456" produces "123456", which is effectively 123 * 1000 + 456.
a·b is essentially a base-k number.
Its value is a * k^len(b) + b (a multiplied by k raised to the power of the length of b, plus b).
For convenience in the mathematical description, suppose k^len(b) is represented by a function m(b). We then have:
| |
From the derivation above, we have proven that the sorting rule in Greedy Strategy 2 is transitive.
2. Prove That the Entire Sorted Array Is Lexicographically Smallest
After sorting, suppose a is any string that appears earlier and b is any string that appears later, as shown below:
| |
We now need to prove that swapping the positions of a and b necessarily increases the lexicographic order of the entire result. The proof is as follows:
| |
Although the example above contains only two strings between a and b, it is easy to see that regardless of how many strings lie between them, swapping any such a and b will increase the lexicographic order of the final result. Therefore, after sorting, the overall concatenation must be lexicographically smallest. This completes the proof.
Greedy Methodology
We can see that the rigorous proof above applies only to this specific strategy for this particular problem. Once the problem or strategy changes, the proof is no longer useful. In practice, rigorously proving a greedy strategy mathematically is very difficult.
Moreover, we always propose a greedy strategy first and then attempt to prove it.
Therefore, we only need to verify whether a greedy strategy can produce the global optimum—that is, whether it is correct. We can use the following methods:
If a counterexample can quickly be found for a strategy, the greedy strategy has failed and should be discarded immediately.
If no counterexample can be found, then in addition to a rigorous mathematical derivation like the one above, we can simply verify it experimentally using a test harness.
The test harness works as follows:
- First, implement a brute-force solution that has high complexity but is easy to write. It does not have to use a greedy approach.
- Implement the greedy strategy we devised.
- Generate random test data in code.
- Write a test function that runs against the random samples 100,000 times—or more or fewer, depending on the situation. If the brute-force and greedy solutions produce the same result every time, this verifies that the greedy strategy is correct.
- If the results differ, the greedy strategy is incorrect. Devise a new greedy strategy and repeat step 4 until a correct one is found.
Note: During testing, the sample size, number of test runs, and other parameters can be adjusted as needed to achieve the verification objective.
This is the proper way to solve greedy algorithm problems. We do not need to prove the strategy; instead, we can directly verify its correctness through experiments.
Solving the Problem Above with a Test Harness
Now that we have introduced experimental verification using a test harness, we can solve the previous problem again in this way. First, we need to implement a purely brute-force solution. The brute-force approach is as follows:
- Exhaustively generate the strings produced by all possible permutations of the string array.
- Find the lexicographically smallest string among all concatenated permutation results.
The brute-force code is as follows:
| |
Our greedy solution is as follows:
| |
The test harness code is as follows:
| |
The test result is shown below:

A Standard Approach to Solving Greedy Algorithm Problems
Implement a solution X that does not rely on a greedy strategy. It can use the most straightforward brute-force approach.
Come up with Greedy Strategy A, Greedy Strategy B, Greedy Strategy C, and so on.
Use solution X and a test harness to experimentally determine which greedy strategy is correct.
Do not get hung up on proving the greedy strategy.