CS 112
Spring 2023

Old version

This is the CS 112 site as it appeared on May 12, 2023.

Problem Set 5

due by 11:59 p.m. on Tuesday, March 21, 2023

Preliminaries

In your work on this assignment, make sure to abide by the collaboration policies of the course.

If you have questions while working on this assignment, please come to office hours, post them on Piazza, or email cs112-staff@cs.bu.edu.

Part I

50 points total

Creating the necessary folder

Create a subfolder called ps5 within your cs112 folder, and put all of the files for this assignment in that folder.

Creating the necessary file

Problems in Part I will be completed in a single PDF file. To create it, you should do the following:

  1. Access the template that we have created by clicking on this link and signing into your Google account as needed.

  2. When asked, click on the Make a copy button, which will save a copy of the template file to your Google Drive.

  3. Select File->Rename, and change the name of the file to ps5_partI.

  4. Add your work for the problems from Part I to this file.

  5. Once you have completed all of these problems, choose File->Download->PDF document, and save the PDF file in your ps5 folder. The resulting PDF file (ps5_partI.pdf) is the one that you will submit. See the submission guidelines at the end of Part I.

Problem 1: Using recursion to print an array

10 points; individual-only

Consider the following method, which uses iteration (a for loop) to print the integers stored in the array arr “vertically”, one number per line:

public static void print(int[] arr) {
    // Always check for null references first!
    if (arr == null) {
        throw new IllegalArgumentException();
    }

    for (int i = 0; i < arr.length; i++) {
        System.out.println(arr[i]);
    }
}
  1. (5 points) In section 1-1 of ps5_partI (see above), rewrite this method so that it uses recursion instead of iteration. You will need to add a second parameter (call it start) that keeps track of where you are in the array. More precisely, start will specify the start of the portion of the array that the current call is focused on. For example, to print the entire array arr, a client would make the call print(arr, 0). To print the portion of the array that begins at position 3, a client would make the call print(arr, 3). If start is negative, the method should throw an IllegalArgumentException. If start is too big given the length of the array, the method should simply return without printing anything.

  2. (4 points) Now write a method that uses recursion to print an array of integers arr in “mirrored” form – with the elements of the array first printed from beginning to end (as in the original method above), followed by the same elements in reverse order, from the end of the array back to the beginning – one value per line. For example, if arr represents the array {2, 5, 7, 4, 1}, the method should print:

    2
    5
    7
    4
    1
    1
    4
    7
    5
    2
    

    The array must be left in its original form.

    Your method should have the following header:

    public static void printMirrored(int[] arr, int i)
    

    where arr is a reference to the array, and i is an integer parameter that you should use as you see fit.

  3. (1 point) Based on your implementation of printMirrored, what is the initial call that a client would make to print the entire array arr in “mirrored” form?

Problem 2: A method that makes multiple recursive calls

10 points; individual-only

A number of the algorithms that we consider in CS 112 involve recursive methods in which a given invocation of the method may make two or more recursive calls. To understand how this type of algorithm works, it helps to draw a call tree for a given set of initial parameters.

In this problem, you will draw such a call tree to illustrate the execution of a simple recursive method that operates on integers. You may find it helpful to review our solutions to the similar problem in Lab 5 problem before continuing.

Here is the method you will trace:

public static int foo(int x, int y) {
    if (y == 1) {
        return x;
    } else if (x <= y) {
        return y;
    } else {
        int result1 = foo(x - 1, y - 1);
        int result2 = foo(x - 1, y + 1);
        return result1 + result2;
    }
}

Assume that we begin with the following initial call to this method:

foo(6, 4)
  1. Construct a call tree that shows each invocation of the method, and that uses lines to connect a given invocation to the recursive calls that it makes (if any). Follow the same format that we use in our solutions for Lab 5, Task 2.1-2.2.

    In section 2-1 of ps5_partI, we have given you an initial diagram.

    You should take the following steps to complete it:

    • Add each subsequent call with its parameters to the call tree in the appropriate place.

    • If a given call is a base case, simply put its return value under the call, as we do in our Lab 5, Task 2 solution for calls to fib(1) and fib(0).

    • If a given call is not a base case, use lines composed of / or \ characters to connect the call to the recursive call(s) that it makes.

    • Precede each call with a number that indicates the overall order in which the calls were made.

  2. Underneath your call tree, list the order in which the calls return, as well as their return values. When you refer to a given call in this part of the problem, use its number from the call tree.

    For example, the initial call always returns last, so the last line in this part of the problem should look like this:

    call 1 (foo(6,4)) returns ...
    

    where you replace the ... with its return value.

    See our solution for Lab 5, Task 2.1-2.2 for another example of what this part of your solution should look like.

Problem 3: Sorting practice

12 points; 2 points for each part; individual-only

We will finish our coverage of sorting during the week after spring break.

Important: When answering these questions, make sure to apply the versions of these algorithms that were discussed in lecture. The Java code for each algorithm can be found in our Sort class.

{10, 18, 4, 24, 33, 40, 8, 3, 12}
  1. If the array were sorted using selection sort, what would the array look like after the second pass of the algorithm (i.e., after the second time that the algorithm performs a pass or partial pass through the elements of the array)?

  2. If the array were sorted using insertion sort, what would the array look like after the fourth iteration of the outer loop of the algorithm?

  3. If the array were sorted using bubble sort, what would the array look like after the third pass of the algorithm?

  4. If the array were sorted using the version of quicksort presented in lecture, what would the array look like after the first call to the partition method?

  5. If the array were sorted using the version of quicksort presented in lecture, what would the array look like after the second call to the partition method?

  6. If the array were sorted using the version of mergesort presented in lecture, what would the array look like after the completion of the fourth call to the merge() method—the method that merges two subarrays? Note: the merge method is the helper method; is not the recursive mSort method.

Important

There will be no partial credit on the above questions, so please check your answers carefully!

Problem 4: Practice with big-O

12 points total; 4 pts. each part; individual-only

Important

When big-O expressions are called for in this and future problems, please use them to specify tight bounds as we’ve done in lecture.

  1. Determine the appropriate big-O expression for each of the following functions, and put your answer in the table we have provided in section 2-1 of ps5_partI. We’ve included the answer for the first function. (Note: We’re using the ^ symbol to represent exponentiation.)

    1.     a(n) = 5n + 1
    2.     b(n) = 2n^3 + 3n^4 + n
    3.     c(n) = 10 + 5log(n) + 6n
    4.     d(n) = 4nlog(n) + 7n
    5.     e(n) = 8n + 4n^2 + 3
  2. In the following code fragment, how many times is the count() method called as a function of the variable n? Use big-O notation, and explain your answer briefly.

    for (int i = n; i > 0; i--) {
        for (int j = 0; j < 5; j++) {
            count();
        }
    }
    
  3. In the following code fragment, how many times is the count() method called as a function of the variable n? Use big-O notation, and explain your answer briefly.

    for (int i = 0; i < 2*n; i++) {
        for (int j = i; j >= 1; j--) {
            count();
        }
    }
    

Problem 5: Comparing two algorithms

6 points; individual-only

Suppose that you want to count the number of duplicates in an unsorted array of n elements. A duplicate is an element that appears multiple times; if a given element appears x times, x - 1 of them are considered duplicates. For example, consider the following array:

{10, 6, 2, 5, 6, 6, 8, 10, 5}

It includes four duplicates: one extra 10, two extra 6s, and one extra 5.

Below are two algorithms for counting duplicates in an array of integers:

Algorithm A:

public static int numDuplicatesA(int[] arr) {
    int numDups = 0;
    for (int i = 0; i < arr.length - 1; i++) {
        for (int j = i + 1; j < arr.length; j++) {
            if (arr[j] == arr[i]) {
                numDups++;
                break;
            }
        }
    }
    return numDups;
}

Algorithm B:

public static int numDuplicatesB(int[] arr) {
    Sort.mergesort(arr);
    int numDups = 0;
    for (int i = 1; i < arr.length; i++) {
        if (arr[i] == arr[i - 1]) {
            numDups++;
        }
    }
    return numDups;
}
  1. What is the worst-case time efficiency of algorithm A in terms of the length n of the array? Make use of big-O notation and explain your answer briefly.

  2. What is the worst-case time efficiency of algorithm B in terms of the length n of the array? Make use of big-O notation and explain your answer briefly.

