数组与字符串

1.相关数据结构

1.1 ArrayList

1.2 String、StringBuffer和StringBuilder

参考String、StringBuffer和StringBuilder

2.编程题

Q1.实现一个算法,确定一个字符串的所有字符是有全都相同。假使不允许使用额外的数据结构,如何处理?

Ans.思路如下:

  • step1.是ASCII字符还是Unicode字符,字符不同,存储空间不同。
  • step2.优化,若字符串的长度大于字母表中字符个数,则肯定有相同的
  • step3.使用boolean数据进行标记该字符是否出现;使用位向量标记

使用位向量:

    public static boolean isUniqueChars(String str) {
        if (str.length() > 128) {
            return false;
        }
        int checker = 0;
        for (int i = 0; i < str.length(); i++) {
            int val = str.charAt(i) - 'a';
            if ((checker & (1 << val)) > 0) return false;
            checker |= (1 << val);
        }
        return true;
    }

使用boolean数组:

    public static boolean isUniqueChars2(String str) {
        if (str.length() > 128) {
            return false;
        }
        boolean[] char_set = new boolean[128];
        for (int i = 0; i < str.length(); i++) {
            int val = str.charAt(i);
            if (char_set[val]) return false;
            char_set[val] = true;
        }
        return true;
    }

Q2.使用C/C++实现void reverse(char* str)函数,反转一个null结尾的字符串。

Ans.思路:

  • 找到尾指针,头尾指针交换,并向中间走
void reverse(char *str) {
  char* end = str;
  char tmp;
  if (str) {
    while (*end) {
      ++end;
    }
    --end;

    while (str < end) {
      tmp = *str;
      *(str++) = *end;
      *(end--) = tmp;
    }
}

Q3.给定两个字符串,请编写程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。变位词(anagram)

Ans.思路:

  • 优化:长度不同,肯定不是变位词
  • 一种直接的想法:排序后是否相等 O(nlgn)
  • 改进的算法:检查两字符串的各字符数是否相同 O(n)

直接的思路:

    public static String sort(String s) {
        char[] content = s.toCharArray();
        java.util.Arrays.sort(content);
        return new String(content);
    }
    
    public static boolean permutation(String s, String t) {
        return sort(s).equals(sort(t));
    }

改进算法:

    public static boolean permutation(String s, String t) {
        if (s.length() != t.length()) {
            return false;
        }
        
        int[] letters = new int[128];
         
        char[] s_array = s.toCharArray();
        for (char c : s_array) { // count number of each char in s.
            letters[c]++;  
        }
          
        for (int i = 0; i < t.length(); i++) {
            int c = (int) t.charAt(i);
            if (--letters[c] < 0) {
                return false;
            }
        }
          
        return true;
    }

算法分析:

  • 1)若相等,则所有letters[c]最后均为0
  • 2)若有小于0,则肯定不等
  • 3)若最终有大于0的,表明对于字符c,s[c] > t[c]。但是由于s和t总长度相等,则必然有其他某个字符s[d] < t[d],表明一定有小于0。所以算法是正确的。

Q4.编写一个方法,将字符串中的空格全部替换为"%20"。假定该字符串尾部有足够空间存放新增字符,并且知道字符串的真实长度。

Ans.思路

  • 字符串操作的常见做法:从尾部开始编辑,从后往前反向操作。从尾部操作有额外缓冲,移动少,不必担心覆盖。
  • 两次遍历:第一次,统计出空格数,计算出最终字符串长度;第二次,反向修改。
    public static void replaceSpaces(char[] str, int length) {
        int spaceCount = 0, index, i = 0;
        for (i = 0; i < length; i++) {
            if (str[i] == ' ') {
                spaceCount++;
            }
        }
        index = length + spaceCount * 2;
        str[index] = '\0';
        for (i = length - 1; i >= 0; i--) {
            if (str[i] == ' ') {
                str[index - 1] = '0';
                str[index - 2] = '2';
                str[index - 3] = '%';
                index = index - 3;
            } else {
                str[index - 1] = str[i];
                index--;
            }
        }
    }

Q5.利用字符重复出现的次数,编写一个方法,实现基本的字符串压缩功能。比如,字符串aabcccccaaa变为a2b1c5a3。若压缩后的字符串没有变短,则返回原先的字符串。

Ans.思路:

  • 此题思路比较直接,关键在于String的拼接处理方式:直接使用String +;使用StringBuffer;使用字符数组代替

直接使用String +:

    public static int countCompression(String str) {
        if (str == null || str.isEmpty()) return 0;
        char last = str.charAt(0);
        int size = 0;
        int count = 1;
        for (int i = 1; i < str.length(); i++) {
            if (str.charAt(i) == last) {
                count++;
            } else {
                last = str.charAt(i);
                size += 1 + String.valueOf(count).length();
                count = 1;
            } 
        }
        size += 1 + String.valueOf(count).length();
        return size;
    }
    
    public static String compressBad(String str) {
        int size = countCompression(str);
        if (size >= str.length()) {
            return str;
        }
        String mystr = "";
        char last = str.charAt(0);
        int count = 1;
        for (int i = 1; i < str.length(); i++) {
            if (str.charAt(i) == last) {
                count++;
            } else {
                mystr += last + "" + count;
                last = str.charAt(i);
                count = 1;
            }
        }
        return mystr + last + count;
    }

