How to find the minimum number of operation(s) to make the string balanced?

后端 未结 5 1645
夕颜
夕颜 2020-12-29 13:27

From Codechef:

A string is considered balanced if and only if all the characters occur in it equal number of times.

You are given a strin

5条回答
  •  Happy的楠姐
    2020-12-29 13:40

    Following code implement the solution, in Java, with unit test.

    The algorithm is almost identical as in @ruakh's answer, if not identical.


    Code

    BalanceString.java

    import java.util.Arrays;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * Assume string only contains A-Z, the 26 uppercase letter,
     * 

    given a string, you can replace a char with another char from the 26 letter, *

    figure out the minimum replacement required to make the string balance, *

    which means each char in the string occurs the same time, * * @author eric * @date 2/2/19 8:54 PM */ public class BalanceString { private final char minChar; private final char maxChar; private final int distinctChars; // total distinct char count, public static final BalanceString EN_UPPER_INSTANCE = new BalanceString('A', 'Z'); public BalanceString(char minChar, char maxChar) { this.minChar = minChar; this.maxChar = maxChar; this.distinctChars = maxChar - minChar + 1; if (distinctChars <= 0) throw new IllegalArgumentException("invalid range of chars: [" + minChar + ", " + maxChar + "]"); } /** * Check minimal moves needed to make string balanced. * * @param str * @return count of moves */ public int balanceCount(String str) { // System.out.printf("string to balance:\t%s\n", str); int len = str.length(); // get length, if (len <= 2) return 0; // corner cases, Map coMap = figureOccurs(str); // figure occurrences, Integer[] occurs = sortOccursReversely(coMap); // reversely order occurrences, int m = coMap.size(); // distinct char count, int maxN = (len < distinctChars ? len : distinctChars); // get max possible distinct char count, included, int smallestMoves = Integer.MAX_VALUE; // smallest moves, among all possible n, // check each possible n, and get its moves, for (int n = 1; n <= maxN; n++) { if (len % n == 0) { int moves = figureMoves(len, coMap, occurs, m, n); if (moves < smallestMoves) smallestMoves = moves; } } return smallestMoves; } /** * Figure occurs for each char. * * @param str * @return */ protected Map figureOccurs(String str) { Map coMap = new HashMap<>(); for (char c : str.toCharArray()) { if (c < minChar || c > maxChar) throw new IllegalArgumentException(c + " is not within range 'A-Z'"); if (!coMap.containsKey(c)) coMap.put(c, 1); else coMap.put(c, coMap.get(c) + 1); } return coMap; } /** * Get reverse sorted occurrences. * * @param coMap * @return */ protected Integer[] sortOccursReversely(Map coMap) { Integer[] occurs = new Integer[coMap.size()]; coMap.values().toArray(occurs); Arrays.sort(occurs, Collections.reverseOrder()); return occurs; } /** * Figure moves needed to balance. * * @param len length of string, * @param coMap * @param m original distinct elements count, * @param n new distinct elements count, * @return count of moves, */ protected int figureMoves(int len, Map coMap, Integer[] occurs, int m, int n) { int avgOccur = len / n; // average occurrence, int moves = 0; if (n == m) { // distinct chars don't change, for (Integer occur : occurs) { if (occur <= avgOccur) break; moves += (occur - avgOccur); } } else if (n < m) { // distinct chars decrease, for (int i = 0; i < n; i++) moves += Math.abs(occurs[i] - avgOccur); // for elements kept, for (int i = n; i < m; i++) moves += occurs[i]; // for elements to replace, moves >>= 1; } else { // distinct chars increase, for (int i = 0; i < occurs.length; i++) moves += Math.abs(occurs[i] - avgOccur); // for existing elements, moves += ((n - m) * avgOccur); // for new elements, moves >>= 1; } return moves; } public char getMinChar() { return minChar; } public char getMaxChar() { return maxChar; } public int getDistinctChars() { return distinctChars; } }

    BalanceStringTest.java
    (Unit test, via TestNG)

    import org.testng.Assert;
    import org.testng.annotations.Test;
    
    /**
     * BalanceString test.
     *
     * @author eric
     * @date 2/2/19 9:36 PM
     */
    public class BalanceStringTest {
        private BalanceString bs = BalanceString.EN_UPPER_INSTANCE;
    
        @Test
        public void test() {
            // n < m case,
            Assert.assertEquals(bs.balanceCount("AAAABBBC"), 1); // e.g 1A -> B,
            Assert.assertEquals(bs.balanceCount("AAAAABBC"), 2); // e.g 1A -> B, 1C -> B,
            Assert.assertEquals(bs.balanceCount("AAAAAABC"), 2); // e.g 1B -> A, 1C -> A,
            Assert.assertEquals(bs.balanceCount("AAAAAAAB"), 1); // e.g 1B -> A,
    
            // n > m case,
            Assert.assertEquals(bs.balanceCount("AAAABBBBCCCCDDDDEEEEAAAA"), 4); // add 1 new char, e.g change 4 A to 4 F,
            Assert.assertEquals(bs.balanceCount(genIncSeq(10)), 15); // A-J, 10 distinct chars, 55 in length; solved by add 1 new char, need 15 steps,
    
            // n == m case,
            Assert.assertEquals(bs.balanceCount(genIncSeq(3)), 1); // A-C, 3 distinct chars, 6 in length; symmetric, solved with same distinct chars, need 1 steps,
            Assert.assertEquals(bs.balanceCount(genIncSeq(11)), 15); // A-K, 11 distinct chars, 66 in length; symmetric, solved with same distinct chars, need 15 steps,
    
            // n < m, or n > m case,
            Assert.assertEquals(bs.balanceCount("ABAC"), 1); // e.g 1A -> B, or 1A -> D,
        }
    
        // corner case,
        @Test
        public void testCorner() {
            // m <= 2,
            Assert.assertEquals(bs.balanceCount(""), 0);
            Assert.assertEquals(bs.balanceCount("A"), 0);
            Assert.assertEquals(bs.balanceCount("AB"), 0);
            Assert.assertEquals(bs.balanceCount("AA"), 0);
    
            /*------ m == n == distinctChars ------*/
            String mndBalanced = genMndBalancedSeq(); // each possible char occurs exactly once, already balanced,
            Assert.assertEquals(mndBalanced.length(), bs.getDistinctChars());
            Assert.assertEquals(bs.balanceCount(mndBalanced), 0); // no need change,
    
            char lastChar = mndBalanced.charAt(mndBalanced.length() - 1);
            String mndOneDup = mndBalanced.replace(lastChar, (char) (lastChar - 1)); // (distinctChars -2) chars occur exactly once, one occurs twice, one is missing, thus it's one step away to balance,
            Assert.assertEquals(mndOneDup.length(), bs.getDistinctChars());
            Assert.assertEquals(bs.balanceCount(mndOneDup), 1); // just replace the duplicate char with missing char,
        }
    
        // invalid input,
        @Test(expectedExceptions = IllegalArgumentException.class)
        public void testInvalidInput() {
            Assert.assertEquals(bs.balanceCount("ABAc"), 1);
        }
    
        // invalid char range, for constructor,
        @Test(expectedExceptions = IllegalArgumentException.class)
        public void testInvalidRange() {
            new BalanceString('z', 'a');
        }
    
        /**
         * Generate a string, with first char occur once, second twice, third three times, and so on.
         * 

    e.g A, ABB, ABBCCC, ABBCCCDDDD, * * @param m distinct char count, * @return */ private String genIncSeq(int m) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < m; i++) { for (int j = 0; j <= i; j++) sb.append((char) (bs.getMinChar() + i)); } return sb.toString(); } /** * Generate a string that contains each possible char exactly once. *

    For [A-Z], it could be: "ABCDEFGHIJKLMNOPQRSTUVWXYZ". * * @return */ private String genMndBalancedSeq() { StringBuilder sb = new StringBuilder(); char minChar = bs.getMinChar(); int distinctChars = bs.getDistinctChars(); for (int i = 0; i < distinctChars; i++) { sb.append((char) (minChar + i)); } return sb.toString(); } }

    All test cases would pass.


    Complexity

    • Time: O(len) + O(m * lg(m)) + O(m * factorCount)
      • Each sequential scan takes O(len), there are several sequential loop.
      • Sorting of array takes O(m*lg(m)), which is at most O(distinctChars * lg(distinctChars)), thus constant, and only sort once.
      • To figure out moves for each n, takes O(m).
      • The count of n that needs to figure moves, depends on count of divisible numbers for len, in the range [minChar, maxChar].
        This count if kind small & constant too.
    • Space: O(len)
      • Input string need O(len).
      • Counter hashmap need O(m).
      • Sorted occurrence array need O(m).

    Where:

    • len is string length.
    • m is distinct char count in original string
    • distinctChars is distinct char count, e.g 26.
    • maxN max possible distinct char count, included,
    • factorCount divisible number count in range [1, n], by len,
    • minChar min char, e.g A
    • maxChar max char, e.g Z

    And:

    • len >= m
    • m <= distinctChars

提交回复
热议问题