Submitting your work for Part I

These instructions will be added after spring break.

Note: There are separate instructions at the end of Part II that you should use when submitting your work for that part of the assignment.

Submit your ps5_partI.pdf file using these steps:

  1. If you still need to create a PDF file, open your ps5_partI file on Google Drive, choose File->Download->PDF document, and save the PDF file in your ps5 folder.

  2. Login to Gradescope by clicking the link in the left-hand navigation bar, and click on the box for CS 112.

  3. Click on the name of the assignment (PS 5: Part I) in the list of assignments on Gradescope. You should see a pop-up window labeled Submit Assignment. (If you don’t see it, click the Submit or Resubmit button at the bottom of the page.)

  4. Choose the Submit PDF option, and then click the Select PDF button and find the PDF file that you created. Then click the Upload PDF button.

  5. You should see a question outline along with thumbnails of the pages from your uploaded PDF. For each question in the outline:

    • Click the title of the question.
    • Click the page(s) on which your work for that question can be found.

    As you do so, click on the magnifying glass icon for each page and doublecheck that the pages that you see contain the work that you want us to grade.

  6. Once you have assigned pages to all of the questions in the question outline, click the Submit button in the lower-right corner of the window. You should see a box saying that your submission was successful.

Important

  • It is your responsibility to ensure that the correct version of every file is on Gradescope before the final deadline. We will not accept any file after the submission window for a given assignment has closed, so please check your submissions carefully using the steps outlined above.

  • If you are unable to access Gradescope and there is enough time to do so, wait an hour or two and then try again. If you are unable to submit and it is close to the deadline, email your homework before the deadline to cs112-staff@cs.bu.edu


Part II

50 points total

Problem 6: Recursion and strings

25 points total; individual-only

Getting started

  1. If you haven’t already done so, create a folder named ps5 for your work on this assignment. You can find instructions for doing so here.

  2. In VS Code, select the File->Open Folder or File->Open menu option, and use the resulting dialog box to find and open the folder that you created for this assignment.

  3. Select File->New File, which will open up an empty editor window.

  4. Select File->Save, and give the new file the name StringRecursion.java.

  5. In the new file, create a class called StringRecursion. In that class, implement the recursive methods described below.

  6. In addition, we highly recommend adding a main method to your class that makes test calls to your methods.

Requirements:

  1. The methods that you write must be purely recursive. The use of iteration (i.e., for, while, or do-while loops) is not allowed.
  2. The only built-in String methods that you may use are charAt, length, equals, and substring. No use of other String methods is allowed. In addition, make sure to follow any additional restrictions specified in the problem.
  3. Do not use any global variables — i.e., variables that are declared outside of a method.
  4. Use the headers specified for the methods without changing them in any way.
  5. Limit yourself to writing the methods specified below. Do not write any additional “helper” methods that assist the required methods; rather, the methods listed below should provide all of their required functionality by themselves.