使用StringBuffer:

    public static String compressBetter(String str) {
        int size = countCompression(str);
        if (size >= str.length()) {
            return str;
        }
        StringBuffer mystr = new StringBuffer();
        char last = str.charAt(0);
        int count = 1;
        for (int i = 1; i < str.length(); i++) {
            if (str.charAt(i) == last) {
                count++;
            } else {
                mystr.append(last);
                mystr.append(count);
                last = str.charAt(i);
                count = 1;
            }
        }
        mystr.append(last);
        mystr.append(count);
        return mystr.toString();
    }   

使用字符数组

    public static String compressAlternate(String str) {
        int size = countCompression(str);
        if (size >= str.length()) {
            return str;
        }
        char[] array = new char[size];
        int index = 0;
        char last = str.charAt(0);
        int count = 1;
        for (int i = 1; i < str.length(); i++) {
            if (str.charAt(i) == last) {
                count++;
            } else {
                index = setChar(array, last, index, count);
                last = str.charAt(i);
                count = 1;
            }
        }
        index = setChar(array, last, index, count);
        return String.valueOf(array);
    }

Q6.给定一幅由NxN矩阵表示的图像,其中每个像素的大小为4字节,编写一个方法,将图像旋转90度。不占用额外内存空间能否做到?


Ans.思路:

  • 逐层处理
  • 对于每一层,找到上边的起点和终点。然后从起点开始逐个旋转。
  • 旋转方法:记住上边top,左->上,下->左,右->下,top->右
    public static void rotate(int[][] matrix, int n) {
        for (int layer = 0; layer < n / 2; ++layer) {
            int first = layer;
            int last = n - 1 - layer;
            for(int i = first; i < last; ++i) {
                int offset = i - first;
                int top = matrix[first][i]; // save top

                // left -> top
                matrix[first][i] = matrix[last-offset][first];          

                // bottom -> left
                matrix[last-offset][first] = matrix[last][last - offset]; 

                // right -> bottom
                matrix[last][last - offset] = matrix[i][last]; 

                // top -> right
                matrix[i][last] = top; // right <- saved top
            }
        }
    }

Q7.编写一个算法,若MxN矩阵中某个元素为0,则将其所在的行与列清0.

Ans.思路

  • 陷阱:不要直接处理,将某一行清0后,会导致整个矩阵为0
  • 改进:遍历一遍矩阵,记住哪些行和哪些列为0;第二遍处理时,对于a[i][j],只要行i和列j其中有一个为0,则置为0
  • 改进:只用遍历一次即可,方法是将哪些行、列为0的标记标记在第一行和第一列中;对于第一行和第一列是否全为0,首先检查出来,做好标记,然后最后进行处理
    public static void nullifyRow(int[][] matrix, int row) {
        for (int j = 0; j < matrix[0].length; j++) {
            matrix[row][j] = 0;
        }       
    }

    public static void nullifyColumn(int[][] matrix, int col) {
        for (int i = 0; i < matrix.length; i++) {
            matrix[i][col] = 0;
        }       
    }       

    public static void setZeros(int[][] matrix) {
        boolean[] row = new boolean[matrix.length]; 
        boolean[] column = new boolean[matrix[0].length];

        // Store the row and column index with value 0
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[0].length;j++) {
                if (matrix[i][j] == 0) {
                    row[i] = true; 
                    column[j] = true;
                }
            }
        }
        
        // Nullify rows
        for (int i = 0; i < row.length; i++) {
            if (row[i]) {
                nullifyRow(matrix, i);
            }
        }
        
        // Nullify columns
        for (int j = 0; j < column.length; j++) {
            if (column[j]) {
                nullifyColumn(matrix, j);
            }
        }
    }   

一次遍历即可:

    public static void setZeros2(int[][] matrix) {
        boolean rowHasZero = false;
        boolean colHasZero = false;     
        
        // Check if first row has a zero
        for (int j = 0; j < matrix[0].length; j++) {
            if (matrix[0][j] == 0) {
                rowHasZero = true;
                break;
            }
        }       
        
        // Check if first column has a zero
        for (int i = 0; i < matrix.length; i++) {
            if (matrix[i][0] == 0) {
                colHasZero = true;
                break;
            }
        }       
        
        // Check for zeros in the rest of the array
        for (int i = 1; i < matrix.length; i++) {
            for (int j = 1; j < matrix[0].length;j++) {
                if (matrix[i][j] == 0) {
                    matrix[i][0] = 0;
                    matrix[0][j] = 0;
                }
            }
        }       
        
        // Nullify rows based on values in first column
        for (int i = 1; i < matrix.length; i++) {
            if (matrix[i][0] == 0) {
                nullifyRow(matrix, i);
            }
        }       
        
        // Nullify columns based on values in first row
        for (int j = 1; j < matrix[0].length; j++) {
            if (matrix[0][j] == 0) {
                nullifyColumn(matrix, j);
            }
        }   
        
        // Nullify first row
        if (rowHasZero) {
            nullifyRow(matrix, 0);
        }
        
        // Nullify first column
        if (colHasZero) {
            nullifyColumn(matrix, 0);
        }
    }

