0

Given a string s, check if it is possible to make it a palindrome by deleting AT MOST one character (meaning zero deletions is acceptable). String s will contain <50,000 lowercase alphabetical characters.

The code I wrote below passed 458/460 test cases, and it got stuck on one in particular with no obvious reason, returning false instead of true. The logic of the algorithm is simple, and I've tried moving conditionals around but nothing seems to change.

class Solution {
  public:
    bool ispalindrome; //holds result
    bool validPalindrome(string s) {
        bool candelete = true; //allows one delete
        ispalindrome = true; //initial condition
        int lcursor = 0;
        int rcursor = s.length() - 1;
        while(lcursor < rcursor && ispalindrome){
            //if cursor points at different letters
            if(s[lcursor] != s[rcursor]){
                // if delete is still allowed and delete works
                if(s[lcursor + 1] == s[rcursor] && candelete){
                    lcursor++;
                    candelete = false;
                } else if (s[lcursor] == s[rcursor - 1] && candelete){
                    rcursor--;
                    candelete = false;
                } else {
                    ispalindrome = false;
                }
            }
            lcursor++;
            rcursor--;
        }
        return ispalindrome;
    }
};

The test case that trips this solution is as follows:

aguokepatgbnvfqmgmlcupuufxoohdfpgjdmysgvhmvffcnqxjjxqncffvmhvgsymdjgpfdhooxfuupuculmgmqfvnbgtapekouga

Code testing with this testcase:

#include <iostream>
using std::string;

// class Solution { ... etc., from above

int main() {
  string s = "aguokepatgbnvfqmgmlcupuufxoohdfpgjdmysgvhmvffcnqxjjxqncffvmhvgsymdjgpfdhooxfuupuculmgmqfvnbgtapekouga";
  std::cout << Solution().validPalindrome(s) << std::endl;
};
ShreevatsaR
  • 35,974
  • 16
  • 97
  • 122