Here are the methods:

  1. public static void printEveryOther(String str)
    This method must use recursion to print every other character in the string str. For example, a call of printEveryOther("method") should produce the following output:

    mto
    

    The method should not return a value.

    You wrote an iterative version of this method for Problem Set 2. Now you will use recursion to solve the same problem.

    Special cases: If the value null or the empty string ("") are passed in as the parameter, the method should just print a newline (by executing the statement System.out.println();).

    Hints:

    • Each call of this method should print only one character from the string; the combination of all of the recursive calls should print the full output string.
    • When constructing the parameter for the recursive call, you may need to take a slightly different approach than the one that we have typically used when processing strings recursively.
    • Make sure that your method works correctly for both even-length and odd-length strings.
  2. public static boolean contains(String str, char ch)
    This method must use recursion to determine if the string specified by the parameter str contains at least one occurrence of the character specified by the parameter ch, returning true if it does and false otherwise. For example:

    • contains("recursion", 's') should return true
    • contains("recursion", 'z') should should return false.

    This method should not do any printing; it should simply return the appropriate boolean value. In addition, this method may not call the numOccur method that we have given you.

    Special cases: If the value null or the empty string ("") are passed in for the first parameter, the method should return false.

  3. public static int lastIndexOf(char ch, String str)
    This method must use recursion to find and return the index of the last occurrence of the character ch in the string str, or -1 if ch does not occur in str. For example:

    • lastIndexOf('r', "recurse") should return 4
    • lastIndexOf('p', "recurse") should return -1

    This method should not do any printing; it should simply return the appropriate integer.

    Special cases: If the value null or the empty string ("") are passed in for the second parameter, the method should return -1.

    Hint: Here again, when constructing the parameter for the recursive call, you may find it helpful to take a slightly different approach than the one that we have typically used when processing strings recursively.

  4. public static String trim(String str)
    This method should take a string str and use recursion to return a string in which any leading and/or trailing spaces in the original string are removed. For example:

    • trim(" hello world ") should return the string "hello world"
    • trim("recursion ") should return the string "recursion"

    The String class comes with a built-in trim() method that does the same thing as the method that we’re asking you to write; you may not use that method in your solution!

    Special cases:

    • If the parameter is null, the method should return null.

    • If the parameter is the empty string, the method should return the empty string.

Make sure that your methods meet all of the requirements listed at the start of the problem.

Problem 7: A Letter Square solver

25 points; pair-optional

This is the only problem of the assignment that you may complete with a partner. See the rules for working with a partner on pair-optional problems for details about how this type of collaboration must be structured.

The New York Times includes a daily puzzle called “Letter Boxed.” It involves a set of 12 letters arranged around the sides of a square, such as the following:

To solve the puzzle, you need to come up with a sequence of English words in which each letter in the puzzle appears at least once. In addition, the words in the sequence must observe the following constraints:

For a given puzzle, there are many possible solutions, but solutions that use fewer words are preferred. For the puzzle above, one possible solution is

LIMES
STONES
SHAKY

However, an even better solution is

MILESTONES
SHAKY

because it uses fewer words.

In this problem, you will write a program that solves this type of puzzle using recursive backtracking!

Getting started

  1. If you haven’t already done so, create a folder named ps5 for your work on this assignment. You can find instructions for doing so here.

  2. Download the following files:

    Make sure to put all three of them in your ps5 folder.

  3. In VS Code, select the File->Open Folder or File->Open menu option, and use the resulting dialog box to find and open your ps5 folder. (Note: You must open the folder; it is not sufficient to simply open the file.)

    The name of the folder should appear in the Explorer pane on the left-hand side of the VS Code window, along with the names of the files.

Task 1: review the provided code

We have provided:

Begin by reading over the code that we have given you in LetterSquare.java. The provided code includes:

The only methods that you should change are solveRB and isValid. All of the other provided methods should be left unchanged. You are welcome to add your own additional helper methods, although doing so is not required.

Task 2: implement isValid

Once you have reviewed the provided code, you can begin to implement the bodies of the two methods that you are required to write. You should not change the headers that we have provided.

You should start by implementing the isValid helper method that will be used to check if a given letter would work as the next letter in the current word, given the words and prefixes in the dictionary and the constraints of the puzzle described at the beginning of the problem.

This method must take three parameters:

It should return true if the specified letter is a valid choice for the letter in position charNum of the word in position wordNum of the words array, and false otherwise. You may assume that only appropriate values will be passed in. In particular, you may assume that letter is one of the letters of the puzzle.

The constraints that you need to check will depend on the value of the charNum parameter (and possibly also of the wordNum parameter).

For example, let’s assume that we have the following situation:

Given this situation:

Now imagine that we have added the letter "s" to the partial solution described above to give a new partial solution {"times", ...} and that we are now focused on the first letter in second word in the solution (i.e., that we are within the call this.solveRB(1, 0, 2)). Given this situation:

Other notes:

We strongly encourage you to thoroughly test your isValid method to ensure that it works in all cases!