Q8.假定有一个方法isSubstring,可检查一个单词是否为其他字符串的子串,给定两个字符串s1和s2,请编写检查s2是否为s1旋转而来,只能调用一次isSubstring。(waterbottler是erbottlewat旋转后的字符串)

Ans.思路

  • 在选择点进行切分:比如字符串xy选择后变成了yx。则必然有xyxy包含yx。
    public static boolean isSubstring(String big, String small) {
        if (big.indexOf(small) >= 0) {
            return true;
        } else {
            return false;
        }
    }
    
    public static boolean isRotation(String s1, String s2) {
        int len = s1.length();
        /* check that s1 and s2 are equal length and not empty */
        if (len == s2.length() && len > 0) { 
            /* concatenate s1 and s1 within new buffer */
            String s1s1 = s1 + s1;
            return isSubstring(s1s1, s2);
        }
        return false;
    }

Q9.打印输入文件的最后K行。

Ans.思路:

  • 使用一个K大小的数组,依次存入0K-1行,1K行,...
  • 使用循环式数组,每次读入新行,都会替换掉最早读入的元素。

Q10.珠玑妙算游戏。四种颜色。猜对某个槽的颜色,则算一次“猜中”,要是只猜对颜色但是槽位错了,算一次“伪猜中”,猜中不能算入伪猜中。

Ans.思路:

  • 猜中要先剔除
    public static class Result {
        public int hits;
        public int pseudoHits;
        
        public Result(int h, int p) {
            hits = h;
            pseudoHits = p;
        }
        
        public Result() {
        }
        
        public String toString() {
            return "(" + hits + ", " + pseudoHits + ")";
        }
    };
    
    public static int code(char c) {
        switch (c) {
        case 'B':
            return 0;
        case 'G':
            return 1;
        case 'R':
            return 2;
        case 'Y':
            return 3;
        default:
            return -1;
        }
    }
    
    public static int MAX_COLORS = 4;
    
    public static Result estimate(String guess, String solution) {
        if (guess.length() != solution.length()) return null;
        Result res = new Result();
        int[] frequencies = new int[MAX_COLORS];
            
        /* Compute hits and built frequency table */
        for (int i = 0; i < guess.length(); i++) {
            if (guess.charAt(i) == solution.charAt(i)) {
                res.hits++;
            } else {
                /* Only increment the frequency table (which will be used for pseudo-hits) if
                 * it's not a hit. If it's a hit, the slot has already been "used." */
                int code = code(solution.charAt(i));
                if (code >= 0) {
                    frequencies[code]++;
                }
            }
        }
        
        /* Compute pseudo-hits */
        for (int i = 0; i < guess.length(); i++) {
            int code = code(guess.charAt(i));
            if (code >= 0 && frequencies[code] > 0 && guess.charAt(i) != solution.charAt(i)) {
                res.pseudoHits++;
                frequencies[code]--;
            }
        }
        return res;
    }

Q11.给定一个整数数组,编写一个函数,找出索引m和n,只要将m和n之间的元素排好序,整个数组就是有序的。注意:n-m越小越好,也就是说,找出符号条件的最短序列。

Ans.思路:

  • step1.首先找出首尾最长递增子序列
  • step2.然后将min取值为 中间+右边最小 中最小者,以此最小值将中间向左推进到<=该最小值的下一位,能保证左边的所有都小于右边
  • step3.同理max取值为 中间+左边最大 中最大者,以此最大值将中间向右推进到>=该最大值的上一位,能保证右边的所有都大于左边
  • 此题本质上要求left < middle < right
public class Question {

    public static int findEndOfLeftSubsequence(int[] array) {
        for (int i = 1; i < array.length; i++) {
            if (array[i] < array[i - 1]) {
                return i - 1;
            }
        }
        return array.length - 1;
    }
    
    public static int findStartOfRightSubsequence(int[] array) {
        for (int i = array.length - 2; i >= 0; i--) {
            if (array[i] > array[i + 1]) {
                return i + 1;
            }
        }
        return 0;
    }       
    
    public static int shrinkLeft(int[] array, int min_index, int start) {
        int comp = array[min_index];
        for (int i = start - 1; i >= 0; i--) {
            if (array[i] <= comp) {
                return i + 1;
            }
        }
        return 0;
    }
    
    public static int shrinkRight(int[] array, int max_index, int start) {
        int comp = array[max_index];
        for (int i = start; i < array.length; i++) {
            if (array[i] >= comp) {
                return i - 1;
            }
        }
        return array.length - 1;
    }   
    
