Sascha Schnepp: Skiena's TADM Problems Chapter 8
Sascha Schnepp: Skiena's TADM Problems Chapter 8
net/coding/solutions-skienas-algorithm/
Sascha Schnepp
"There must be some way out of here" said the joker to the thief8 — Bob Dylan
Problem 8-1
Typists often make transposition errors exchanging neighboring characters, such as typing setve when you mean steve. This requires two
substitutions to fix under the conventional definition of edit distance. Incorporate a swap operation into our edit distance function, so that such
neighboring transposition errors can be fixed at the cost of one operation.
Solution
(no guarantee that the solution is good or even correct)
I kept the somewhat quirky requirement that all strings start in position 1 (instead of 0). Be sure to add a blank at the front if you test your own
string literals. Note that I changed the meaning of “S” in the edit sequence to SWAP (I suppose it indicated SUBSTITUTE originally).
Substitutions are now indicated using R for REPLACE.
Compile with gcc -std=c99 and please leave comments if you find a mistake or an improvement.
1 #include <stdio.h>
2 #include <string.h>
3
4 #define MATCH 0 /* enumerated type symbol for match */
5 #define INSERT 1 /* enumerated type symbol for insert */
6 #define DELETE 2 /* enumerated type symbol for delete */
7 #define SWAP 3 /* enumerated type symbol for swap */
8
9 typedef struct {
10 int cost; /* cost of reaching this cell */
11 int parent; /* parent cell */
12 } cell;
13
14 /* EXAMPLE 1
15 #define MAXLEN 14
16 cell m[MAXLEN+1][MAXLEN+1];
17 char s[] = " thou shalt not";
18 char t[] = " you should not";
19 */
20
21 /* EXAMPLE 2
22 #define MAXLEN 5
23 cell m[MAXLEN+1][MAXLEN+1];
24 char s[] = " setev";
25 char t[] = " steve";
26 */
1 of 17 8/11/2014 12:00 AM
Solutions to Skiena’s Algorithm Design Manual | Sascha Schnepp http://www.saschaschnepp.net/coding/solutions-skienas-algorithm/
27
28 /* EXAMPLE 3 */
29 #define MAXLEN 35
30 cell m[MAXLEN+1][MAXLEN+1];
31 char s[] = " Heigth si on of my favurite tapas! ";
32 char t[] = " Height is one of my favorite typos!";
33
34 /*********** initializations ***********/
35 void row_init (int i) {
36 m[0][i].cost = i;
37 if (i>0)
38 m[0][i].parent = INSERT;
39 else
40 m[0][i].parent = -1;
41 }
42
43 void column_init (int i) {
44 m[i][0].cost = i;
45 if (i>0)
46 m[i][0].parent = DELETE;
47 else
48 m[i][0].parent = -1;
49 }
50
51 /*********** edit distance ***********/
52 void goal_cell(char *s, char *t, int *i, int *j) {
53 *i = strlen(s) - 1;
54 *j = strlen(t) - 1;
55 }
56
57 int match ( char s, char t ) {
58 if ( s==t )
59 return 0;
60 else
61 return 1;
62 }
63
64 int indel ( char c ) {
65 return 1;
66 }
67
68 int swap ( char *s, char *t, int *i, int *j ) {
69 /* check for end of string */
70 if ( !( (*i < MAXLEN) && (*j < MAXLEN) ))
71 return 10;
72 /* Swap if the next two chars of s and t
73 are cross-wise identical but not if they are
74 pairwise the same - in this case we want
75 to match */
76 if ( s[*i] == t[(*j)+1] && s[(*i)+1] == t[*j] && (s[*i] != s[(*i)+1]) && (*i == *j) ) {
77 return -1; /* -1 will beat all other 1-to-1 edits */
78 }
79 else
80 return 10;
81 }
82
83 int string_edit_distance (char *s, char *t)
84 {
85 int i, j;
86 int opt[4]; /* cost of the options */
87 for (unsigned int i=0; i<MAXLEN; i++) {
88 row_init(i);
89 column_init(i);
90 }
91
92 for (i=1; i < (int) strlen(s); i++) {
93 for (j=1; j < (int) strlen(t); j++) {
94 opt[MATCH] = m[i-1][j-1].cost + match(s[i],t[j]);
95 opt[INSERT] = m[i][j-1].cost + indel(t[j]);
96 opt[DELETE] = m[i-1][j].cost + indel(s[i]);
97 opt[SWAP] = m[i-1][j-1].cost + swap(s,t,&i,&j);
98 m[i][j].cost = opt[MATCH];
99 m[i][j].parent = MATCH;
100 for (int k=INSERT; k<=SWAP; k++)
101 if (opt[k] < m[i][j].cost) {
102 m[i][j].cost = opt[k];
103 m[i][j].parent = k;
104 if ( k == SWAP ) {
105 /* correct for the -1 added before */
106 m[i][j].cost += 1;
107 m[i][j].parent = k;
2 of 17 8/11/2014 12:00 AM
Solutions to Skiena’s Algorithm Design Manual | Sascha Schnepp http://www.saschaschnepp.net/coding/solutions-skienas-algorithm/
Output of example 1:
Edit distance of " thou shalt not" and " you should not" is 5
DRMMMMMIRMRMMMM
Output of example 2:
Output of example 3:
3 of 17 8/11/2014 12:00 AM
Solutions to Skiena’s Algorithm Design Manual | Sascha Schnepp http://www.saschaschnepp.net/coding/solutions-skienas-algorithm/
Edit distance of " Heigth si on of my favurite tapas! " and " Height is one of my favorite typos!" is 7
MMMMSMSMMMIMMMMMMMMMMRMMMMMMRMRMMD
Problem 8-2
Suppose you are given three strings of characters: X, Y , and Z, where |X| = n, |Y| = m, and |Z| = n+m. Z is said to be a shuffle of X and Y iff Z
can be formed by interleaving the characters from X and Y in a way that maintains the left-to-right ordering of the characters from each string.
1. Show that cchocohilaptes is a shuffle of chocolate and chips, but chocochilatspe is not.
2. Give an efficient dynamic-programming algorithm that determines whether Z is a shuffle of X and Y. Hint: the values of the dynamic
programming matrix you construct should be Boolean, not numeric.
Solution
(no guarantee that the solution is good or even correct)
1. This one is easy to see if you start from the end. The ‘s’ cannot occur before the ‘p’
2. Give an efficient dynamic-programming algorithm that determines whether Z is a shuffle of X and Y . Hint: the values of the dynamic
programming matrix you construct should be Boolean, not numeric.
Compile with -std=c++11 option and please leave comments if you find a mistake or an improvement.
1 #include <iostream>
2 #include <vector>
3
4 class Shuffle {
5 public:
6 Shuffle(const std::string& a, const std::string& b, const std::string& c);
7 void is_shuffle ( const unsigned int i, const unsigned int j );
8 void init ();
9 void printXYZ () const;
10 void printDPmat () const;
11 bool get ( const unsigned int i, const unsigned int j ) const;
12
13 private:
14 const std::string x;
15 const std::string y;
16 const std::string z;
17 const std::string::size_type n, m;
18 std::vector<std::vector<bool>> DPmat;
19 };
20
21 Shuffle::Shuffle(const std::string& a, const std::string& b, const std::string& c) : x{a}, y{b}, z{c}, n{x.size()},
22 DPmat.resize(n+1);
23 for ( unsigned int i = 0 ; i <= n; i++ )
24 DPmat[i].resize(m+1);
25 }
26
27 void Shuffle::printXYZ () const {
28 std::cout << x << std::endl << y << std::endl << z << std::endl;
29 }
30
31 void Shuffle::printDPmat () const {
32 for ( unsigned int i = 0; i <= n; ++i ) {
33 for ( unsigned int j = 0; j <= m; ++j )
34 std::cout << DPmat[i][j] << " ";
35 std::cout << std::endl;
36 }
37 }
38
39 void Shuffle::is_shuffle ( const unsigned int i, const unsigned int j ) {
40 DPmat[i][j] = ((x[i-1] == z[i+j-1]) && DPmat[i-1][j]) || ((y[j-1] == z[i+j-1]) && DPmat[i][j-1] );
41 //-SMS printf ( "x[%i]: %c y[%i]: %c z[%i]: %c\n", i-1, x[i-1], j-1, y[j-1], i+j-1, z[i+j-1] );
42 //-SMS printDPmat();
43 }
44
45 bool Shuffle::get ( const unsigned int i, const unsigned int j ) const {
46 return DPmat[i][j];
47 }
48
49 void Shuffle::init () {
50 DPmat[0][0] = true;
51
4 of 17 8/11/2014 12:00 AM
Solutions to Skiena’s Algorithm Design Manual | Sascha Schnepp http://www.saschaschnepp.net/coding/solutions-skienas-algorithm/
Output:
chocolate
chips
cchocohilaptes
1 1 0 0 0 0
1 1 1 0 0 0
0 1 0 0 0 0
0 1 0 0 0 0
0 1 0 0 0 0
0 1 1 1 0 0
0 0 0 1 0 0
0 0 0 1 1 0
0 0 0 0 1 0
0 0 0 0 1 1
cchocohilaptes is a shuffle of chocolate and chips
Problem 8-3
The longest common substring (not subsequence) of two strings X and Y is the longest string that appears as a run of consecutive letters in
both strings. For example, the longest common substring of photograph and tomography is ograph.
1. Let n = |X| and m = |Y |. Give a Θ(nm) dynamic programming algorithm for longest common substring based on the longest common
subsequence/edit distance algorithm.
2. Give a simpler Θ(nm) algorithm that does not rely on dynamic programming.
Solution
(no guarantee that the solution is good or even correct)
5 of 17 8/11/2014 12:00 AM
Solutions to Skiena’s Algorithm Design Manual | Sascha Schnepp http://www.saschaschnepp.net/coding/solutions-skienas-algorithm/
1 #include <iostream>
2 #include <vector>
3 #include <string>
4
5 class LongestSubstring {
6 public:
7 LongestSubstring(const std::string& a, const std::string& b);
8 void find_longest_substring ();
9 void init ();
10 void printDPmat () const;
11 std::string getZ () const;
12 unsigned int getMaxlen () const;
13
14 private:
15 const std::string x;
16 const std::string y;
17 std::string z;
18 const std::string::size_type n, m;
19 std::vector<std::vector<bool>> DPmat;
20 unsigned int maxlen;
21 };
22
23 LongestSubstring::LongestSubstring(const std::string& a, const std::string& b) : x{a}, y{b}, n{x.size()}, m{y.size()
24 DPmat.resize(n);
25 for ( unsigned int i = 0 ; i <= n; i++ )
26 DPmat[i].resize(m);
27 }
28
29 void LongestSubstring::printDPmat () const {
30 for ( unsigned int i = 0; i != n; ++i ) {
31 for ( unsigned int j = 0; j != m; ++j )
32 std::cout << DPmat[i][j] << " ";
33 std::cout << std::endl;
34 }
35 }
36
37 // the longest diagonal sequence of true values in the DP matrix
38 // constitutes the longest substring
39 void LongestSubstring::find_longest_substring () {
40 for ( unsigned int i = 0; i != n; ++i ) {
41 for ( unsigned int j = 0; j != m; ++j ) {
42 unsigned int k = i, l = j, len = 0;
43 std::string tmp = "";
44 while ( DPmat[k][l] == true ) {
45 tmp += x[k];
46 ++k, ++l, ++len;
47 }
48 if ( len > maxlen ) {
49 maxlen = len;
50 z = tmp;
51 }
52 }
53 }
54 }
55
56 std::string LongestSubstring::getZ () const {
57 return z;
58 }
59
60 unsigned int LongestSubstring::getMaxlen () const {
61 return maxlen;
62 }
63
64 // the DP matrix can be constructed entirely during initialization
65 void LongestSubstring::init () {
66 DPmat[0][0] = true;
67
68 for ( std::string::size_type i = 0; i != n; ++i )
69 for ( std::string::size_type j = 0; j != m; ++j )
70 DPmat[i][j] = x[i] == y[j];
71 }
72
73 int main () {
74 const std::string X {"photograph"};
75 const std::string Y {"tomography"};
76 LongestSubstring ls(X, Y);
77
78 ls.init();
79 ls.find_longest_substring ();
6 of 17 8/11/2014 12:00 AM
Solutions to Skiena’s Algorithm Design Manual | Sascha Schnepp http://www.saschaschnepp.net/coding/solutions-skienas-algorithm/
80
81 ls.printDPmat();
82 std::cout << "Longest common substring of " << X << " and " << Y << " is " << ls.getZ() << " (length: "
83 return 0;
84 }
Output:
0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0 1 0
0 1 0 1 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0
0 1 0 1 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0
0 0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0 1 0
Longest common substring of photograph and tomography is ograph (length: 6)
2. A Python 3 solution
1 def longest_substring_b( s, t ):
2 u = []
3 S = len(s)
4 T = len(t)
5 maxlen = 0
6
7 i = 0
8 j = 0
9 for i in range(S):
10 for j in range(T):
11 tmplen = 0
12 v = []
13 if s[i] == t[j]:
14 n = i
15 m = j
16 while n < S and m < T and s[n] == t[m]:
17 v.append(s[n])
18 n += 1
19 m += 1
20 if ( len(v) > maxlen ):
21 maxlen = len(v)
22 u = v
23
24 return ''.join(u)
25
26 if __name__ == "__main__":
27
28 s = "photograph"
29 t = "tomography"
30
31 u = longest_substring_b ( s, t )
32 print ("Longest substring of '" + s + "' and '" + t + "' is '" + u + "' (length: " + str(len(u)) + ")"
Output:
Problem 8-4
The longest common subsequence (LCS) of two sequences T and P is the longest sequence L such that L is a subsequence of both T and P.
The shortest common supersequence (SCS) of T and P is the smallest sequence L such that both T and P are a subsequence of L.
1. Give efficient algorithms to find the LCS and SCS of two given sequences.
2. Let d(T,P) be the minimum edit distance between T and P when no substitutions are allowed (i.e., the only changes are character
insertion and deletion). Prove that d(T, P ) = |SCS(T, P )| − |LCS(T, P )| where |SCS(T, P )| (|LCS(T,P)|) is the size of the shortest SCS
7 of 17 8/11/2014 12:00 AM
Solutions to Skiena’s Algorithm Design Manual | Sascha Schnepp http://www.saschaschnepp.net/coding/solutions-skienas-algorithm/
Solution
(no guarantee that the solution is good or even correct)
1. From how I understand dynamic programming by now there is not THE ONE solution. You can rather build the DP matrix in different
ways, which result in different paths across the matrix for obtaining the desired result. Here, is a Python 3 solution.
8 of 17 8/11/2014 12:00 AM
Solutions to Skiena’s Algorithm Design Manual | Sascha Schnepp http://www.saschaschnepp.net/coding/solutions-skienas-algorithm/
71 if __name__ == "__main__":
72
73 s = "photograph"
74 t = "tomography"
75
76 u = longest_common_subsequence ( s, t )
77 print ("Longest common subsequence of '" + s + "' and '" + t + "' is '" + u + "' (length: " + str(len
78
79 u = shortest_common_supersequence ( s, t )
80 print ("Shortest common supersequence of '" + s + "' and '" + t + "' is '" + u + "' (length: " + str
81 # photomography would be another valid SCS
2. The obvious case is T = P, where d = |SCS| – |LCS| = 0. Otherwise, the sequences T and P differ by the set of letters D = SCS \ LCS. To
edit from P to T (or vice versa) for each letter of D exactly one of the operations insert or delete has to be applied. The edit distance is
hence d = |D| = |SCS| – |LCS|.
Problem 8-5
Let be n programs to be stored on a disk with capacity D megabytes. Program requires megabytes of storage. We
cannot store them all because
1. Does a greedy algorithm that selects programs in order of nondecreasing maximize the number of programs held on the disk? Prove or
give a counter-example.
2. Does a greedy algorithm that selects programs in order of nonincreasing order use as much of the capacity of the disk as possible?
Prove or give a counter-example.
Solution
1. Yes, it does. Assume with , then replacing any of these with a leads to a larger
.
2. No. Consider, e.g., and . The algorithm would choose {8} and terminate but {2,3,4} fills the entire disk.
Problem 8-6
Coins in the United States are minted with denominations of 1, 5, 10, 25, and 50 cents. Now consider a country whose coins are minted with
denominations of units. We seek an algorithm to make change of n units using the minimum number of coins for this country.
1. The greedy algorithm repeatedly selects the biggest coin no bigger than the amount to be changed and repeats until it is zero. Show that
the greedy algorithm does not always use the minimum number of coins in a country whose denominations are {1,6,10}.
2. Give an efficient algorithm that correctly determines the minimum number of coins needed to make change of n units using
denominations . Analyze its running time.
Solution
1. Consider, e.g., . The greedy algorithm will select but a better set is .
2. No solution here but you can find one on panictank.
Problem 8-8
In the single-processor scheduling problem, we are given a set of n jobs J. Each job i has a processing time ti, and a deadline di. A feasible
schedule is a permutation of the jobs such that when the jobs are performed in that order, every job is finished before its deadline. The greedy
algorithm for single-processor scheduling selects the job with the earliest deadline first. Show that if a feasible schedule exists, then the
schedule produced by this greedy algorithm is feasible.
Solution
I am not completely sure that I get the question right because it seems rather obvious that if such a schedule exists and the jobs are executed
in order of their deadline, then that will be a feasible solution (and maybe the only one).
The formalize, it the proof by induction concept might be the right choice
9 of 17 8/11/2014 12:00 AM
Solutions to Skiena’s Algorithm Design Manual | Sascha Schnepp http://www.saschaschnepp.net/coding/solutions-skienas-algorithm/
1. Start: : if
2. Assume:
3. Then: For
This can be interpreted as the feasibility condition. If it fulfilled the schedule is valid.
In words: If all jobs up to k finished on time and a feasible solution exists, the greedy algorithm will correctly pick the next job. As I said, maybe I
misunderstood the question8
Problem 8-9
The knapsack problem is as follows: given a set of integers and a given target number T, find a subset of S that adds up
exactly to T. For example, within S = {1,2,5,9,10} there is a subset that adds up to T = 22 but not T = 23. Give a correct programming algorithm
for knapsack that runs in O(nT) time.
Solution
The code is written as backtracking solution, where pruning and dynamic programming can be enabled as optimizations. Also, it constructs all
possible subsets that fill the knapsack or just stop once a solution is found. The respective variables to set/unset are
generate_all_solutions{true/false} and useDP{true/false}.
The dynamic programming matrix in this case is a table. Every time a set with some total of t < T is constructed, it is stored in the table. Then
every time a number is added to the knapsack, we check if there is a known combination for the remainder computed already, thus avoiding to
compute a feasible set for any intermediate knapsack total smaller than T twice.
The DP algorithm runs at at worst. This is the case if all intermediate knapsack sets for totals from 1 to T are really constructed and all
of them require testing all n numbers of the set. In average the algorithm will perform far better. If useDP is true, the DP table is printed at the
end, and you can see that is not very populated.
1 #include <iostream>
2 #include <vector>
3 #include <chrono>
4
5 class Knapsack {
6 public:
7 Knapsack ( const std::vector<unsigned int>& numbers, const unsigned int T );
8 bool is_a_solution () const;
9 void process_solution ();
10 unsigned int get_total () const;
11 void init_dp ();
12 bool use_dp () const;
13 void backtrack ( );
14 unsigned int num_solutions () const;
15 unsigned int num_skips () const;
16 void print_dp () const;
17
18 private:
19 const unsigned int target;
20 const std::vector<unsigned int> set;
21 const std::vector<unsigned int>::size_type set_size;
22 std::vector<bool> in_sack;
23 unsigned int solutions_found;
24 unsigned int skips;
25 const bool generate_all_solutions;
26 const bool useDP;
27 std::vector<std::vector<unsigned int>> DP_sack;
28 std::vector<std::vector<bool>> DP_in_sack;
29 std::vector<unsigned int> sack;
30 };
31
32 Knapsack::Knapsack ( const std::vector<unsigned int>& numbers, const unsigned int T ) :
33 target{T}, set{numbers}, set_size{numbers.size()}, solutions_found{0}, skips{0},
34 generate_all_solutions{true}, // change here to construct one/all solutions
35 useDP{true} // change here to use dynamic programming
36 {
37 in_sack.resize(numbers.size(), false);
38 }
39
40 bool Knapsack::use_dp () const {
41 return useDP;
10 of 17 8/11/2014 12:00 AM
Solutions to Skiena’s Algorithm Design Manual | Sascha Schnepp http://www.saschaschnepp.net/coding/solutions-skienas-algorithm/
42 }
43
44 void Knapsack::init_dp () {
45 for ( unsigned int i = 0; i != target; ++i ) {
46 DP_sack.resize(target+1);
47 DP_in_sack.resize(target+1);
48 }
49 }
50
51 void Knapsack::print_dp () const {
52 for ( unsigned int n = 0; n != DP_sack.size(); ++n ) {
53 if ( DP_sack[n].size() == 0 ) continue;
54 std::cout << n << ": ";
55 for ( auto& m : DP_sack[n] )
56 std::cout << m << " ";
57 std::cout << std::endl;
58 }
59 }
60
61 unsigned int Knapsack::get_total () const {
62 unsigned int sum {0};
63 for ( auto& i : sack )
64 sum += i;
65 return sum;
66 }
67
68 bool Knapsack::is_a_solution ( ) const {
69 return get_total() == target;
70 }
71
72 unsigned int Knapsack::num_solutions () const {
73 return solutions_found;
74 }
75
76 // check how often we pruned
77 unsigned int Knapsack::num_skips () const {
78 return skips;
79 }
80
81 // processing = print it + count it
82 void Knapsack::process_solution ( ) {
83 ++solutions_found;
84 printf ( "Solution #%4u: ", solutions_found );
85 for ( auto& i : sack )
86 printf ("%u ", i);
87 printf ( "\n" );
88 }
89
90 void Knapsack::backtrack ( ) {
91
92 if ( generate_all_solutions == false && solutions_found > 0 ) return;
93
94 if ( is_a_solution () ) {
95 process_solution ();
96 } else {
97 // >>> dynamic programming part I
98 if ( useDP ) {
99 unsigned int total = get_total();
100 if ( DP_sack[total].size() == 0 ) {
101 DP_sack[total].insert( end(DP_sack[total]), begin(sack), end(sack) );
102 DP_in_sack[total] = in_sack;
103 }
104 }
105 // <<<
106
107 for ( unsigned int i = 0; i != set_size; ++i ) {
108 if ( !in_sack[i] ) {
109 // >>> comment this to disable pruning
110 if ( get_total () + set[i] > target ) {
111 ++skips;
112 continue;
113 }
114 // <<<
115
116 in_sack[i] = true;
117 sack.push_back (set[i]);
118
119 // >>> dynamic programming part II
120 if ( useDP && (get_total() <= target) ) {
121 const unsigned int remainder = target – get_total ();
122 if ( DP_sack[remainder].size() > 0 ) {
11 of 17 8/11/2014 12:00 AM
Solutions to Skiena’s Algorithm Design Manual | Sascha Schnepp http://www.saschaschnepp.net/coding/solutions-skienas-algorithm/
For the set {1,2,5,9,10,21,33,57,98,111,1001} and the target 1111 the execution times for constructing all 864 possible knapsack sets on my
machine are:
Problem 8-10
The integer partition takes a set of positive integers and asks if there is a subset such that . Let
. Give an O(nM) dynamic programming algorithm to solve the integer partition problem.
Solution
This can be solved with the code of Problem 8-9 setting T = M/2.
No solution to Problems 8-11 to 8-13
If you have one, I am happy to post it and credit it to you.
Problem 8-14
The traditional world chess championship is a match of 24 games. The current champion retains the title in case the match is a tie. Each game
ends in a win, loss, or draw (tie) where wins count as 1, losses as 0, and draws as 1/2. The players take turns playing white and black. White
has an advantage, because he moves first. The champion plays white in the first game. He has probabilities ww, wd, and wl of winning,
drawing, and losing playing white, and has probabilities bw, bd, and bl of winning, drawing, and losing playing black.
1. Write a recurrence for the probability that the champion retains the title. Assume that there are g games left to play in the match and that
the champion needs to win i games (which may end in a 1/2).
2. Based on your recurrence, give a dynamic programming to calculate the champion's probability of retaining the title.
3. Analyze its running time for an n game match.
Solution
12 of 17 8/11/2014 12:00 AM
Solutions to Skiena’s Algorithm Design Manual | Sascha Schnepp http://www.saschaschnepp.net/coding/solutions-skienas-algorithm/
1. This is achieved by an exhaustive search over all possible game series writing a trackback program like the one in Problem 8-9. The
candidate set is composed from the possible outcomes {w,d,l} with multiple inclusion (i.e. without cutting the candidates). A solution is
found if the length of the outcome vector has length g. There are such outcome vectors (permutations).
The process_solution step consists of computing the number of points won by the defending champion given the current outcome vector
. In this step we have to take into account whether the champion plays white or black, which can be determined from the number of
games remaining r by r % 2.
We count for how many outcome vectors the defending champion wins and divide by . This gives the requested probability.
2. The DP matrix maintains the points won at intermediate stages of a match, i.e., the sum of points for vectors with .
3. Once we consider game k, all required numbers of points won in games 1 to k-1 are stored in the DP matrix. At game k there are hence
lookups and multiplications and DP writes to be performed. The runtime should thus be .
Problem 8-24
Given a set of coin denominators, find the minimum number of coins to make a certain amount of change.
Solution
1 coins = [1,2,5,10,20,50,100,200]
2 target = 11111
3 DP = [[] for i in range(target+1)]
4
5 def construct_hierachically ( i ):
6 shortest = []
7
8 for n,c in reversed(list(enumerate(coins))):
9 # one coin suffices
10 if i - c == 0:
11 DP[i].append(c)
12 return
13 # find the minimal set
14 elif i - c > 0:
15 set = 1
16 if len(DP[i-c]) == 0:
17 construct_hierachically ( i-c )
18 set.extend ( DP[i-c] )
19 if len(shortest) == 0:
20 shortest = set
21 else:
22 if len(set) < len(shortest):
23 shortest = set
24 DP[i] = shortest
25
26 if __name__ == "__main__":
27 construct_hierachically ( target )
28 print (DP[target])
SH ARE T HI S:
13 of 17 8/11/2014 12:00 AM
Solutions to Skiena’s Algorithm Design Manual | Sascha Schnepp http://www.saschaschnepp.net/coding/solutions-skienas-algorithm/
Problem 7-19
Use a random number generator (rng04) that generates numbers from {0, 1, 2, 3, 4} with equal probability to write a random number generator
that generates numbers from 0 to 7 (rng07) with equal probability. What are expected number of calls to rng04 per call of rng07?
Solution
(no guarantee that the solution is good or even correct)
The basic idea is to represent the numbers 0..7 with three bits, where each bit is set randomly. To this end the rng04() is wrapped into a method
rng03(), which trims its output to 0..3. Then, rng07() uses this output to set each of the three bits and return the random number in 0..7.
Here is a Python 3 solution. Please leave comments if you find a mistake or an improvement.
1 #!/usr/bin/env python
2
3 import sys, random
4
5 # used for counting method calls. taken from
6 # http://stackoverflow.com/questions/1301735/counting-python-method-calls-within-another-method
7 def counted(fn):
8 def wrapper(*args, **kwargs):
9 wrapper.called+= 1
10 return fn(*args, **kwargs)
11 wrapper.called= 0
12 wrapper.__name__= fn.__name__
13 return wrapper
14
15 # RNG 0..4 (given)
16 @counted
17 def rng04():
18 return random.randint(0,4)
19
20 # filtered call to rng04
21 # return 0..3
22 @counted
23 def rng03():
24 while True:
25 i = rng04()
26 if i < 4:
27 return i
28
29 # generate uniform numbers in 0..7
30 # use rng03 to generate representations of the 3 bits for
31 # numbers in the range 0..7
32 def rng07():
33 b3 = rng03() % 2
34 b2 = rng03() % 2
35 b1 = rng03() % 2
36 n = b3*4 + b2*2 + b1
37 return n
38
39 if __name__ == "__main__":
40 rands = []
41 n = 100000
42 for i in range(n):
43 rands.append(rng07())
44 for i in range(8):
45 print (str(i) + "'s: " + str(rands.count(i)))
46
47 print ("Called rng04() " + str(rng04.called) + " times")
48 print ("rng04()/rng03() call ratio: " + str(rng04.called/rng03.called))
49 print ("rng04()/rng07() call ratio: " + str(rng04.called/n))
50
51 sys.exit()
Each call to rng07() calls rng03() to set each of the three bits, which in turn calls rng04() 5/4 times to obtain a number in 0..3. Hence, rng04() is
called 5/4*3 = 3.75 times in average on each call to rng07(). The solution on the AlgoWiki requires about one call less, the solution on panictank
requires more.
14 of 17 8/11/2014 12:00 AM
Solutions to Skiena’s Algorithm Design Manual | Sascha Schnepp http://www.saschaschnepp.net/coding/solutions-skienas-algorithm/
SH ARE T HI S:
Posted in coding, nh, TADM | Tagged Algorithms, Programming, TADM | Leave a reply
Solution
(no guarantee that the solution is good or even correct)
Compile with -std=c++11 and please leave comments if you find a mistake or an improvement.
1 #include <iostream>
2 #include <array>
3
4 #define N 5
5 #define K 3
6
7 // Comment 1: some function parameters are unused but kept
8 // to provide consistency with Skiena's book
9
10 // By construction it is a solution if we reach the last element
11 bool is_a_solution (
12 std::array<int, K>& arr, int k, int n
13 ) {
14 return k == n-1;
15 }
16
17 // processing = print it
18 void process_solution (
19 std::array<int, K>& arr, int k, int n
20 ) {
21 for ( int i = 0; i != n; ++i )
22 printf ("%i ", arr[i]);
23 printf ( "\n" );
24 }
25
26 void construct_candidates (
27 std::array<int, k="">& arr, int k, int n, std::array<int, n="">& cands, int& ncandidates
28 ) {
29 ncandidates = 0;
30
31 if ( k == 0 ) {
32 for ( int i=0; i != N; ++i )
33 cands[ncandidates++] = i;
34 } else {
35 // all numbers greater than the currently greatest are candidates
36 for ( int i = arr[k-1]+1; i != N; ++i )
37 cands[ncandidates++] = i;
38 }
39 }
40
41 void backtrack (
42 std::array<int, K>& arr, int k, int n
43 ) {
44 std::array<int, n=""> cands = {0};
45 int ncandidates;
15 of 17 8/11/2014 12:00 AM
Solutions to Skiena’s Algorithm Design Manual | Sascha Schnepp http://www.saschaschnepp.net/coding/solutions-skienas-algorithm/
46
47 if ( is_a_solution ( arr, k, n ))
48 process_solution ( arr, k, n );
49 else {
50 k = k+1;
51 construct_candidates ( arr, k, n, cands, ncandidates );
52 // pruning condition
53 if ( ncandidates < K-k )
54 return;
55
56 for (int i=0; i<ncandidates; i++) {
57 arr[k] = cands[i];
58 backtrack ( arr, k, n );
59 }
60 }
61 }
62
63 int main () {
64
65 std::array<int, K> arr = {0};
66 int k = -1;
67
68 backtrack ( arr, k, K );
69
70 return 0;
71 }
SH ARE T HI S:
Posted in coding, nh, TADM | Tagged Algorithms, Programming, TADM | Leave a reply
A derangement is a permutation of such that no item is in its proper position, i.e. . Write an efficient
backtracking program with pruning that constructs all the derangements of n items.
Solution
(no guarantee that the solution is good or even correct)
Compile with -std=c++11 and please leave comments if you find a mistake or an improvement.
1 #include <iostream>
2 #include <array>
3
4 #define N 10
5
6 // Comment 1: some function parameters are unused but kept
7 // to provide consistency with Skiena's book
8 // Comment 2: there is no pruning because the candidate lists
9 // are constructed to contain only possible numbers
10
11 // By construction it is a solution if we reach the last element
12 bool is_a_solution (
13 std::array<int, N>& arr, int k, int n
14 ) {
15 return k == n-1;
16 }
17
18 // processing = print it
16 of 17 8/11/2014 12:00 AM
Solutions to Skiena’s Algorithm Design Manual | Sascha Schnepp http://www.saschaschnepp.net/coding/solutions-skienas-algorithm/
19 void process_solution (
20 std::array<int, N>& arr, int k, int n
21 ) {
22 for ( int i = 0; i != n; ++i )
23 printf ("%i ", arr[i]);
24 printf ( "\n" );
25 }
26
27 void construct_candidates (
28 std::array<int, N>& arr, int k, int n, std::array<int, N>& cands, int& ncandidates
29 ) {
30 ncandidates = 0;
31
32 // generate a full list of candidates
33 int candlist[N];
34 for ( int i = 0; i != n; ++i )
35 candlist[i] = i;
36
37 // remove those that are in the array already and the one in position k
38 for ( int i = 0; i != k; ++i )
39 candlist[arr[i]] = -1;
40 candlist[k] = -1;
41
42 // the rest are candidates
43 for ( int i = 0; i != n; ++i )
44 if ( candlist[i] != -1 )
45 cands[ncandidates++] = candlist[i];
46 }
47
48 void backtrack (
49 std::array<int, N>& arr, int k, int n
50 ) {
51 std::array<int, N> cands = {0};
52 int ncandidates;
53
54 if ( is_a_solution ( arr, k, n ))
55 process_solution ( arr, k, n );
56 else {
57 k = k+1;
58 construct_candidates ( arr, k, n, cands, ncandidates );
59 for (int i=0; i<ncandidates; i++) {
60 arr[k] = cands[i];
61 backtrack ( arr, k, n );
62 }
63 }
64 }
65
66 int main () {
67
68 std::array<int, N> arr = {-1};
69 int k = -1;
70
71 backtrack ( arr, k, N );
72
73 return 0;
74 }
SH ARE T HI S:
Sascha Schnepp
Proudly powered by WordPress Theme: Twenty Eleven Child.
17 of 17 8/11/2014 12:00 AM