The best way to do this is to add some temporary test code to the beginning of the main method in LetterSquare.

For example, the description above includes some cases involving isValid that are based on the puzzle shown at the start of the problem (the one with sides {"tae", "nih", "omk", "lys"}).

You could test these cases by adding temporary code that looks like the following to the start of main:

String[] testSides = {"tae", "nih", "omk", "lys"};
LetterSquare test = new LetterSquare(testSides);
test.words[0] = "time";

// to test cases involving the second or third word,
// assign strings to other positions in test.words

// should get true
System.out.println(test.isValid("l", 0, 4));

// should get false
System.out.println(test.isValid("a", 0, 4));

// other tests go here

// exit the program after testing
System.exit(0);

To test other scenarios, you can change the strings in the testSides array and/or the strings assigned to the positions in the test.words array.

Once you have convinced yourself that your isValid method works in all cases, you can remove any temporary test code that you added to the start of main and run the program to see if it works.

Task 3: implement solveRB

The recursive-backtracking method (solveRB) must take three integer parameters:

It should follow the same basic approach as the recursive-backtracking template from lecture (the one designed to find a single solution). However, there will be a couple of key differences:

Other notes:

Task 4: test your code

Below are some puzzles that you can use for testing your full implementation. (In each case, we specify the four sides of the puzzle, separated by commas. You should enter them one at a time, without the commas!)

  1. puv, rce, otl, diy
    This has a single-word solution: productively

  2. puv, rce, otl, dix
    This has a two-word solution:

    productive
    expel
    
  3. abc, def, ghi, jkl
    The shortest possible solution is one of length 6:

    jibe
    eagle
    elf
    fleck
    kea
    ahead
    

Depending on how you implement the methods, you may end up with different solutions than the ones shown above. However, you should still end up with a solution that has the same number of words as our solution.

If your program doesn’t correctly solve these test cases, see thePS 5 FAQ for suggestions on debugging.

Submitting your work for Part II

These instructions will be added after spring break.

Note: There are separate instructions at the end of Part I that you should use when submitting your work for that part of the assignment.

You should submit only the following files:

In addition, make sure that you do not try to submit a .class file or a file with a ~ character at the end of its name.

Here are the steps:

  1. Login to Gradescope as needed by clicking the link in the left-hand navigation bar, and then click on the box for CS 112.

  2. Click on the name of the assignment (PS 5: Part II) in the list of assignments. You should see a pop-up window with a box labeled DRAG & DROP. (If you don’t see it, click the Submit or Resubmit button at the bottom of the page.)

  3. Add your files to the box labeled DRAG & DROP. You can either drag and drop the files from their folder into the box, or you can click on the box itself and browse for the files.

  4. Click the Upload button.

  5. You should see a box saying that your submission was successful. Click the (x) button to close that box.

  6. The Autograder will perform some tests on your files. Once it is done, check the results to ensure that the tests were passed. If one or more of the tests did not pass, the name of that test will be in red, and there should be a message describing the failure. Based on those messages, make any necessary changes. Feel free to ask a staff member for help.

    Note: You will not see a complete Autograder score when you submit. That is because additional tests for at least some of the problems will be run later, after the final deadline for the submission has passed. For such problems, it is important to realize that passing all of the initial tests does not necessarily mean that you will ultimately get full credit on the problem. You should always run your own tests to convince yourself that the logic of your solutions is correct.

  7. If needed, use the Resubmit button at the bottom of the page to resubmit your work. Important: Every time that you make a submission, you should submit all of the files for that Gradescope assignment, even if some of them have not changed since your last submission.

  8. Near the top of the page, click on the box labeled Code. Then click on the name of each file to view its contents. Check to make sure that the files contain the code that you want us to grade.

Important

  • It is your responsibility to ensure that the correct version of every file is on Gradescope before the final deadline. We will not accept any file after the submission window for a given assignment has closed, so please check your submissions carefully using the steps outlined above.

  • If you are unable to access Gradescope and there is enough time to do so, wait an hour or two and then try again. If you are unable to submit and it is close to the deadline, email your homework before the deadline to cs112-staff@cs.bu.edu