    public static void findUnsortedSequence(int[] array) {
        // find left subsequence
        int end_left = findEndOfLeftSubsequence(array); 
        
        if (end_left >= array.length - 1) {
            //System.out.println("The array is already sorted.");
            return; // Already sorted
        }
        
        // find right subsequence
        int start_right = findStartOfRightSubsequence(array);   
        
        int max_index = end_left; // max of left side
        int min_index = start_right; // min of right side
        for (int i = end_left + 1; i < start_right; i++) {
            if (array[i] < array[min_index]) {
                min_index = i;
            }
            if (array[i] > array[max_index]) {
                max_index = i;
            }
        }       
        
        // slide left until less than array[min_index]
        int left_index = shrinkLeft(array, min_index, end_left);

        // slide right until greater than array[max_index]
        int right_index = shrinkRight(array, max_index, start_right);
        
        if (validate(array, left_index, right_index)) {
            System.out.println("TRUE: " + left_index + " " + right_index);
        } else {
            System.out.println("FALSE: " + left_index + " " + right_index);
        }
    }
    
    /* Validate that sorting between these indices will sort the array. Note that this is not a complete
     * validation, as it does not check if these are the best possible indices.
     */
    public static boolean validate(int[] array, int left_index, int right_index) {
        int[] middle = new int[right_index - left_index + 1];
        for (int i = left_index; i <= right_index; i++) {
            middle[i - left_index] = array[i];
        }
        java.util.Arrays.sort(middle);
        for (int i = left_index; i <= right_index; i++) {
            array[i] = middle[i - left_index];
        }
        for (int i = 1; i < array.length; i++) {
            if (array[i-1] > array[i]) {
                return false;
            }       
        }
        return true;
    }
    
    public static void main(String[] args) {
        int[] array = {1, 2, 4, 7, 10, 11, 8, 12, 5, 7, 16, 18, 19};
        findUnsortedSequence(array);
    }

}

Q12.给定一个整数,打印该整数的英文描述。

Ans.思路:

  • 分段处理,每三位转换一次,并在适当的时候插入相应的thousand、million、hundred等
public class Question {
    public static String[] digits = {"One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"};
    public static String[] teens = {"Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"};
    public static String[] tens = {"Ten", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"};
    public static String[] bigs = {"", "Thousand", "Million", "Billion"};
    
    public static String numToString(int number) {
        if (number == 0) {
            return "Zero";
        }
        
        if (number < 0) {
            return "Negative " + numToString(-1 * number);
        }
        int count = 0;
        String str = "";
        
        while (number > 0) {
            if (number % 1000 != 0) {
                str = numToString100(number % 1000) + bigs[count] + " " + str;
            }
            number /= 1000;
            count++;
        }
        
        return str;
    }
    
    public static String numToString100(int number) {   
        String str = "";
        
        /* Convert hundreds place */
        if (number >= 100) {
            str += digits[number / 100 - 1] + " Hundred ";
            number %= 100;
        }
        
        /* Convert tens place */
        if (number >= 11 && number <= 19) {
            return str + teens[number - 11] + " ";
        } else if (number == 10 || number >= 20) {
            str += tens[number / 10 - 1] + " ";
            number %= 10;
        }
        
        /* Convert ones place */
        if (number >= 1 && number <= 9) {
            str += digits[number - 1] + " ";
        }
        
        return str;
    }
    
    public static void main(String[] args) {        
        /* numbers between 100000 and 1000000 */
        for (int i = 0; i < 8; i++) {
            int value = (int) Math.pow(10, i);
            String s = numToString(-1 * value);
            System.out.println(value + ": " + s);
        }           
        
        /* numbers between 0 and 100 */
        for (int i = 0; i < 10; i++) {
            int value = AssortedMethods.randomIntInRange(0, 100);
            String s = numToString(value);
            System.out.println(value + ": " + s);
        }   
        
        /* numbers between 100 and 1000 */
        for (int i = 0; i < 10; i++) {
            int value = AssortedMethods.randomIntInRange(100, 1000);
            String s = numToString(value);
            System.out.println(value + ": " + s);
        }
        
        /* numbers between 1000 and 100000 */
        for (int i = 0; i < 10; i++) {
            int value = AssortedMethods.randomIntInRange(1000, 100000);
            String s = numToString(value);
            System.out.println(value + ": " + s);
        }       
        
        
        /* numbers between 100000 and 100000000 */
        for (int i = 0; i < 10; i++) {
            int value = AssortedMethods.randomIntInRange(100000, 100000000);
            String s = numToString(value);
            System.out.println(value + ": " + s);
        }   
        
        /* numbers between 100000000 and 1000000000 */
        for (int i = 0; i < 10; i++) {
            int value = AssortedMethods.randomIntInRange(100000000, 1000000000);
            String s = numToString(value);
            System.out.println(value + ": " + s);
        }           
                
        /* numbers between 1000000000 and Integer.MAX_VALUE */
        for (int i = 0; i < 10; i++) {
            int value = AssortedMethods.randomIntInRange(1000000000, Integer.MAX_VALUE);
            String s = numToString(value);
            System.out.println(value + ": " + s);
        }           
    }
}

Q13.给定一个整数数组(有正数、负数),找出总和最大的连续数列,并返回综合。

Ans.思路:

  • 只要当前和为正数,就可以连续加下去;若累积为负数,则重新开始,否则会拖累后面的计算,因为去掉前面的负数累赘,后面的求和会更大。
    public static int getMaxSum(int[] a) {
        int maxSum = 0;
        int runningSum = 0;
        for (int i = 0; i < a.length; i++) {
            runningSum += a[i];
            if (maxSum < runningSum) {
                maxSum = runningSum;
            } else if (runningSum < 0) {
                runningSum = 0;
            }
        }
        return maxSum;
    }

Q14.设计一个算法,找出数组中两数之和为指定值的所有整数对。

Ans.思路:

  • 使用散列表
  • 对于排好序的数组,可以使用first和last指向头和尾进行查找
    public static void printPairSums(int[] array, int sum) {
        Arrays.sort(array);
        int first = 0;
        int last = array.length - 1;
        while (first < last) {
            int s = array[first] + array[last];
            if (s == sum) {
                System.out.println(array[first] + " " + array[last]);
                ++first;
                --last;
            } else {
                if (s < sum) {
                    ++first;
                } else {
                    --last;
                }
            }
        }
    }

Q15.有个内含单词的超大文本文件,给定任意两个单词,找出在这个文件中这两个单词的最短距离。有办法在O(1)时间内完成搜索操作吗?解法的空间复杂度如何?

Ans.思路:

  • 普通做法,从前向后遍历,不断更新min
  • 对于需要经常调用的,可以生成hash表,记录每个单词及其出现的位置。然后计算两个链表中最短的距离。做法跟普通做法类似。
    public static int shortest(String[] words, String word1, String word2) {
        int min = Integer.MAX_VALUE;
        int lastPosWord1 = -1;
        int lastPosWord2 = -1;
        for (int i = 0; i < words.length; i++) {
            String currentWord = words[i];
            if (currentWord.equals(word1)) {
                lastPosWord1 = i;
                // Comment following 3 lines if word order matters
                int distance = lastPosWord1 - lastPosWord2;
                if (lastPosWord2 >= 0 && min > distance) {
                    min = distance;
                }
            } else if (currentWord.equals(word2)) {
                lastPosWord2 = i;
                int distance = lastPosWord2 - lastPosWord1;
                if (lastPosWord1 >= 0 && min > distance) {
                    min = distance;
                }
            }
        }
        return min;
    }

Q16.给定一组单词,编写一个程序,找出其中的最长单词,且该单词由这组单词中的其他单词组合而成。

Ans.思路:

  • 先将单词放入哈希表,然后从最长单词开始,将每个单词分解为可能的两半,然后检查左右两半是否在列表中。
  • 若单词可以由任意数量的其他单词组成,又会怎么样?
    递归检查右半部分是否由数组其他元素构建出来。
    public static String printLongestWord(String arr[]) {
        HashMap<String, Boolean> map = new HashMap<String, Boolean>();
        for (String str : arr) {
            map.put(str, true);
        }
        Arrays.sort(arr, new LengthComparator()); // Sort by length
        for (String s : arr) {
            if (canBuildWord(s, true, map)) {
                System.out.println(s);
                return s;
            }
        }
        return "";
    }
    
    public static boolean canBuildWord(String str, boolean isOriginalWord, HashMap<String, Boolean> map) {
        if (map.containsKey(str) && !isOriginalWord) {
            return map.get(str);
        }
        for (int i = 1; i < str.length(); i++) {
            String left = str.substring(0, i);
            String right = str.substring(i);
            if (map.containsKey(left) && map.get(left) == true && 
                canBuildWord(right, false, map)) {
                return true;
            }
        }
        map.put(str, false);
        return false;
    }   
public class LengthComparator implements Comparator<String> {
    public int compare(String o1, String o2) {
        if (o1.length() < o2.length()) return 1;
        if (o1.length() > o2.length()) return -1;
        return 0;
    }
}

Q17.给定一个字符串s和一个包含较短字符串的数组T,设计一个方法,根据T中的每个较短字符串,对s进行搜索。

Ans.思路:

  • 创建s的后缀树,例如bibs
  • 注意indexes,indexes表示该后缀分支的起始索引,多个表示有多个起始索引都为该值。


public class SuffixTree {
    SuffixTreeNode root = new SuffixTreeNode();
    
    public SuffixTree(String s) {
        for (int i = 0; i < s.length(); i++) {
            String suffix = s.substring(i);
            root.insertString(suffix, i);
        }
    }
    
    public ArrayList<Integer> search(String s) {
        return root.search(s);
    }
}
public class SuffixTreeNode {
    HashMap<Character, SuffixTreeNode> children = new HashMap<Character, SuffixTreeNode>();
    
    char value;
    ArrayList<Integer> indexes = new ArrayList<Integer>();
    public SuffixTreeNode() { }
    
    public void insertString(String s, int index) {
        indexes.add(index);
        if (s != null && s.length() > 0) {
            value = s.charAt(0);
            SuffixTreeNode child = null;
            if (children.containsKey(value)) {
                child = children.get(value);
            } else {
                child = new SuffixTreeNode();
                children.put(value, child);
            }
            String remainder = s.substring(1);
            child.insertString(remainder, index);
        }
    }
    
