Code Review Stack Exchange is a question and answer site for peer programmer code reviews. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I'm trying to pass a programming challenge where we are to replace all instances of a substring in a string until there are none left. So an input like

 Pibabakekezza
 bake

Has output

Pizza

And if there is nothing left say so. I fail an unknown test case because I take longer than 1 second.

//Rextester.Program.Main is the entry point for your code. Don't change it.
//Compiler version 4.0.30319.17929 for Microsoft (R) .NET Framework 4.5

using System;
using System.Runtime;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Runtime.CompilerServices;

namespace Rextester
{
    public class Program
    {
        public static void Main(string[] args)
        {
            GCLatencyMode oldMode = GCSettings.LatencyMode;

            // Make sure we can always go to the catch block, 
            // so we can set the latency mode back to `oldMode`
            RuntimeHelpers.PrepareConstrainedRegions();

            try
            {
                GCSettings.LatencyMode = GCLatencyMode.LowLatency;
                //Your code goes here
                string s =Console.ReadLine();
                string b =Console.ReadLine();
                while(s.Contains(b))
                {
                    s=s.Replace(b, "");
                }
                if(s!="")
                {
                    Console.WriteLine(s);
                }
                else
                {
                    Console.WriteLine("FRULA");
                }
            }
            finally
            {
                // ALWAYS set the latency mode back
                GCSettings.LatencyMode = oldMode;
            }
        }
    }
}

Restrictions:

1≤|bigString|≤1 000 000

1≤|substring|≤36

bigString and the substring string consist of uppercase and lowercase letters of the English alphabet and digits 0, 1, …, 9. The characters in the substring are all different.

CPU Time limit: 1 second Memory limit: 1024 MB

share|improve this question
    
I'm thinking that you can use a completely different algorithm for this, possibly with some handy data structures as well. I was starting on an answer but realized another edge-case. I might try this challenge out myself. – Simon Forsberg 12 hours ago
    
Do you have more info about this challenge? For example if there are more than one point in the string where the substring can appear? Maybe you can perform the replacement in a substring of the original string. // I mean, if the original string is "ssss..(a lot of 's')...ssssssbabakeketttt...(a lot of ' t'...)..ttttt", you can split your original string in three, two 'safe' strings ("sss...sss" and "tttt....tttt") and one replacement-in-progess where you do the replacement operation..// This could be efficient for some kind of input, so more info can help! – mayo 6 hours ago
    
What are the constraints (time limit, strings' length)? I suspect that simplistic approach won't work here and you'll need to implement some variation of KMP algorithm – default locale 6 hours ago
1  
@mayo I've added constraints – Seth Kitchen 6 hours ago
    
This method is basically brute-force. Surely using a stack is most efficient? You could read the string, and stack probable matches as ou find them. Every time a full match is found, unstack it. This is made easy because the characters in the substring are all different (you're not using this hint). When the string is fully browsed, all remaining characters are the answer. This doesn't even need to split/instantiate strings. Sorry, not proficient enough in C# to propose a full answer. – Sylvain Boisse 45 mins ago

As stated by 200_success +1 string is immutable

StringBuilder also has Replace but getting it to loop is trickier
You would need to time this

string s = "Pibabakekezza";
string r = "bake";
StringBuilder sb = new StringBuilder(s);
while (sb.Length != sb.Replace(r, "").Length) { }
Debug.WriteLine(sb.ToString());
share|improve this answer
    
Thanks! Unfortunately this is a little slower than my original post. Original code passes up to the 4th to last test case. This passes up to the 5th to last test case. – Seth Kitchen 9 hours ago
1  
@SethKitchen Wow, I was not sure. Need to try for a new approach. – Paparazzi 9 hours ago

Strings in C# are immutable, so every time you do s=s.Replace(b, "") you are constructing a new object. To reduce memory churn, you should use StringBuilder.Replace() instead.

share|improve this answer

You say it is an unknown test case, so suppose the bigString is equal to 500,000 "a"s followed by 500,000 "b"s, and the subString is "ab". It will take 500,000 iterations for the string to be completely reduced, and on the nth iteration it will take 500,000 - n steps to locate the substring if we're searching from the beginning as the Contains and Replace methods presumably do. But then the total number is steps is quadratic or about 1.25e11 which would explain why it is taking more than a second on an ordinary CPU. Maybe there is a different algorithm that can handle this case faster?

share|improve this answer
    
Yes this is true and is probably why I am not passing. I will try to think of a better algorithm – Seth Kitchen 5 hours ago

OK I'm no good at C#, I'll present an alternative solution is pseudo-code, using a Stack of matches.

List<int> nonMatch = new List()
Stack<List<int>> partialMatches = new Stack<List<int>>()
List<int> currentPartialMatch = new List<int>(substring.length)
for i = i to bigString.length
    if bigString.charAt(i) == substring.charAt(0)
        // A new sustring seem to be starting
        partialMatches.push(currentPartialMatch) // save the current match
        currentPartialMatch = new List<int>(substring.length()) // start a new one
        partialMatch.add(i)
    else if bigString.charAt(i) == substring.charAt(currentPartialMatch.size())
        // The current substring match is continuing
        currentPartialMatch.add(i)
    else
        // The current match streak is broken!
        nonMatch.addAll(currentPartialMatch)
        nonMatch.add(i)
        // Pick up the previous matching streak (if any)
        currentPartialMatch = partialMatches.pop()
        if currentPartialMatch == null
            currentPartialMatch = new List<int>(substring.length()) // start a new one
        endIf
    endIf
endFor
StringBuilder builder = new StringBuilder(
// Reconstruct the list of non matches
for index in nonMatch.sorted()
    builder.apend(bigString.charAt(index))
return builder.toString()

Quick-and-dirty complexity analysis:

  • The for runs in linear time, the sorting in log(n), so overall O(n) time.
  • Memory-wise, each index is stored only once in only one List, so O(n) memory

If anyone want to edit this to make it more C#-friendly...

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.