William Su
  • 13
  • 2
  • 1
    It sounds like you may need to learn how to use a debugger to step through your code. With a good debugger, you can execute your program line by line and see where it is deviating from what you expect. This is an essential tool if you are going to do any programming. Further reading: [How to debug small programs](http://ericlippert.com/2014/03/05/how-to-debug-small-programs/) and [Debugging Guide](http://idownvotedbecau.se/nodebugging/) – NathanOliver May 30 '19 at 20:19
  • You know you can check for palindrome in one line, inside one if statement right? https://stackoverflow.com/a/8362657/2104697 – Guillaume Racicot May 30 '19 at 20:21
  • it is not palindrome: there is an extra letter, second to last 'u' letter in the word. – Bianca May 30 '19 at 20:22
  • To the last two commenters (@GuillaumeRacicot and @Bianca): The question asks whether the string can be made a palindrome by deleting a letter. – ShreevatsaR May 30 '19 at 20:25
  • It looks like this algorithm always tries to delete from the first half of the list, if there's no match. I'm surprised 458 out of 560 test cases pass (or maybe my understanding of the algorithm is wrong). I know there are many people on Stack Overflow who insist on code even for algorithm questions, but IMO I'd prefer for the code to at least be accompanied by a natural-language description so that one could debug the algorithm instead of debugging the code. – ShreevatsaR May 30 '19 at 20:29
  • Where is your debugging trace of *how* the program fails? It's a simple matter to insert `print` commands to illustrate the problem. – Prune May 30 '19 at 22:08
  • @Prune I disagree. The program fails by returning `false` instead of `true` (as mentioned in the question), because the algorithm is flawed. This is not something you can discover with print statements IMO. – ShreevatsaR May 30 '19 at 22:27
  • @ShreevatsaR: simple `print` statements would show the flaw in the algorithm, seen as failing to check at all for the critical deletion. – Prune May 30 '19 at 22:58
  • @Prune With a 102-character test case (and conservatively, 2x as many print statements), it's no easier to read the print statements than to read the code. (At some point you'll end up just verifying that the code does what it is written to do.) This has the same problem as trying to step through the code with a debugger: you need to know what you're looking for (i.e., knowing what the algorithm should have done instead), which is exactly the original problem. – ShreevatsaR May 30 '19 at 23:04
  • You have only a few `print` statements -- although there will be 2-4 lines per character in the test string. The problem does not require 102 characters; visual inspection or a trivial loop will show the character that needs to be deleted; the test case can be dropped to no more than 7 chars, greatly reducing the output. Since OP knows the algorithm he implemented, he ostensibly has an idea of the logical inflection points. And yes, one *can* discover this with simple `print` statements, as I know from debugging 50-100k lines of trace, to find anomalies in the trail of a complex parser. – Prune May 30 '19 at 23:33
  • @Prune I think what you're missing is that the parts you describe are exactly what is being asked for help with -- the OP knows *his* algorithm, but does not know what is wrong with the algorithm. Knowledge of its 'logical inflection points' doesn't help when what is being investigated is that very logic itself. If he could apply test-case minimization and get it down to a small test case, there wouldn't be a question here (I got it down to a 5-char counterexample but only after seeing the answer). So how, according to you, to ask a question like this without already being able to answer it? – ShreevatsaR May 31 '19 at 01:27
  • @ShreevatsaR You don't need to know the failure mechanism to start tracking. First, you pare down the test case to a smaller structure -- attack those inflection points from the data first. For instance, `gulcupuculug` removes 90 letters. Inflection points in the code are straightforward: any non-linearity in the logic flow. This suggests 4-5 `print` statements. With 12 letters, that's only 60 lines of output, and you *know* which letter is the trigger, so you can look at the spot that deals with `lc` on the left, `cul` of the right, and hand-trace the cursors. – Prune May 31 '19 at 16:15
  • Paring *that* down to the posting I'd expect would be a test case of 5-7 chars and two print statements. OP might or might not have noticed the problem and solution at that point. However, I do expect a poster to make an attempt to minimize the posted example, per SO guidelines. – Prune May 31 '19 at 16:19
  • @Prune Thanks for taking the time to explain. In my opinion, realizing that the original 101-char test case can be shrunk, by removing chars from each end and also the interior, is exactly the kind of take-a-few-moments-to-convince-yourself algorithmic insight that is straightforward in hindsight but is often missing: in this case, once one proves to oneself that such characters are irrelevant, one is not too far from reaching an actual correct (and optimal, i.e. O(n)) algorithm for the problem. So IMO that's the real crux of the question, and even an answer just pointing out that it can (1/2) – ShreevatsaR May 31 '19 at 22:06
  • @Prune ... be done, would be helpful to a sincere learner. And expecting that to already be done by the person asking the question does not seem reasonable when it's precisely that algorithmic insight that's missing (maybe it's too easy for you, but it's not easy for everyone — as I know from teaching simple algorithms to students and seeing their faces light up) and is being asked for help with. (If this was a question that at heart was about the code rather than about the algorithm, I might agree with you.) But I guess reasonable people can disagree on this, so let's stop here. – ShreevatsaR May 31 '19 at 22:06

1 Answers1

2

If there is a case where the cursor points at different letters, and a character can be deleted from either the left or right cursors, your algorithm will only check with a delete from the left. If a palindrome is formed by deleting from the right, instead, your code will miss it.

So if you delete from the left, you need to also check if a delete from the right is possible and (potentially) check that if there is no palindrome when deleting from the left.

1201ProgramAlarm
  • 30,320
  • 7
  • 40
  • 49
  • `else if (s[lcursor] == s[rcursor - 1] && candelete)` doesn't check if it can delete from the right side? – NathanOliver May 30 '19 at 20:32
  • 1
    @NathanOliver It does, but if you can _also_ delete from the left side, it won't go back and check the right side if the left deletion doesn't make a palindrome. – 1201ProgramAlarm May 30 '19 at 20:34
  • 2
    Smaller test case illustrating the issue: CUUCU. The algorithm in the question deletes the initial C as the U's match, but instead what works is to delete the final U. – ShreevatsaR May 30 '19 at 21:09