    public ArrayList<Integer> search(String s) {
        if (s == null || s.length() == 0) {
            return indexes;
        } else {
            char first = s.charAt(0);
            if (children.containsKey(first)) {
                String remainder = s.substring(1);
                return children.get(first).search(remainder);
            }
        }
        return null;
    }
}

Q18.给定字典里的两个单词,长度相等,编写一个方法,将一个单词变换成另一个单词,一次只改动一个字母。在变换过程中,每一步得到的新单词都必须是字典里存在的。

Ans.思路:

  • 可以使用广度优先搜索,每次相差一个字符。
    public static LinkedList<String> transform(String startWord, String stopWord, Set<String> dictionary) {
        startWord = startWord.toUpperCase();
        stopWord = stopWord.toUpperCase();
        Queue<String> actionQueue = new LinkedList<String>();
        Set<String> visitedSet = new HashSet<String>();
        Map<String, String> backtrackMap = new TreeMap<String, String>();

        actionQueue.add(startWord);
        visitedSet.add(startWord);

        while (!actionQueue.isEmpty()) {
            String w = actionQueue.poll();
            // For each possible word v from w with one edit operation
            for (String v : getOneEditWords(w)) {
                if (v.equals(stopWord)) {
                    // Found our word!  Now, back track.
                    LinkedList<String> list = new LinkedList<String>();
                    // Append v to list
                    list.add(v);
                    while (w != null) {
                        list.add(0, w);
                        w = backtrackMap.get(w);
                    }
                    return list;
                }

                // If v is a dictionary word
                if (dictionary.contains(v)) {
                    if (!visitedSet.contains(v)) {
                        actionQueue.add(v);
                        visitedSet.add(v); // mark visited
                        backtrackMap.put(v, w);
                    }
                }
            }
        }
        return null;
    }

    private static Set<String> getOneEditWords(String word) {
        Set<String> words = new TreeSet<String>();
        // for every letter
        for (int i = 0; i < word.length(); i++) {
            char[] wordArray = word.toCharArray();
            // change that letter to something else
            for (char c = 'A'; c <= 'Z'; c++) {
                if (c != word.charAt(i)) {
                    wordArray[i] = c;
                    words.add(new String(wordArray));
                }
            }
        }
        return words;
    }
    
    public static HashSet<String> setupDictionary(String[] words) {
        HashSet<String> hash = new HashSet<String>();
        for (String word : words) {
            hash.add(word.toUpperCase());
        }
        return hash;
    }

Q19.给定一个方阵,其中每个单元非黑即白。设计一个算法,找出四条边皆为黑色像素的最大子方阵。

Ans.思路:

  • 一种简单思路,穷举法——从NxN逐渐递减查找 (N-1)x(N-1) ...,时间复杂度为O(N^4),大范围要遍历N次,小范围,例如N/2 x N/2,有N/2 x N/2种可能性,检查N/2 x 4周长才能确定,所以O(N3),总共为O(N4)
  • 预处理:穷举每次检查一个方阵是否符合要求,都需要O(N)时间,经过预处理,可以将其降为O(1)。
    处理的方法:记住每一个点右边最长黑边和下边最长的黑边。

穷举法:

    public static Subsquare findSquare(int[][] matrix){
        assert(matrix.length > 0);
        for (int row = 0; row < matrix.length; row++){
            assert(matrix[row].length == matrix.length);
        }
        
        int N = matrix.length;
        
        for (int i = N; i >= 1; i--) {
            Subsquare square = findSquareWithSize(matrix, i);
            if (square != null) {
                return square;
            }
        }
        return null;
    }   

    public static Subsquare findSquareWithSize(int[][] matrix, int squareSize) {
        // On an edge of length N, there are (N - sz + 1) squares of length sz.
        int count = matrix.length - squareSize + 1; 
        
        // Iterate through all squares with side length square_size.
        for (int row = 0; row < count; row++) {
            for (int col = 0; col < count; col++) {
                if (isSquare(matrix, row, col, squareSize)) {
                    return new Subsquare(row, col, squareSize);
                }
            }
        }
        return null;
    }
    


    private static boolean isSquare(int[][] matrix, int row, int col, int size) {
        // Check top and bottom border.
        for (int j = 0; j < size; j++){
            if (matrix[row][col+j] == 1) {
                return false;
            }
            if (matrix[row+size-1][col+j] == 1) {
                return false;
            }
        }
        
        // Check left and right border.
        for (int i = 1; i < size - 1; i++) {
            if (matrix[row+i][col] == 1){
                return false;
            }
            if (matrix[row+i][col+size-1] == 1) {
                return false;
            }
        }
        return true;
    }
public class Subsquare {
    public int row, column, size;
    public Subsquare(int r, int c, int sz) {
        row = r;
        column = c;
        size = sz;
    }
    
    public void print() {
        System.out.println("(" + row + ", " + column + ", " + size + ")");
    }
}

预处理法:

    public static Subsquare findSquare(int[][] matrix){
        assert(matrix.length > 0);
        for (int row = 0; row < matrix.length; row++){
            assert(matrix[row].length == matrix.length);
        }

        SquareCell[][] processed = processSquare(matrix);

        int N = matrix.length;

        for (int i = N; i >= 1; i--) {
            Subsquare square = findSquareWithSize(processed, i);
            if (square != null) {
                return square;
            }
        }
        return null;
    }

    public static Subsquare findSquareWithSize(SquareCell[][] processed, int square_size) {
        // On an edge of length N, there are (N - sz + 1) squares of length sz.
        int count = processed.length - square_size + 1; 
        
        // Iterate through all squares with side length square_size.
        for (int row = 0; row < count; row++) {
            for (int col = 0; col < count; col++) {
                if (isSquare(processed, row, col, square_size)) {
                    return new Subsquare(row, col, square_size);
                }
            }
        }
        return null;
    }


    private static boolean isSquare(SquareCell[][] matrix, int row, int col, int size) {
        SquareCell topLeft = matrix[row][col];
        SquareCell topRight = matrix[row][col + size - 1];
        SquareCell bottomRight = matrix[row + size - 1][col];
        if (topLeft.zerosRight < size) { // Check top edge
            return false;
        }
        if (topLeft.zerosBelow < size) { // Check left edge
            return false;
        }
        if (topRight.zerosBelow < size) { // Check right edge
            return false;
        }
        if (bottomRight.zerosRight < size) { // Check bottom edge
            return false;
        }
        return true;
    }
    
    public static SquareCell[][] processSquare(int[][] matrix) {
        SquareCell[][] processed = new SquareCell[matrix.length][matrix.length];
        
        for (int r = matrix.length - 1; r >= 0; r--) {
            for (int c = matrix.length - 1; c >= 0; c--) {
                int rightZeros = 0;
                int belowZeros = 0;
                if (matrix[r][c] == 0) { // only need to process if it's a black cell
                    rightZeros++;
                    belowZeros++;
                    if (c + 1 < matrix.length) { // next column over is on same row
                        SquareCell previous = processed[r][c + 1];
                        rightZeros += previous.zerosRight;
                    }
                    if (r + 1 < matrix.length) {
                        SquareCell previous = processed[r + 1][c];
                        belowZeros += previous.zerosBelow;
                    }
                }
                processed[r][c] = new SquareCell(rightZeros, belowZeros);
            }
        }   
        return processed;
    }

public class SquareCell {
    public int zerosRight = 0;
    public int zerosBelow = 0;
    public SquareCell(int right, int below) {
        zerosRight = right;
        zerosBelow = below;
    }
    
    public void setZerosRight(int right) {
        zerosRight = right;
    }
    
    public void setZerosBelow(int below) {
        zerosBelow = below;
    }
}

Q20.给定一个正整数和负整数组成的NxN矩阵,编写代码找出元素总和最大的子矩阵。

Ans.思路:

  • 暴力法:迭代O(N^4)个子矩阵,然后计算每个子矩阵的总和用时O(N^2),总时间复杂度为O(N^6)。
  • 将每个矩阵和计算的时间降为O(1),可以预先处理好这些值O(N^4)
  • 给定一个整数数组,找出元素总和最大的子数组。可以在O(N)时间内找到和最大的子数组。O(N^3)

预处理:

    public static int getMaxMatrix(int[][] original) {
        int maxArea = Integer.MIN_VALUE; // Important! Max could be < 0
        int rowCount = original.length;
        int columnCount = original[0].length;
        int[][] matrix = precomputeMatrix(original);
        for (int row1 = 0; row1 < rowCount; row1++) {
            for (int row2 = row1; row2 < rowCount; row2++) {
                for (int col1 = 0; col1 < columnCount; col1++) {
                    for (int col2 = col1; col2 < columnCount; col2++) {
                        int sum = computeSum(matrix, row1, row2, col1, col2);
                        if (sum > maxArea) {
                            System.out.println("New Max of " + sum + ": (rows " + row1 + " to " + row2 + ") and (columns " + col1 + " to " + col2 + ")");
                            maxArea = sum;
                        }
                        
                    }
                }
            }
        }
        return maxArea;
    }
        
    private static int[][] precomputeMatrix(int[][] matrix) {
        int[][] sumMatrix = new int[matrix.length][matrix[0].length];
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[0].length; j++) {
                if (i == 0 && j == 0) { // first cell
                    sumMatrix[i][j] = matrix[i][j];
                } else if (j == 0) { // cell in first column
                    sumMatrix[i][j] = sumMatrix[i - 1][j] + matrix[i][j];
                } else if (i == 0) { // cell in first row
                    sumMatrix[i][j] = sumMatrix[i][j - 1] + matrix[i][j];
                } else {
                    sumMatrix[i][j] = sumMatrix[i - 1][j] + 
                      sumMatrix[i][j - 1] - sumMatrix[i - 1][j - 1] +
                      matrix[i][j];
                }
            }
        }
        return sumMatrix;
    }
    
    private static int computeSum(int[][] sumMatrix, int i1, int i2, 
                                  int j1, int j2) {
        if (i1 == 0 && j1 == 0) { // starts at row 0, column 0
            return sumMatrix[i2][j2];
        } else if (i1 == 0) { // starts at row 0
            return sumMatrix[i2][j2] - sumMatrix[i2][j1 - 1];
        } else if (j1 == 0) { // starts at column 0
            return sumMatrix[i2][j2] - sumMatrix[i1 - 1][j2];
        } else {
            return sumMatrix[i2][j2] - sumMatrix[i2][j1 - 1] - sumMatrix[i1 - 1][j2] + sumMatrix[i1 - 1][j1 - 1];
        }
    }

使用O(n)计算最大子数组和:

    public static void clearArray(int[] array) {
        for (int i = 0; i < array.length; i++) {
            array[i] = 0;
        }
    }
    
    public static int maxSubMatrix(int[][] matrix) {
        int rowCount = matrix.length;
        int colCount = matrix[0].length;
    
        int[] partialSum = new int[colCount]; 
        int maxSum = 0; // Max sum is an empty matrix
    
        for (int rowStart = 0; rowStart < rowCount; rowStart++) {
            clearArray(partialSum);
        
            for (int rowEnd = rowStart; rowEnd < rowCount; rowEnd++) {
                for (int i = 0; i < colCount; i++) {
                    partialSum[i] += matrix[rowEnd][i];
                }
        
                int tempMaxSum = maxSubArray(partialSum, colCount);
                
                // if you want to track the coordinates, add code here to do that
                maxSum = Math.max(maxSum, tempMaxSum);
            }
        }
        return maxSum;
    }

    public static int maxSubArray(int array[], int N) {
        int maxSum = 0;
        int runningSum = 0;

        for (int i = 0; i < N; i++) {
            runningSum += array[i];
            maxSum = Math.max(maxSum, runningSum);
            
            /* If running_sum is < 0 no point in trying to continue the 
             * series. Reset. */
            if (runningSum < 0) {
                runningSum = 0;
            }
        }

        return maxSum;
    }

Q21.给定一份几百万个单词的清单,设计一个算法,创建由字母组成的最大矩形,其中每一行组成一个单词(自左向右),每一列组成一个单词(自上向下)。不要求这些单词在清单里连续出现,但要求所有行等长,所有列等高。

Ans.思路:

  • 预处理:将字典按单词长短进行分组。
  • 对于查找,可以用trie树
  • 典型的回溯法,穷举某个长度的情况,然后借助trie树检查高度是否符合

step1.将单词按长度分组

public class Question {
    private int maxWordLength;
    private WordGroup[] groupList ;
    private Trie trieList[];

    public Question(String[] list) {
        groupList = WordGroup.createWordGroups(list);
        maxWordLength = groupList.length;
        // Initialize trieList to store trie of groupList[i] at ith position.
        trieList = new Trie[maxWordLength];
    }

step2.从可能最大面积矩形开始,然后面积依次减一,第一个成功构建的必定是最大的

    public Rectangle maxRectangle() {
        // The dimensions of the largest possible rectangle.
        int maxSize = maxWordLength * maxWordLength; 

        for (int z = maxSize; z > 0; z--) {
            // Find out all pairs i,j less than maxWordLength 
            // such that i * j = z
            for (int i = 1; i <= maxWordLength; i ++ ) {
                if (z % i == 0) {
                    int j = z / i;
                    if (j <= maxWordLength) {
                        // Check if a Rectangle of length i and height 
                        // j can be created. 
                        Rectangle rectangle = makeRectangle(i,j);
                        if (rectangle != null) {
                            return rectangle;
                        }
                    }
                }
            }
        }
        return null;
    }

step3.构造指定长度和高度的矩形

    private Rectangle makeRectangle(int length, int height) {
        if (groupList[length - 1] == null || groupList[height - 1] == null) {
            return null;
        }
        if (trieList[height - 1] == null) {
            ArrayList<String> words = groupList[height - 1].getWords();
            trieList[height - 1] = new Trie(words); 
        }
        return makePartialRectangle(length, height, new Rectangle(length));
    }

step4.深度优先遍历解空间,分支为该长度所有可能性

    private Rectangle makePartialRectangle(int l, int h, Rectangle rectangle) {

        // Check if we have formed a complete rectangle by seeing if each column
        // is in the dictionary
        if (rectangle.height == h) {
            if (rectangle.isComplete(l, h, groupList[h - 1])) {
                return rectangle;
            } else {
                return null;
            }
        }

        // If the rectangle is not empty, validate that each column is a
        // substring of a word of length h in the dictionary using the
        // trie of words of length h.
        if (!rectangle.isPartialOK(l, trieList[h - 1])) {
            return null;
        }
        
        // For each word of length l, try to make a new rectangle by adding
        // the word to the existing rectangle.
        for (int i = 0; i < groupList[l-1].length(); i++) {
            Rectangle orgPlus = rectangle.append(groupList[l-1].getWord(i));
            Rectangle rect = makePartialRectangle(l, h, orgPlus);
            if (rect != null) {
                return rect;
            }
        }
        return null;
    }

参考

  • Cracking the coidng interview. 5th
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容