LeetCode 401-500

LeetCode Problems 401-500.

401. Binary Watch

https://leetcode.com/problems/binary-watch/


直接调用Integer.bitCount()函数,如果时和分中的1相加等于turnedOn,就添加到结果中。

class Solution {
    public List<String> readBinaryWatch(int turnedOn) {
        List<String> result = new ArrayList<>();

        for (int hour = 0; hour < 12; hour++) {
            for (int minute = 0; minute < 60; minute++) {
                if (Integer.bitCount(hour) + Integer.bitCount(minute) == turnedOn) {
                    result.add(hour + (minute < 10 ? ":0" : ":") + minute);
                }
            }
        }

        return result;
    }
}

402. Remove K Digits

https://leetcode.com/problems/remove-k-digits/


遍历字符串,找到递减序列,定位到第一个需要被替换的“大”数字,从之后找到更小的数字覆盖它。如果循环结束后,k的次数仍有剩余,根据endk的大小关系,判断返回值。

class Solution {
    public String removeKdigits(String num, int k) {
        int end = 0;
        char[] array = num.toCharArray();

        for (int i = 0; i < num.length(); i++) {
            // encounter a decreasing sequence, locate the digit to be overwritten
            while (end > 0 && k > 0 && array[end - 1] > array[i]) {
                k--;
                end--;
            }

            // skip leading '0' and overwrite one digit with a digit after
            if (end > 0 || array[i] != '0') {
                array[end] = array[i];
                end++;
            }
        }

        // if there are still k left, return 0 or truncate the last k digits
        return end <= k ? "0" : String.valueOf(array, 0, end - k);
    }
}

403. Frog Jump

https://leetcode.com/problems/frog-jump/


DFS思想,每一跳都尝试三种长度可能。同时可以用一个map存储每个下标与对应的k值,如果我们此前已经用k值到达过这个下标,就记录下来,避免之后重复DFS。

class Solution {
    // <index, set of all possible k>
    Map<Integer, Set<Integer>> checked = new HashMap<>();
    
    public boolean canCross(int[] stones) {
        for (int i = 0; i < stones.length; i++) {
            checked.put(stones[i], new HashSet<>());
        }
        return helper(0, 1, stones[stones.length - 1]);
    }

    private boolean helper(int current, int k, int last) {
        if (current == last) {
            return true;
        }
        
        // skip if we have checked this k at the current stone
        if (k <= 0 || checked.get(current).contains(k)) {
            return false;
        }
        
        int next = current + k;
        if (checked.containsKey(next)) {
            if (helper(next, k - 1, last) || helper(next, k, last) || helper(next, k + 1, last)) {
                return true;
            }
        }
        checked.get(current).add(k);
        
        return false;
    }
}

404. Sum of Left Leaves

https://leetcode.com/problems/sum-of-left-leaves/


从根节点开始:

  1. 如果root.left不空,则检查左子树
    • 如果root.left.left已经是叶节点,直接加上它的值
    • 否则,递归加上左子树中所有左叶节点的和
  2. 如果root.right不空,检查右子树中所有左叶节点的和
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int sumOfLeftLeaves(TreeNode root) {
        int result = 0;
        
        if (root == null) {
            return result;
        }
        
        if (root.left != null) {
            if (root.left.left == null && root.left.right == null) {
                result += root.left.val;
            } else {
                result += sumOfLeftLeaves(root.left);
            }
        }

        if (root.right != null) {
            result += sumOfLeftLeaves(root.right);
        }
        
        return result;
    }
}

405. Convert a Number to Hexadecimal

https://leetcode.com/problems/convert-a-number-to-hexadecimal/


使用循环,每次提取num最后4位转成16进制,再将最后的结果倒转。

class Solution {
    public String toHex(int num) {
        if (num == 0) {
            return "0";
        }

        char[] map = new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        StringBuilder sb = new StringBuilder();
        while (num != 0) {
            // transform last 4 bits to hex
            sb.append(map[num & 15]);
            
            // get rid of the last 4 bits
            num >>>= 4;
        }
        
        return sb.reverse().toString();
    }
}

406. Queue Reconstruction by Height

https://leetcode.com/problems/queue-reconstruction-by-height/


本质而言,队列的恢复是一个排序并且重新插入元素的过程。

  1. 对输入数组进行排序。排序规则是:默认按照h(身高)进行排序,身高越高排在越前面;身高一样时,按照k(面前身高大于等于h的人数)进行排序,人数越少越靠前
  2. 把排序好的队列插入到List中,注意插入时需要参考k值,k值是多少就插入到哪个位置。因为上一步中已经排序,而此时又是按照排序后的顺序进行遍历,因此k值相同时,身高矮的人比身高高的人后插入,起到了调整顺序的作用

最后,把List转为array即可。

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        Arrays.sort(people, (a, b) -> (a[0] == b[0] ? a[1] - b[1] : b[0] - a[0]));

        List<int[]> result = new LinkedList<>();
        for (int[] p : people) {
            result.add(p[1], p);
        }

        return result.toArray(new int[result.size()][]);
    }
}

407. Trapping Rain Water II

https://leetcode.com/problems/trapping-rain-water-ii/


核心思路是:把每个block定义成Comparable的object,这样能够利用Priority Queue根据每个Block的高度进行排序。之后,从map的四条边缘开始,把四条边上的所有Block加入到Priority Queue中,开始进行BFS遍历。

BFS遍历的流程是:从边缘Block开始,向四个方向的邻近Block遍历,筛除掉超过map边界和已访问Block的情况。之后按照高度进行判断:如果邻近的Block比现有的这个更高,就把它加入到Priority Queue中,用于后续进一步的判断,并且中止BFS;而如果它更矮,说明可以trap一些水,水的体积就是两个Block的高度差,并且继续进行BFS。

因为使用了Priority Queue,因此能够确保遍历Block的时候一定是按照从低到高的顺序开始,因此能保证这种方式trap的水量是最大的。

class Solution {
    private int water = 0;    
    private boolean[][] visited;
    private int visitedCnt = 0;

    private static class Block implements Comparable<Block> {
        int row;
        int col;
        int value;
        
        Block(int row, int col, int value) {
            this.row = row;
            this.col = col;
            this.value = value;
        }
        
        @Override
        public int compareTo(Block block) {
            return value - block.value;
        }
    }

    public int trapRainWater(int[][] heightMap) {
        if (heightMap == null || heightMap.length < 3 || heightMap[0].length < 3) {
            return 0;
        }

        int rows = heightMap.length;
        int cols = heightMap[0].length;
        PriorityQueue<Block> pq = new PriorityQueue<Block>();
        visited = new boolean[rows][cols];

        // add row edge blocks to priority queue
        for (int i = 0; i < cols; i++) {
            Block block1 = new Block(0, i, heightMap[0][i]);
            Block block2 = new Block(rows - 1, i, heightMap[rows - 1][i]);
            pq.offer(block1);
            pq.offer(block2);
            visited[0][i] = true;
            visited[rows - 1][i] = true;
            visitedCnt += 2;
        }

        // add column edge blocks to priority queue
        for (int i = 1; i < rows - 1; i++) {
            Block block1 = new Block(i, 0, heightMap[i][0]);
            Block block2 = new Block(i, cols - 1, heightMap[i][cols - 1]);
            pq.offer(block1);
            pq.offer(block2);
            visited[i][0] = true;
            visited[i][cols - 1] = true;
            visitedCnt += 2;
        }

        // BFS
        while (visitedCnt < rows * cols) {
            Block block = pq.poll();
            fill(block.row - 1, block.col, block.value, pq, heightMap);
            fill(block.row, block.col - 1, block.value, pq, heightMap);
            fill(block.row + 1, block.col, block.value, pq, heightMap);
            fill(block.row, block.col + 1, block.value, pq, heightMap);
        }
        
        return water;
    }


    public void fill(int row, int col, int min, PriorityQueue<Block> pq, int[][] heightMap) {
        if (row < 0 || col < 0 || row >= heightMap.length || col >= heightMap[0].length || visited[row][col]) {
            // out of bound, or visited block
            return;
        } else if (heightMap[row][col] >= min) {
            // visit a higher block, add to queue and mark it as visited
            Block newBlock = new Block(row, col, heightMap[row][col]);
            pq.offer(newBlock);
            visited[row][col] = true;
            visitedCnt++;
        } else {
            // visit a lower block, add water to be trapped
            water += min - heightMap[row][col];
            visited[row][col] = true;
            visitedCnt++;
            fill(row - 1, col, min, pq, heightMap);
            fill(row, col - 1, min, pq, heightMap);
            fill(row + 1, col, min, pq, heightMap);
            fill(row, col + 1, min, pq, heightMap);
        }
    }
}

408. Valid Word Abbreviation

https://leetcode.com/problems/valid-word-abbreviation/


使用两个指针分别遍历wordabbr,如果abbr遇到了数字,判断这个数字number是多少,直到数字结束;否则,让word的指针跳过number的下标,看此时wordabbr下标对应的字母仍然相等。重复这个循环,最后判断两个指针是否能分别完成遍历。

class Solution {
    public boolean validWordAbbreviation(String word, String abbr) {
        int number = 0;
        int i = 0, j = 0;

        while (i < word.length() && j < abbr.length()) {
            if (abbr.charAt(j) >= '0' && abbr.charAt(j) <= '9') {
                number = number * 10 + abbr.charAt(j) - '0';
                if (number == 0) {
                    return false;
                }
                j++;
            } else {
                i += number;
                if (i >= word.length() || word.charAt(i) != abbr.charAt(j)) {
                    return false;
                }
                number = 0;
                i++;
                j++;
            }
        }

        // abbr ends with number
        i += number; 
        return i == word.length() && j == abbr.length();
    }
}

409. Longest Palindrome

https://leetcode.com/problems/longest-palindrome/


统计每个字符出现的次数,用一个变量odd记录出现次数为奇数的字符数量。如果odd最后小于2,说明可以直接用s构造最长的回文串;反之,需要剔除掉odd - 1个这种奇数字符(只能选一个字符全部加入,其他的奇数字符每个要减掉一个)。

class Solution {
    public int longestPalindrome(String s) {
        int[] freq = new int[128];
        for (int i = 0; i < s.length(); i++) {
            freq[s.charAt(i)]++;
        }

        int odd = 0;
        for (int i = 0; i < freq.length; i++) {
            if (freq[i] % 2 != 0) {
                odd++;
            }
        }
            
        return odd < 2 ? s.length() : s.length() - odd + 1;
    }
}

410. Split Array Largest Sum

https://leetcode.com/problems/split-array-largest-sum/


如果mnums的个数相等,那么每个数组都是一个子数组,所以返回nums中最大的数字即可;如果m为1,那么整个nums数组就是一个子数组,返回nums所有数字之和,所以对于其他有效的m值,返回的值必定在上面两个值之间,可以用二分搜索法来做。

判断和是否满足条件的办法:遍历找到数组的最大值作为初始minVal,数组的和为初始maxVal,选择mid作为二者的平均值。从数组头开始遍历,计算累加和大于等于mid的子数组总量。如果子数组的数量大于m,说明子数组太多了,也就是说区间的和定义的太小了,应该增大minVal的值,因此把minVal设置为mid + 1;相反,则要降低maxVal的值。

class Solution {
    public int splitArray(int[] nums, int m) {
        int minVal = Integer.MIN_VALUE, maxVal = 0;
        for (int num : nums) {
            minVal = Math.max(minVal, num);
            maxVal += num;
        }

        while (minVal < maxVal) {
            int mid = minVal + (maxVal - minVal) / 2;
            
            if (canSplit(mid, nums, m)) {
                maxVal = mid;
            } else {
                minVal = mid + 1;
            }
        }

        return minVal;
    }

    private boolean canSplit(int upperBoundSubarraySum, int[] nums, int m) {
        int curSubarraySum = 0, subarrayCount = 1;
        
        for (int num : nums) {
            curSubarraySum += num;
            
            if (curSubarraySum > upperBoundSubarraySum) {
                subarrayCount++;
                curSubarraySum = num;
                if (subarrayCount > m) {
                    return false;
                }
            }
        }

        return true;
    }
}

411. Minimum Unique Word Abbreviation

https://leetcode.com/problems/minimum-unique-word-abbreviation/


尝试利用位掩码来快速表示不同字符串之间的差别。具体而言,用一个整数diff表示字符串wordtarget之间的差别,假设要比较它们第i位的字符是否相等,如果不相等,diff就加上1 << i。解题流程如下:

  1. 对于给定的target,在dic中寻找和target长度相同的word,记录下表示wordtarget之间的差异的diff,用一个Set存起来
  2. 如果经过第一步后Set为空,说明不存在缩写冲突的可能,当前字符串直接使用字符串长度作为最短缩写
  3. 寻找最大的冲突表示整数bits。具体而言,需要穷举0 ~ 1 << targetLen之间的数与diff进行比较,跳过完全与diff完全不相等的情况,并在剩下的情况中用位运算寻找bits
  4. 根据最大冲突位数生成最终的缩略字符串。将bits1 << i逐位进行对比,如果这当前位相等,说明此时出现冲突,不能用数字缩略,必须将之前的缩略长度添加到缩略字符串后清空,再加上当前位置的字符
class Solution {
    public String minAbbreviation(String target, String[] dic) {
        Set<Integer> diffs = new HashSet<>();
        int targetLen = target.length();

        for (String word : dic) {
            if (word.length() == targetLen) {
                // mark each bit's difference between target and word
                int diff = 0;
                for (int i = 0; i < targetLen; i++) {
                    if (target.charAt(i) != word.charAt(i))  {
                        diff += 1 << i;
                    }
                }
                        
                diffs.add(diff);
            }
        }

        // all words have different length, then return the shortest abbr
        if (diffs.isEmpty()) {
            return Integer.toString(targetLen);
        }

        int bits = Integer.MIN_VALUE;
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < (1 << targetLen); i++) {
            boolean flag = true;

            // skip cases that are totally different from dic
            for (int diff : diffs) {
                if ((i & diff) == 0) {
                    flag = false;
                    break;
                }
            }

            // get maximum conflicting bits
            if (flag) {
                int sum = 0;
                for (int j = 0; j < targetLen - 1; j++) {
                    if (((i >> j) & 3) == 0) {
                        sum++;
                    }
                }
                    
                if (sum > max) {
                    max = sum;
                    bits = i;
                }
            }
        }

        StringBuilder result = new StringBuilder();
        int count = 0;
        for (int i = 0; i < targetLen; i++) {

            // current pos cannot be replaced with an abbr digit, must use alphabet instead
            if ((bits & (1 << i)) != 0) {
                if (count != 0) {
                    result.append(count);
                    count = 0;
                }
                result.append(target.charAt(i));
            } else {
                count++;
            }
        }

        if (count != 0) {
            result.append(count);
        }
        return result.toString();
    }
}

412. Fizz Buzz

https://leetcode.com/problems/fizz-buzz/


直接使用常规循环判断法解题即可。

class Solution {
    public List<String> fizzBuzz(int n) {
        List<String> result = new ArrayList<>();
        for (int i = 1; i <= n; i++) {
            if (i % 3 == 0 && i % 5 == 0) {
                result.add("FizzBuzz");
            } else if (i % 3 == 0) {
                result.add("Fizz");
            } else if (i % 5 == 0) {
                result.add("Buzz");
            } else {
                result.add(String.valueOf(i));
            }
        }
        
        return result;
    }
}

如果想让代码更加优雅,并且在未来具有良好的拓展性,可以设计一个TreeMap,存放所有的编码规则,在循环遍历时直接去Map中找当前数字所有匹配的规则。

class Solution {
    public List<String> fizzBuzz(int n) {
        // define the order of how you want to concat the words here using lambda comparator
        TreeMap<Integer, String> map = new TreeMap<>((a, b) -> a - b); 
        map.put(3, "Fizz");
        map.put(5, "Buzz");
        // map.put(7, "Yuzz"); 
        // map.put(9, "Mozz");
        // add more encoding options here ... 
        
        List<String> result = new LinkedList();
        for (int i = 1; i <= n; i++) { 
            StringBuilder encoded = new StringBuilder();
            for (int num : map.keySet()) {
                if (i % num == 0) {
                    encoded.append(map.get(num));
                }
            }
            
            result.add((encoded.length() == 0) ? String.valueOf(i) : encoded.toString());
        }
        
        return result;
    }
}

413. Arithmetic Slices

https://leetcode.com/problems/arithmetic-slices/


循环遍历数组,只需要跟踪当前下标数与之前的两个数,判断这三个数能不能成为一组新的slice。如果可以,当前newSlice数量加1,并且注意实时更新结果数量时,要添加的是目前的newSlice数量而不是1(因为当前newSlice每加一,就代表前面已有的slice都可以再拓展一位当前数,组成一个新的slice)。而一旦三个数中断组成slice,newSlice全要被清零。

class Solution {
    public int numberOfArithmeticSlices(int[] A) {
        int newSlice = 0, result = 0;
        
        for (int i = 2; i < A.length; i++) {
            if (A[i-1] - A[i-2] == A[i] - A[i-1]) {
                newSlice++;
                result += newSlice;
            } else {
                newSlice = 0;
            }
        }
        
        return result;
    }
}

414. Third Maximum Number

https://leetcode.com/problems/third-maximum-number/


使用三变量法进行统计,注意变量初始化时声明为null更加严谨。

class Solution {
    public int thirdMax(int[] nums) {
        Integer max1 = null;
        Integer max2 = null;
        Integer max3 = null;

        for (Integer n : nums) {
            if (n.equals(max1) || n.equals(max2) || n.equals(max3)) {
                continue;
            }
            
            if (max1 == null || n > max1) {
                max3 = max2;
                max2 = max1;
                max1 = n;
            } else if (max2 == null || n > max2) {
                max3 = max2;
                max2 = n;
            } else if (max3 == null || n > max3) {
                max3 = n;
            }
        }
        
        return max3 == null ? max1 : max3;
    }
}

如果要拓展成更加一般性的第k大数字,可以使用优先队列+集合。

class Solution {
    public int thirdMax(int[] nums) {
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        Set<Integer> set = new HashSet<>();
        
        for (int n : nums) {
            if (set.add(n)) {
                pq.offer(n);
                
                if (pq.size() > 3) {
                    pq.poll();
                }
            }
        }
        
        if (pq.size() == 2) {
            pq.poll();
        }
        return pq.peek();
    }
}

415. Add Strings

https://leetcode.com/problems/add-strings/


仍然是模拟加法,从字符串尾部开始,取数 -> 做加法 -> 得到进位,循环直到两个数都完整被遍历过一次。

class Solution {
    public String addStrings(String num1, String num2) {
        StringBuilder result = new StringBuilder();
        int carry = 0;
        int len1 = num1.length() - 1;
        int len2 = num2.length() - 1;

        while (len1 >= 0 || len2 >= 0) {
            int x = len1 >= 0 ? num1.charAt(len1) - '0' : 0;
            int y = len2 >= 0 ? num2.charAt(len2) - '0' : 0;
            int sum = (x + y + carry) % 10;
            carry = (x + y + carry) / 10;
            result.append(sum);
            len1--;
            len2--;
        }

        if (carry != 0) {
            result.append(carry);
        }
        return result.reverse().toString();
    }
}

416. Partition Equal Subset Sum

https://leetcode.com/problems/partition-equal-subset-sum/


使用DFS,把问题转化成选若干个数,使得总和恰好为sum的一半。注意考虑两种情况:

  • 选择当前数字,并修改下一次递归的target
  • 不选择当前数字,而是找到下一个不同的数字并选择它进行递归
class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int val: nums) sum += val;
        if (sum % 2 == 1) {
            return false;
        }
        sum /= 2;

        Boolean[] dp = new Boolean[sum + 1];
        dp[0] = true;
        boolean result = canPartition(sum, 0, nums, dp);
        return result;
    }

    private boolean canPartition(int sum, int pos, int[] nums, Boolean[] dp) {
        if (sum == 0) {
            return true;
        }
        if (sum < 0 || pos == nums.length) {
            return false;
        }
        if (dp[sum] != null) {
            return dp[sum];
        }

        dp[sum] = canPartition(sum - nums[pos], pos + 1, nums, dp) || canPartition(sum, pos + 1, nums, dp);
        return dp[sum];
    }
}

417. Pacific Atlantic Water Flow

https://leetcode.com/problems/pacific-atlantic-water-flow/


使用DFS逆向推导,因为边缘的格子一定能够进入海洋。逆向推导的过程中,用两个二维数组分别表示当前的坐标是否能进入太平洋/大西洋。注意,因为两个海洋对应四条边,因此需要以四条边的每个点做为起点进行DFS。

class Solution {
    public List<List<Integer>> pacificAtlantic(int[][] matrix) {
        List<List<Integer>> result = new ArrayList<>();
        if (matrix.length == 0 || matrix[0].length == 0) {
            return result;
        }

        int m = matrix.length;
        int n = matrix[0].length;
        boolean[][] pacific = new boolean[m][n];
        boolean[][] atlantic = new boolean[m][n];

        // top and bottom
        for (int col = 0; col < n; col++) {
            dfs(matrix, 0, col, matrix[0][col], pacific);
            dfs(matrix, m-1, col, matrix[m-1][col], atlantic);
        }

        // left and right
        for (int row = 0; row < m; row++) {
            dfs(matrix, row, 0, matrix[row][0], pacific);
            dfs(matrix, row, n-1, matrix[row][n-1], atlantic);
        }

        // find all grids that meet requirements
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (pacific[i][j] && atlantic[i][j]) {
                    List<Integer> temp = new ArrayList();
                    temp.add(i);
                    temp.add(j);
                    result.add(temp);
                }
            }
        }
        
        return result;
    }

    private void dfs(int[][] matrix, int r, int c, int preHeight, boolean[][] ocean) {
        // skip out-of-bound & higher flow & visited cases
        if (r < 0 || c < 0 || r > matrix.length-1 || c > matrix[0].length-1 || matrix[r][c] < preHeight || ocean[r][c]) {
            return;
        }

        ocean[r][c] = true;
        dfs(matrix, r - 1, c, matrix[r][c], ocean); // up
        dfs(matrix, r + 1, c, matrix[r][c], ocean); // down
        dfs(matrix, r, c - 1, matrix[r][c], ocean); // left
        dfs(matrix, r, c + 1, matrix[r][c], ocean); // right
    }
}

418. Sentence Screen Fitting

https://leetcode.com/problems/sentence-screen-fitting/


使用数组times记录每一行完成遍历的次数;使用数组nextIndex记录下一行的起始单词对应下标。

中间的循环,本质是遍历单词填满每一行长度的过程,如果填满了就保存下一行的起始下标与这一行的次数,之后跳下一行;如果这一行完成了遍历,还要更新遍历次数。最后,根据rows的长度,顺序累加遍历次数即可,检查完一行需要根据下一行起始下标进行跳转。

class Solution {
    public int wordsTyping(String[] sentence, int rows, int cols) {
        // a new line which is starting with certain index in sentence, what is the starting index of next line
        int[] nextIndex = new int[sentence.length];

        // how many times the pointer in current line passes over the last index
        int[] times = new int[sentence.length];

        for (int i = 0; i < sentence.length; i++) {
            int curLen = 0;
            int cur = i;
            int time = 0;

            while (curLen + sentence[cur].length() <= cols) {
                // word + space
                curLen += sentence[cur].length() + 1;
                cur++;

                // the whole sentence is added for a time
                if (cur == sentence.length) {
                    cur = 0;
                    time++;
                }
            }

            // stop at the first word of next line and times of the previous line
            nextIndex[i] = cur;
            times[i] = time;
        }

        int result = 0;
        int cur = 0; // start from the first word in the sentence
        for (int i = 0; i < rows; i++) {
            result += times[cur]; 
            cur = nextIndex[cur]; // update start of next row
        }
        
        return result;
    }
}

419. Battleships in a Board

https://leetcode.com/problems/battleships-in-a-board/


因为战舰的构造一定是1*N或者N*1,因此不需要搜索,直接两重循环遍历,检查当前方格是否处在一行或一列’X’中,具体而言只需要循环和左、上邻近方格比较即可。

class Solution {
    public int countBattleships(char[][] board) {
        if (board.length == 0 || board[0].length == 0) {
            return 0;
        }

        int result = 0;
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if (board[i][j] == '.') {
                    continue;
                }

                // 1 x N or N x 1, thus we can always check left or up grid to determine the same battleship
                if ((i > 0 && board[i-1][j] == 'X') || (j > 0 && board[i][j-1] == 'X')) {
                    continue;
                }
                result++;
            }
        }

        return result;
    }
}

420. Strong Password Checker

https://leetcode.com/problems/strong-password-checker/


记录下缺失的字符类型数量(小写、大写、数字),再用一个数组记录字符串截止到每一位时的重复字符数量。

  • 如果字符串太短,检查是否需要补缺失的字符,再看剩下的长度是否达到至少6位
  • 删减重复子字符串,根据每位重复字符数量,把大于2的情况删减修改成2
  • 如果字符串很长,就先做删减重复子字符串,看删完之后剩下的长度是否还超过20位,并视需要补缺失字符
class Solution {
    public int strongPasswordChecker(String s) {
        int result = 0;
        
        // number of missing lowercase letters, uppercase letters and digits
        int a = 1, A = 1, d = 1;
        
        // number of repeating characters starting at the corresponding position in the string
        char[] carr = s.toCharArray();
        int[] arr = new int[carr.length];

        for (int i = 0; i < arr.length;) {
            char currentChar = carr[i];
            if (Character.isLowerCase(currentChar)) a = 0;
            if (Character.isUpperCase(currentChar)) A = 0;
            if (Character.isDigit(currentChar)) d = 0;

            int j = i;
            while (i < carr.length && carr[i] == carr[j]) {
                i++;
            }
            arr[j] = i - j;
        }

        int totalMissing = (a + A + d);

        // pad length to at least 6
        if (arr.length < 6) {
            result += totalMissing + Math.max(0, 6 - (arr.length + totalMissing));
        } else {
            int overLenCharNum = Math.max(arr.length - 20, 0), leftOverNum = 0;
            result += overLenCharNum;

            for (int k = 1; k < 3; k++) {
                for (int i = 0; i < arr.length && overLenCharNum > 0; i++) {
                    if (arr[i] < 3 || arr[i] % 3 != (k - 1)) {
                        continue;
                    }

                    // turn all numbers in the arr array greater than 2 into the form of (3m + 2)
                    arr[i] -= Math.min(overLenCharNum, k);
                    overLenCharNum -= k;
                }
            }

            for (int i = 0; i < arr.length; i++) {
                if (arr[i] >= 3 && overLenCharNum > 0) {
                    // minimum number of changes needed to fix the repeating character problem by deletion only
                    int need = arr[i] - 2;
                    arr[i] -= overLenCharNum;
                    overLenCharNum -= need;
                }

                if (arr[i] >= 3) {
                    leftOverNum += arr[i] / 3;
                }
            }

            result += Math.max(totalMissing, leftOverNum);
        }

        return result;
    }
}

421. Maximum XOR of Two Numbers in an Array

https://leetcode.com/problems/maximum-xor-of-two-numbers-in-an-array/


使用循环,设定前i位为可能的最大异或值,之后从set中寻找能匹配的记录。

class Solution {
    public int findMaximumXOR(int[] nums) {
        int max = 0, mask = 0;

        // find max number to get max length
        int maxNum = nums[0];
        for (int i = 1; i < nums.length; i++) {
            maxNum = Math.max(maxNum, nums[i]);
        }
            
        int len = Integer.toBinaryString(maxNum).length(); 

        for (int i = len - 1; i >= 0; i--) {
            // The mask will grow like  100..000 , 110..000, 111..000,  then 1111...111
            mask = mask | (1 << i);

            HashSet<Integer> set = new HashSet<>();

            // only care about the left parts
            for (int num : nums) {
                set.add(num & mask);
            }

            // try to find a candidate which can give the greediest try;
            int tmp = max | (1 << i);
            for (int prefix : set) {
                // if a ^ b = c, then a ^ c = b
                // to get b, we need a ^ c, if a ^ c exisited in our set, then we're good to go
                if (set.contains(tmp ^ prefix)) {
                    max = tmp;
                    break;
                }
            }
        }
        
        return max;
    }
}

422. Valid Word Square

https://leetcode.com/problems/valid-word-square/


words中每个word提取出来,拷贝到二维数组matrix中,之后直接在matrix根据对角线镜像对称判断是否合法。

class Solution {
    public boolean validWordSquare(List<String> words) {
        if (words == null || words.isEmpty()) {
            return false;
        }
        int m = words.size();
        char[][] matrix = new char[m][m];

        for (int i = 0; i < m; i++) {
            String word = words.get(i);
            if (word.length() > m) {
                return false;
            }
            System.arraycopy(word.toCharArray(), 0, matrix[i], 0, word.length());
        }

        for (int row = 0; row < m; row++) {
            for (int col = row; col < m; col++) {
                if (matrix[row][col] != matrix[col][row]) {
                    return false;
                }     
            }
        }
        return true;
    }
}

423. Reconstruct Original Digits from English

https://leetcode.com/problems/reconstruct-original-digits-from-english/


所有偶数英文单词都有一个独有的字母;而奇数的单词中虽然没有独有字母,但可以通过排除掉偶数之后得到唯一的情况。

class Solution {
    public String originalDigits(String s) {
        // hashmap, map letter into its frequency
        char[] count = new char[26 + (int)'a'];
        for (char letter: s.toCharArray()) {
            count[letter]++;
        }

        // map array, map digit into its frequency
        int[] out = new int[10];

        // letter "z" only in "zero"
        out[0] = count['z'];

        // letter "w" only in "two"
        out[2] = count['w'];

        // letter "u" only in "four"
        out[4] = count['u'];

        // letter "x" only in "six"
        out[6] = count['x'];

        // letter "g" only in "eight"
        out[8] = count['g'];

        // letter "h" only in "three" and "eight"
        out[3] = count['h'] - out[8];

        // letter "f" only in "five" and "four"
        out[5] = count['f'] - out[4];

        // letter "s" only in "seven" and "six"
        out[7] = count['s'] - out[6];

        // letter "i" is in "nine", "five", "six", and "eight"
        out[9] = count['i'] - out[5] - out[6] - out[8];

        // letter "n" is in "one", "nine", and "seven"
        out[1] = count['n'] - out[7] - 2 * out[9];

        StringBuilder result = new StringBuilder();
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < out[i]; j++) {
                result.append(i);
            }
        }
        return result.toString();
    }
}

424. Longest Repeating Character Replacement

https://leetcode.com/problems/longest-repeating-character-replacement/


使用滑动窗口,用一个变量maxCount记录当前窗口中出现次数最多的单个字符数量。因为最多只能进行k次替换,因此必须保证当前窗口内的其他字符不能超过k个,否则就必须从窗口头部删去字符。每完成一次窗口的更新,就更新当前窗口的最大长度。

class Solution {
    public int characterReplacement(String s, int k) {
        int maxCount = 0; // largest count of a single, unique character in the current window
        int maxLength = 0;
        int[] freq = new int[26];

        char[] chars = s.toCharArray();
        for (int start = 0, end = 0; end < s.length(); end++) {
            maxCount = Math.max(maxCount, ++freq[chars[end] - 'A']);

            // at most k replacements in the window, and there are more characters in the window than we can replace
            if (end - start + 1 - maxCount > k) {
                freq[chars[start] - 'A']--;
                start++;
            }
            
            maxLength = Math.max(maxLength, end - start + 1);
        }

        return maxLength;
    }
}

425. Word Squares

https://leetcode.com/problems/word-squares/


构造Trie,把输入的词都添加到TrieNode中,再构建一个len*len大小的char map用于DFS检查。注意DFS开始时传入的参数是一个TrieNode的数组tns,表示一共组建了n个Trie,tns[i]在DFS中表示对于word[i],下一个可用的字母。每一层DFS中都新开一个数组,拷贝符合的字母树路径。遍历当前的candidate字符串时,根据对称性修改二维char map的值进行匹配比较。如果纵向每个字母都能匹配就开始下一层递归,直到len层长度都匹配完成。

class Solution {
    class TrieNode {
        List<String> list; // all words that have this node as prefix
        TrieNode[] children;
        TrieNode() {
            list = new ArrayList<>();
            children = new TrieNode[26];
        }
    }

    // O(n^len) * len
    public List<List<String>> wordSquares(String[] words) {
        TrieNode root = new TrieNode();
        for (String w : words) {
            root.list.add(w);
            TrieNode tn = root;

            for (char ch : w.toCharArray()) {
                if (tn.children[ch - 'a'] == null) {
                    tn.children[ch - 'a'] = new TrieNode();
                }
                tn = tn.children[ch - 'a'];

                // add word to each node in the tree
                tn.list.add(w);
            }
        }

        // length of square
        int len = words[0].length();

        // form n tries starting from root, one trie for each word
        // tns[i] tells us in the DFS, for word[i], which letter is available next
        TrieNode[] tns = new TrieNode[len];
        Arrays.fill(tns, root);

        List<List<String>> result = new ArrayList<>();
        dfs(0, new char[len][len], result, tns);
        return result;
    }

    private void dfs(int idx, char[][] m, List<List<String>> result, TrieNode[] tns) {
        // one word square, add all the strings to result
        if (idx == m.length) {
            List<String> wordSquare = new ArrayList<>(idx);
            for (char[] ca : m) {
                wordSquare.add(new String(ca));
            }
            result.add(wordSquare);
            return;
        }

        TrieNode[] tns2 = new TrieNode[tns.length];

        //for each word in prefix list of this idx node, recursively build the word square starting from cand
        for (String cand : tns[idx].list) {
            int i = idx;

            // set m[idx][i] to be cand[idx] the first time in this for loop
            // for every after iteration i, set m[idx][i] and m[i][idx] to be cand[idx+i]
            // check if one of the tns2 possible roots contain that ch, otherwise backtrack from dfs to try another candidate word
            for (; i < m.length; ++i) {
                char ch = cand.charAt(i);
                m[idx][i] = ch; 
                if (i > idx) {
                    if (tns[i].children[ch - 'a'] == null) {
                        break;
                    }
                    m[i][idx] = ch;            

                    //tns2 serves as the trie node for each position i that we are trying to fill
                    tns2[i] = tns[i].children[ch - 'a']; 
                }
            }

            // found a match for every letter in this cand word, then dfs at next level
            if (i == m.length) {
                dfs(idx + 1, m, result, tns2); 
            }
        }
    }    
}

426. Convert Binary Search Tree to Sorted Doubly Linked List

https://leetcode.com/problems/convert-binary-search-tree-to-sorted-doubly-linked-list


解题流程分为2步:

  1. 用中序遍历,把每一个BST节点之间用双向指针连起来
  2. 把头尾节点用双向指针连起来
class Solution {
    Node curr = null;
    
    public Node treeToDoublyList(Node root) {
        if (root == null) {
            return root;
        }

        Node dummyHead = new Node(0);
        curr = dummyHead;

        // inorder tranversal by recursion to connect the original BST
        inOrderConnect(root);

        // connect head and tail, notice that current ptr is tail
        curr.right = dummyHead.right;
        dummyHead.right.left = curr;

        return dummyHead.right;
    }

    private void inOrderConnect(Node node) {
        if (node == null) {
            return;
        }

        inOrderConnect(node.left);

        // construct doubly link
        curr.right = node;
        node.left = curr;
        curr = node;

        inOrderConnect(node.right);
    }
}

427. Construct Quad Tree

https://leetcode.com/problems/construct-quad-tree/


深度优先搜索,在四个子区域内不断减半长度,得到的四个新的区域节点,如果这四个区域节点的isLeaf()都返回true,并且节点的val都一致,说明当前节点本身已经是一个子节点了。否则,当前节点就是这四个子节点的父节点。

/*
// Definition for a QuadTree node.
class Node {
    public boolean val;
    public boolean isLeaf;
    public Node topLeft;
    public Node topRight;
    public Node bottomLeft;
    public Node bottomRight;

    
    public Node() {
        this.val = false;
        this.isLeaf = false;
        this.topLeft = null;
        this.topRight = null;
        this.bottomLeft = null;
        this.bottomRight = null;
    }
    
    public Node(boolean val, boolean isLeaf) {
        this.val = val;
        this.isLeaf = isLeaf;
        this.topLeft = null;
        this.topRight = null;
        this.bottomLeft = null;
        this.bottomRight = null;
    }
    
    public Node(boolean val, boolean isLeaf, Node topLeft, Node topRight, Node bottomLeft, Node bottomRight) {
        this.val = val;
        this.isLeaf = isLeaf;
        this.topLeft = topLeft;
        this.topRight = topRight;
        this.bottomLeft = bottomLeft;
        this.bottomRight = bottomRight;
    }
};
*/

class Solution {
    public Node construct(int[][] grid) {
        return helper(grid, 0, 0, grid.length);
    }

    private Node helper(int[][] grid, int x, int y, int len) {
        // the minimum grid
        if (len == 1) {
            return new Node(grid[x][y] != 0, true);
        }

        Node result = new Node();
        Node topLeft = helper(grid, x, y, len / 2);
        Node topRight = helper(grid, x, y + len / 2, len / 2);
        Node bottomLeft = helper(grid, x + len / 2, y, len / 2);
        Node bottomRight = helper(grid, x + len / 2, y + len / 2, len / 2);

        
        if (topLeft.isLeaf && topRight.isLeaf && bottomLeft.isLeaf && bottomRight.isLeaf
            && topLeft.val == topRight.val && topRight.val == bottomLeft.val && bottomLeft.val == bottomRight.val) {
            // current grid has the same value
            result.isLeaf = true;
            result.val = topLeft.val;
        } else {
            // up to parent node
            result.topLeft = topLeft;
            result.topRight = topRight;
            result.bottomLeft = bottomLeft;
            result.bottomRight = bottomRight;
        }
        
        return result;
    }
}

428. Serialize and Deserialize N-ary Tree

https://leetcode.com/problems/serialize-and-deserialize-n-ary-tree/


核心是使用前序遍历法。序列化时,按照前序遍历顺序,每一个节点提取出val和子节点的个数进行转化,通过 + '0'的操作保证转化后这两个值能用1个字符表示(这里的前提是valN值都不会很大,例如都是200以内的数)。反序列化时,同样按照前序遍历,把字符转化为具体的值。可以使用一个变量index[1]记录当前处理序列化字符串的下标。

/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> children;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
};
*/

class Codec {
    // Encodes a tree to a single string.
    public String serialize(Node root) {
        StringBuilder sb = new StringBuilder();
        serialize(root, sb);
        return sb.toString();
    }

    private void serialize(Node node, StringBuilder sb) {
        if (node == null) {
            return;
        }

        // append value char
        sb.append((char) (node.val + '0'));
        
        // append children size char
        sb.append((char) (node.children.size() + '0'));

        for (Node n : node.children) {
            serialize(n, sb);
        }
    }

    // Decodes your encoded data to tree.
    public Node deserialize(String data) {
        if (data == null || data.length() == 0) {
            return null;
        }
        return deserialize(data, new int[1]);
    }

    Node deserialize(String data, int[] index) {
        int val = data.charAt(index[0]) - '0';
        int childrenSize = data.charAt(++index[0]) - '0';

        Node node = new Node(val, new ArrayList<Node>());
        for (int i = 0; i < childrenSize; i++) {
            index[0]++;
            node.children.add(deserialize(data, index));
        }
        
        return node;
    }
}

429. N-ary Tree Level Order Traversal

https://leetcode.com/problems/n-ary-tree-level-order-traversal/


与二叉树层次遍历类似,仍然是使用队列+BFS的思路,队列弹出当前节点时,遍历把所有子节点添加到队列中即可。

/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> children;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
    public List<List<Integer>> levelOrder(Node root) {
        List< List<Integer> > result = new ArrayList<>();
        if (root == null) {
            return result;
        }

        Queue<Node> queue = new LinkedList<>();
        queue.offer(root);

        while (!queue.isEmpty()) {
            List<Integer> curLevel = new ArrayList<>();

            // these are all nodes in current level
            int currentLevelSize = queue.size();
            for (int i = 0; i < currentLevelSize; i++) {
                Node cur = queue.poll();
                curLevel.add(cur.val);

                // add current node's children to queue
                for (Node child : cur.children) {
                    queue.offer(child);
                }
            }

            // finish a level, add current level's value to result
            result.add(curLevel);
        }

        return result;
    }
}

430. Flatten a Multilevel Doubly Linked List

https://leetcode.com/problems/flatten-a-multilevel-doubly-linked-list/


建立一个栈,如果遇到某个节点存在child指针,就把这个节点的next指针存到栈里,以待日后重新返回这一层时不迷路。之后,把这个节点和child指针指向的节点双向连起来,再把child指针删掉。

/*
// Definition for a Node.
class Node {
    public int val;
    public Node prev;
    public Node next;
    public Node child;
};
*/

class Solution {
    public Node flatten(Node head) {
        if (head == null) {
            return null;
        }

        Node dummyHead = new Node(0, null, head, null);
        Node cur, prev = dummyHead;

        // set a stack and push head into it
        Stack<Node> stack = new Stack();
        stack.push(head);

        while (!stack.isEmpty()) {
            cur = stack.pop();

            // link previous and current node
            prev.next = cur;
            cur.prev = prev;

            // push next node or child node
            if (cur.next != null) stack.push(cur.next);
            if (cur.child != null) {
                stack.push(cur.child);

                // unlink current node with child
                cur.child = null;
            }

            // move pointers forward
            prev = cur;
        }

        // link first node with dummyHead to make a valid doubly linked list
        dummyHead.next.prev = null;

        return dummyHead.next;
    }
}

431. Encode N-ary Tree to Binary Tree

https://leetcode.com/problems/encode-n-ary-tree-to-binary-tree/


N叉树转化成二叉树时,总是将当前节点的第一个子节点转化成左儿子,剩下的儿子递归组成左儿子的右子树。

/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> children;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
};
*/

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */

class Codec {
    // Encodes an n-ary tree to a binary tree.
    public TreeNode encode(Node root) {
        if (root == null) {
            return null;
        }
        TreeNode result = new TreeNode(root.val);

        // next level -> left
        if (root.children.size() > 0) {
            result.left = encode(root.children.get(0));
        }

        // same level -> right
        TreeNode cur = result.left;
        for (int i = 1; i < root.children.size(); i++) {
            cur.right = encode(root.children.get(i));
            cur = cur.right;
        }
        return result;
    }
	
    // Decodes your binary tree to an n-ary tree.
    public Node decode(TreeNode root) {
        if (root == null) {
            return null;
        }
        Node result = new Node(root.val, new ArrayList<>());

        // first child is root.left, all other children are on the right subtree
        TreeNode cur = root.left;
        while (cur != null) {
            result.children.add(decode(cur));
            cur = cur.right;
        }

        return result;
    }
}

432. All O`one Data Structure

https://leetcode.com/problems/all-oone-data-structure/


对于插入和删除操作,使用HashMap可以轻松达到O(1)复杂度。而在此基础上,要想实现取最值也是O(1)复杂度,还需要借助双向链表LinkedHashSet。人工维护一个按照value大小排序的链表,每个链表节点中包含一个LinkedHashSet,里面存储了所有value为当前链表节点值的key。每一次插入删除节点时,更新链表以及节点的LinkedHashSet。具体而言,需要时刻跟踪链表的headtail两个头尾节点:

  • 插入key时,检查这个节点是不是新的节点
    • 如果是,把headtail都指向这个节点,更新节点中的Set以及全局Map
    • 如果不是,这个key对应的节点value自增。由于发生了自增,需要把key从之前的节点Set中踢掉,然后用新value创建一个新节点,把新节点插到原来节点的后面,再把key插到新节点的key中去。最后更新全局Map
  • 删除key时,需要确认这个key是否存在,之后
    • 如果是头节点,直接更新headtail
    • 否则,由于发生了自减,把把key从原来节点Set中踢掉,然后用新value创建一个新节点,把新节点插到原来节点的前面,再把key插到新节点的key中去。最后更新全局Map
  • 获取最大最小值,直接从链表headtail节点中的Set中提取,调用迭代器方法即可
class AllOne {

    Map<String, Node> map;
    Node head, tail;
    
    class Node {
        int val;
        LinkedHashSet<String> keys;
        Node next, pre;
        public Node(int val) {
            this.val = val;
            keys = new LinkedHashSet();
            next = pre = null;
        }
    }

    /** Initialize your data structure here. */
    public AllOne() {
        map = new HashMap();
        head = tail = null;
    }
    
    /** Inserts a new key <Key> with value 1. Or increments an existing key by 1. */
    public void inc(String key) {
        if (map.containsKey(key)) {
            // get old node
            Node node = map.get(key);
            int val = node.val;
            
            // remove old key in the old node
            node.keys.remove(key);
            
            // add new node with new val
            if (node.next == null || node.next.val != val + 1) {
                Node newNode = new Node(val + 1);
                newNode.pre = node;
                newNode.next = node.next;
                node.next = newNode;
                if (newNode.next != null) {
                    newNode.next.pre = newNode;
                }
            }
            
            // add new key to the next node
            node.next.keys.add(key);
            
            // update node
            map.put(key, node.next);
            
            // update head
            if (node.keys.isEmpty()) {
                if (node == head) {
                    head = node.next;
                }
                node.next.pre = node.pre;
                if (node.pre != null) {
                    node.pre.next = node.next;
                }
            }
            
            // update tail
            if (tail == node) {
                tail = node.next;
            }
        } else {
            if (head == null || head.val > 1) {
                Node node = new Node(1);
                node.next = head;
                if (head != null) {
                    head.pre = node;
                }
                head = node;
                if (tail == null) {
                    tail = node;
                }
            }
            head.keys.add(key);
            map.put(key, head);
        }
    }
    
    /** Decrements an existing key by 1. If Key's value is 1, remove it from the data structure. */
    public void dec(String key) {
        if (!map.containsKey(key)) {
            return;
        }
        
        // get old node
        Node node = map.get(key);
        int val = node.val;
        
        // remove old val from old node
        node.keys.remove(key);
        
        // remove old key from map
        map.remove(key);
        
        // if it's head, update head and tail
        if (val == 1) {
            if (node.keys.isEmpty()) {
                if (head == node) {
                    head = node.next;
                }
                if (head != null) {
                    head.pre = null;
                }
                if (tail == node) {
                    tail = null;
                }
            }
        } else {
            // add new node at pre position
            if (node.pre == null || node.pre.val != val - 1) {
                Node newNode = new Node(val - 1);
                newNode.next = node;
                newNode.pre = node.pre;
                node.pre = newNode;
                if (newNode.pre != null) {
                    newNode.pre.next = newNode;
                }
            }
            
            // add new key to the newNode or pre node
            node.pre.keys.add(key);
            
            // update node
            map.put(key, node.pre);
            
            // update tail
            if (node.keys.isEmpty()) {
                if (tail == node) {
                    tail = node.pre;
                }
                node.pre.next = node.next;
                if (node.next != null) {
                    node.next.pre = node.pre;
                }
            }
            
            // update head
            if (head == node) {
                head = node.pre;
            }
        }
    }
    
    /** Returns one of the keys with maximal value. */
    public String getMaxKey() {
        return tail == null ? "" : tail.keys.iterator().next();
    }
    
    /** Returns one of the keys with Minimal value. */
    public String getMinKey() {
        return head == null ? "" : head.keys.iterator().next();
    }
}

/**
 * Your AllOne object will be instantiated and called as such:
 * AllOne obj = new AllOne();
 * obj.inc(key);
 * obj.dec(key);
 * String param_3 = obj.getMaxKey();
 * String param_4 = obj.getMinKey();
 */

433. Minimum Genetic Mutation

https://leetcode.com/problems/minimum-genetic-mutation/


使用回溯算法,遍历bank,每一次mutate一个字母,看是否能得到end;如果不能,退回尝试mutate其他字母。

class Solution {
    private int count = Integer.MAX_VALUE;
    
    public int minMutation(String start, String end, String[] bank) {
        recurse(start, end, bank, 0, new HashSet<String>());
        return count == Integer.MAX_VALUE ? -1 : count;
    }

    private void recurse(String start, String end, String[] bank, int mutateNum, Set<String> visited) {
        if (start.equals(end)) {
            count = Math.min(count, mutateNum);
        }

        for (String e : bank) {
            int diff = 0;
            
            // only 1 diff is allowed to mutate to the next state
            for (int i = 0; i < e.length(); i++) {
                if (start.charAt(i) != e.charAt(i)) {
                    diff++;
                    if (diff > 1) {
                        break;
                    }
                }
            }

            // backtrack, try to mutate to next state then come back
            if (diff == 1 && !visited.contains(e)) {
                visited.add(e);
                recurse(e, end, bank, mutateNum + 1, visited);
                visited.remove(e);
            }
        }
    }
}

434. Number of Segments in a String

https://leetcode.com/problems/number-of-segments-in-a-string/


去掉空白部分,再分割字符串。

class Solution {
    public int countSegments(String s) {
        s = s.trim();
        if (s == null || s.length() == 0) {
            return 0;
        }

        String[] result = s.split("\\s+");
        return result.length;
    }
}

435. Non-overlapping Intervals

https://leetcode.com/problems/non-overlapping-intervals/


按照每个区间的尾元素,从小到大排序。之后遍历所有区间,如果相邻两个区间不重叠,就把不重叠的区间个数加1。最后将所有区间个数减去不重叠的区间个数,得到需要移除的区间个数。

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        if (intervals == null || intervals.length == 0 || intervals[0].length == 0) {
            return 0;
        }

        // sort by end element
        Arrays.sort(intervals, (int[] a, int[] b) -> { return a[1] - b[1]; });

        int end = intervals[0][1];
        int nonOverlap = 1;        

        for (int i = 1; i < intervals.length; i++) {
            if (intervals[i][0] >= end) {
                end = intervals[i][1];
                nonOverlap++;
            }
        }
        
        return intervals.length - nonOverlap;
    }
}

436. Find Right Interval

https://leetcode.com/problems/find-right-interval/


使用桶,找出最小的start和最大的end,确定桶的大小。因为每个区间的start是唯一的,因此能够根据每个区间的start值,判断应该丢在哪个桶内。处理之后,每个桶里存放的就是下一个区间的起始值。注意部分桶可能为空,因此需要用相邻非空桶里的值覆盖。

class Solution {
    public int[] findRightInterval(int[][] intervals) {
        if (intervals == null || intervals.length == 0 || intervals[0].length == 0) {
            return new int[] {};
        }

        int[] result = new int[intervals.length];

        // find min start and max end
        int min = intervals[0][0], max = intervals[0][1];
        for (int i = 1 ; i < intervals.length; i++) {
            min = Math.min(min, intervals[i][0]);
            max = Math.max(max, intervals[i][1]);
        }

        int[] buckets = new int[max - min + 1];
        Arrays.fill(buckets, -1);
        for (int i = 0; i < intervals.length; i++) {
            buckets[intervals[i][0] - min] = i;
        }

        for (int i = buckets.length - 2; i >= 0; i--) {
            if (buckets[i] == -1) {
                buckets[i] = buckets[i+1]; 
            }
        }   

        for (int i = 0; i < intervals.length; i++) {
            result[i] = buckets[intervals[i][1] - min];
        }

        return result;
    }
}

437. Path Sum III

https://leetcode.com/problems/path-sum-iii/


使用一个Hashmap,统计每一条path sum以及对应出现的次数。遍历每一个节点的时候,可能出现的path sum由三部分组成:

  1. 以某个上层节点起始,当前节点结束的路径
  2. 左子树中的路径
  3. 右子树中的路径

在每完成一次递归调用返回上层时,需要注意把map中当前path sum对应的出现次数减少1。(可以理解为,通过左子树累加出来的次数,并不能对右子树产生效果,反之同理)

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int pathSum(TreeNode root, int sum) {
        if (root == null) {
            return 0;
        }
        
        // <root to current node's sum, frequency>
        Map<Long, Integer> map = new HashMap<>();
        map.put(0L, 1);
        return findPathSum(root, 0, sum, map);  
    }

    private int findPathSum(TreeNode node, long sum, int target, Map<Long, Integer> map) {
        if (node == null) {
            return 0;
        }

        // update the prefix sum by adding the current node's val
        sum += node.val;
        
        // get the number of valid path, ended by the current node
        int numPathToCurr = map.getOrDefault(sum - target, 0);
        
        // update the map with the current sum
        map.put(sum, map.getOrDefault(sum, 0) + 1);

        // the total number of valid paths in the subtree rooted at the current node's left child
        // the total number of valid paths in the subtree rooted at the current node's right child
        // the number of valid paths ended by the current node
        int result = numPathToCurr + findPathSum(node.left, sum, target, map) + findPathSum(node.right, sum, target, map);

        // restore the map, as the recursion goes from the bottom to the top
        map.put(sum, map.get(sum) - 1);
        
        return result;
    }
}

438. Find All Anagrams in a String

https://leetcode.com/problems/find-all-anagrams-in-a-string/


使用双指针滑动窗口,用一个变量count统计频率为正的字母个数。移动滑动窗口时,如果count为0,说明窗口中能组成一个anagram。

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> result = new ArrayList<>();
        if (s == null || p == null || s.length() == 0 || p.length() == 0) {
            return result;
        }

        int[] arr = new int[26];
        for (int i = 0; i < p.length(); i++) {
            arr[p.charAt(i) - 'a']++;
        }

        int left = 0, right = 0;
        int count = p.length();
        while (right < s.length()) {
            // character is existing in the map, update count and map
            if (arr[s.charAt(right) - 'a'] >= 1) {
                count--;
            }
            
            arr[s.charAt(right) - 'a']--;
            right++;

            // sum of all frequency >= 0, and count is the sum of all positive frequency
            // so, every frequency is 0 if and only if count is 0
            if (count == 0) {
                result.add(left); 
            }

            // window size is p's length, shrink the window size from the begin
            if (right - left == p.length()) {
                // restore a freq, and only update if in the map
                if (arr[s.charAt(left) - 'a'] >= 0) {
                    count++;
                }
                arr[s.charAt(left) - 'a']++;
                left++;
            } 
        }
        
        return result;  
    }
}

439. Ternary Expression Parser

https://leetcode.com/problems/ternary-expression-parser/


统计?:的配对情况,遍历时如果遇到的?:数量相同,就检查?之前是T还是F,并根据相应true或false情况,截取对应的子字符串进行递归检查。

class Solution {
    public String parseTernary(String expression) {
        for (int i = 0; i < expression.length(); i++) {
            if (expression.charAt(i) == '?') {
                int flag = 1;
                for (int j = i + 1; j < expression.length(); j++) {
                    if (expression.charAt(j) == '?') {
                        flag++;
                    }
                    if (expression.charAt(j) == ':') {
                        flag--;
                    }

                    // ? and : are equal now
                    if (flag == 0) {
                        if (expression.charAt(i-1) == 'T') {
                            return parseTernary(expression.substring(i+1, j));
                        } else {
                            return parseTernary(expression.substring(j+1, expression.length()));
                        }
                    }
                }
            }
        }

        return expression;
    }
}

440. K-th Smallest in Lexicographical Order

https://leetcode.com/problems/k-th-smallest-in-lexicographical-order/


可以把起始的1~9看成9棵树的根节点。对于根节点1,它有10个儿子,分别是10, 11, 12, … 19,而这10个子节点分别又可以有10个子节点,例如节点10有儿子100, 101, 102…以此类推,只要没有超过范围n,每个节点都可以有10个子节点,因此问题也就转化为了十叉树的先后遍历问题。

要确定每棵树的大小,其实就是确定以当前节点为根,子树中节点中的个数。观察发现,对于节点i,子节点中最小的一个节点一定是10*i。通过逐层*10,能够确定每一层的节点大小范围,进而确定每一层的节点个数,由此得到树的大小。之后,将树的大小与k进行判断,如果树的大小小于k,说明目标值并不在当前的树中,直接跳过;否则,才继续递归查找子树。

class Solution {
    public int findKthNumber(int n, int k) {
        long cur = 1;
        
        while (k > 1) {
            long count = findSubTreeCount(cur, n);

            if (count <= k - 1) {
                // travel to next tree
                k -= count;
                cur = cur + 1;
            } else {
                // travel to leftmost child
                cur = cur * 10;
                k -= 1;
            }
        }

        return (int)cur;
    }

    private long findSubTreeCount(long cur, int n) {
        long count = 0;
        long upper = cur + 1;
        
        while (cur <= n) {
            // number of nodes in this level
            count += Math.min(n + 1, upper) - cur;
            cur = cur * 10;
            upper = upper * 10;
        }
        
        return count;
    }
}

441. Arranging Coins

https://leetcode.com/problems/arranging-coins/


解方程即可,注意考虑溢出问题。

class Solution {
    public int arrangeCoins(int n) {
        return (int)((-1 + Math.sqrt(1 + 8 * (long)n)) / 2);
    }
}

442. Find All Duplicates in an Array

https://leetcode.com/problems/find-all-duplicates-in-an-array/


由于数组中的数范围都在1到n之间,因此可以遍历数组做一个映射,遇到数字num的时候,把nums[abs(num)] - 1对应的数翻转成负数。在翻转前进行判断,如果已经是负数了,说明之前遇到过num,这就是一个重复数。

class Solution {
    public List<Integer> findDuplicates(int[] nums) {
        List<Integer> result = new ArrayList<>();

        for (int i = 0; i < nums.length; i++) {
            int index = Math.abs(nums[i]) - 1;
            if (nums[index] < 0) {
                result.add(index + 1);
            }
            nums[index] = -nums[index];
        }

        return result;
    }
}

443. String Compression

https://leetcode.com/problems/string-compression/


由于限定只能用O(1)的空间,因此只能用双指针遍历输入字符数组,右边的处理每个字符,左边的负责原地修改数组内容。解题流程分为3步:

  1. 遇到相同的字符就用循环跳过,并统计出现的相同字符个数,直到遇上下一个不同的字符
  2. 把当前处理的字符拷贝到数组左侧
  3. 如果当前的字符被压缩,把重复的个数转化成字符串,添加到字符后面
class Solution {
    public int compress(char[] chars) {
        int result = 0, index = 0;

        while (index < chars.length) {
            char currentChar = chars[index];
            int count = 0;

            // compress same character
            while (index < chars.length && chars[index] == currentChar) {
                index++;
                count++;
            }
            
            // add current char to array
            chars[result] = currentChar;
            result++;

            // convert count to string format then add
            if (count != 1) {
                for (char c : Integer.toString(count).toCharArray())  {
                    chars[result] = c;
                    result++;
                }
            }
        }

        return result;
    }
}

444. Sequence Reconstruction

https://leetcode.com/problems/sequence-reconstruction/


使用一个数组index记录下nums中每个元素的下标。之后遍历seqs中的每个list,进行如下检查:

  1. list中的每个数必须在[1, n]的范围内
  2. 任意list中,前一个数在nums中的下标必须小于后一个数在nums中的下标,保证拓扑序的一致性
  3. 如果前一个数在nums中的下标 + 1为后一个数在nums中的下标,便能够保证这两个数的拓扑序的唯一性。必须保证所有相邻两数之间都能满足这一拓扑序的唯一性,才能最终覆盖序列的所有数
class Solution {
    public boolean sequenceReconstruction(int[] nums, List<List<Integer>> seqs) {
        if (seqs == null || seqs.size() == 0) {
            return false;
        }
        int len = nums.length;
        boolean[] covered = new boolean[len + 1];

        // for each element in nums, record its index
        int[] index = new int[len + 1];
        for (int i = 0; i < len; i++) {
            index[nums[i]] = i;
        }

        for (List<Integer> list : seqs) {
            for (int i = 0; i < list.size(); i++) {
                int cur = list.get(i);

                // each number should be in the range of [1, n]
                if (cur < 1 || cur > nums.length) {
                    return false;
                }

                // previous element must appear before current element in nums
                if (i == 0) {
                    continue;
                }
                int prev = list.get(i - 1);
                if (index[prev] >= index[cur]) {
                    return false;
                }

                // ensure one-by-one order in index
                if (!covered[prev] && index[prev] + 1 == index[cur]) {
                    covered[prev] = true;
                }
            }
        }

        // must cover all numbers
        for (int i = 0; i < nums.length - 1; i++) {
            if (covered[nums[i]] == false) {
                return false;
            }
        }

        return true;
    }
}

445. Add Two Numbers II

https://leetcode.com/problems/add-two-numbers-ii/


由于限制不能反转链表,因此只能从高位开始按顺序递归累加。先提前获取两个链表的长度,确保能先从长的链表开始遍历,每完成一次遍历,更新两个链表的长度差,判断此时长链表是否与短链表的最高位齐平。遍历的时候有两种情况:

  • 单独遍历长链表节点时,节点的值由长链表节点的值 + 进位组成。
  • 同时遍历双链表节点时,节点的值由两个链表节点的值 + 进位组成

当前位调用递归的时候,能够获取后面所有位数的结果post,并同时携带了进位信息。注意,进位信息保存在post的最前一个节点,并不需要额外用一个变量储存。同理,全部递归完成后,处理第一个节点的进位也是用相同的方法。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        int size1 = getListLength(l1);
        int size2 = getListLength(l2);
        ListNode head = new ListNode(1);

        // make sure l1.length >= l2.length
        head.next = size1 < size2 ? helper(l2, l1, size2 - size1) : helper(l1, l2, size1 - size2);

        // handle the first digit
        if (head.next.val > 9) {
            head.next.val = head.next.val % 10;
            return head;
        }
        
        return head.next;
    }

    public int getListLength(ListNode l) {
        int len = 0;
        while (l != null) {
            l = l.next;
            len++;
        }
        return len;
    }

    public ListNode helper(ListNode l1, ListNode l2, int lenDiff) {
        if (l1 == null) {
            return null;
        }

        ListNode current = lenDiff == 0 ? new ListNode(l1.val + l2.val) : new ListNode(l1.val);
        ListNode post = lenDiff == 0 ? helper(l1.next, l2.next, 0) : helper(l1.next, l2, lenDiff - 1);

        // handle carry
        if (post != null && post.val > 9) {
            current.val += 1;
            post.val = post.val % 10;
        }

        current.next = post;
        return current;
    }
}

446. Arithmetic Slices II - Subsequence

https://leetcode.com/problems/arithmetic-slices-ii-subsequence/


使用二维dp,其中dp[i][j]表示包含dp[i]dp[j]的arithmetic序列个数。遍历数组,记录每一个数出现的下标,并把所有下标归类到一个链表中,再把<下标, 链表>映射以HashMap形式存储。之后双重循环,锁定ij,利用A[k] - A[i] = A[i] - A[j] (k < i < j)这个性质,从HashMap中查找是否存在合适的k值。如果存在,dp[i][j]值的就在dp[k][i]的基础上 + 1,表示新增了一个arithmetic序列。每次完成循环后,当前dp[i][j]新加的值就统计到总的结果中。

class Solution {
    public int numberOfArithmeticSlices(int[] nums) {
        int n = nums.length;
        int[][] dp = new int[n][n];

        Map<Long, List<Integer>> map = new HashMap();
        for (int i = 0; i < n; i++) {
            map.putIfAbsent((long)nums[i], new ArrayList());
            map.get((long)nums[i]).add(i);
        }

        int result = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                long target = 2 * (long) nums[i] - nums[j];

                if (map.containsKey(target)) {
                    for (int k : map.get(target)) {
                        if (k < i) {
                            dp[i][j] += (dp[k][i] + 1);
                        }
                    }
                }

                result += dp[i][j];
            }
        }

        return result;
    }
}

447. Number of Boomerangs

https://leetcode.com/problems/number-of-boomerangs/


双重循环,两两判断点之间点的距离,固定i并得到当前points[i]points[j]之间的距离,通过hashmap.merge()方法得到这一轮i循环中这个距离出现的次数,如果已经出现了distCount次,组合数增加就要增加distCount - 1(相当于在加入当前这段距离之前,原来的重复距离每一个都要跟新加入的距离组成配对)。由于每个组合内后两个点顺序可以互换,因此最后总的结果还要乘2。

注意,这里的hashmap.merge()方法作用是依照BiFunction,替换更新key对应的value。在本题中,其时间复杂度是O(1),因为BiFunction是两数求和,而其余的合并底层操作,比如get和remove等也都是O(1)。

class Solution {
    public int numberOfBoomerangs(int[][] points) {
        int result = 0;
        Map<Integer, Integer> map = new HashMap<>();

        for (int i = 0; i < points.length; i++) {
            for (int j = 0; j < points.length; j++) {
                if (i == j) {
                    continue;
                }

                // count of current distance between two points
                int distCount = map.merge(getDist(points[i], points[j]), 1, Integer::sum);

                // shake hand rules
                result += distCount - 1;
            }
            
            map.clear();
        }

        // double order, double result
        return result * 2;
    }

    public int getDist(int a[], int b[]) {
        return (a[0] - b[0])*(a[0] - b[0]) + (a[1] - b[1])*(a[1] - b[1]);
    }
}

448. Find All Numbers Disappeared in an Array

https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/


两次循环,第一次循环访问每个数对应的下标,把下标位置上对应的数加上n。第二次循环直接顺序检查每个下标,如果当前下标对应的值小于n,说明之前并没有被访问修改过,因此说明数组中并不存在对应的数字来跳转访问这个下标。

class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        List<Integer> result = new ArrayList<>();
        int n = nums.length;

        // modify original number and add n
        for (int i = 0; i < n; i++) {
            nums[(nums[i]-1) % n] += n;
        }

        // numbers still less than n are disappeared
        for (int i = 0; i < n; i++) {
            if (nums[i] <= n) {
                result.add(i+1);
            }
        }
        
        return result;
    }
}

449. Serialize and Deserialize BST

https://leetcode.com/problems/serialize-and-deserialize-bst/


依然是前序遍历编码解码的思想。由于要求编码的结果尽可能紧凑,可以将null的值压缩成n

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        StringBuilder sb = new StringBuilder();
        serializeHelper(root, sb);
        return sb.toString();
    }

    private void serializeHelper(TreeNode node, StringBuilder sb) {
        if (node == null) {
            sb.append("n,");
            return;
        }
        sb.append(node.val).append(",");
        serializeHelper(node.left, sb);
        serializeHelper(node.right, sb);
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        String[] nodes = data.split(",");
        int[] index = new int[1];
        return deserializeHelper(nodes, index);
    }
    private TreeNode deserializeHelper(String[] nodes, int[] index) {
        if (nodes[index[0]].equals("n")) {
            index[0]++;
            return null;
        }
        TreeNode cur = new TreeNode(Integer.valueOf(nodes[index[0]]));
        index[0]++;
        cur.left = deserializeHelper(nodes, index);
        cur.right = deserializeHelper(nodes, index);
        return cur;
    }
}

// Your Codec object will be instantiated and called as such:
// Codec ser = new Codec();
// Codec deser = new Codec();
// String tree = ser.serialize(root);
// TreeNode ans = deser.deserialize(tree);
// return ans;

450. Delete Node in a BST

https://leetcode.com/problems/delete-node-in-a-bst/


找到目标节点,从右子树中找到最小的节点进行替换。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode deleteNode(TreeNode root, int key) {
        if (root == null) {
            return root;
        }

        // Search in subtrees
        if (root.val < key) {
            root.right = deleteNode(root.right, key);
            return root;
        } else if (root.val > key) {
            root.left = deleteNode(root.left, key);
            return root;
        }

        // No child or only one child
        if (root.left == null && root.right == null) {
            return null;
        } else if (root.left == null) {
            return root.right;
        } else if (root.right == null) {
            return root.left;
        }

        // Two children
        if (root.right.left == null) {
            root.right.left = root.left;
            return root.right;
        } else {
            TreeNode smallest = deleteSmallest(root.right);
            smallest.left = root.left;
            smallest.right = root.right;
            return smallest;
        }
    }

    // Find the smallest node in right subtree and delete it
    private TreeNode deleteSmallest(TreeNode root) {
        TreeNode cur = root.left;
        TreeNode pre = root;
        while (cur.left != null) {
            pre = cur;
            cur = cur.left;
        }
        pre.left = cur.right;
        return cur;
    }
}

451. Sort Characters By Frequency

https://leetcode.com/problems/sort-characters-by-frequency/


存储<character, frequency>map序列并按照frequency降序排序,之后按照顺序输出每个字符即可。

class Solution {
    public String frequencySort(String s) {
        int[] map = new int[128];

        for (char c : s.toCharArray()) {
            map[c]++;
        }

        // insert <character, frequency> into a priority queue, with descending frequency order
        PriorityQueue<int[]> q = new PriorityQueue<>((a, b) -> b[1] - a[1]);

        // insert only if the char is present in input string
        for (int i = 0; i < 128; i++) {
            if (map[i] > 0) {
                q.offer(new int[] {i, map[i]});
            }
        }

        char[] result = new char[s.length()];
        int index = 0;

        // output characters from queue
        while (!q.isEmpty()) {
            int[] entry = q.poll();
            char letter = (char) entry[0];
            int frequency = entry[1];

            for (int i = 0; i < frequency; i++) {
                result[index] = letter;
                index++;
            }
        }

        return String.valueOf(result);        
    }
}

452. Minimum Number of Arrows to Burst Balloons

https://leetcode.com/problems/minimum-number-of-arrows-to-burst-balloons/


本质上是区间合并问题的变种,先按照xend对每个区间进行排序,之后遍历,如果相邻两个区间没有重叠,就说明这两个区间一定需要2箭分别穿过,总的箭数量+1;反之说明两个区间可以合并,用1箭穿过。

class Solution {
    public int findMinArrowShots(int[][] points) {
        if (points == null || points.length == 0) {
            return 0;
        }

        // sort by x-end coordinate
        Arrays.sort(points, (a, b) -> { return a[1] == b[1] ? 0 : a[1] > b[1] ? 1 : -1; });

        int result = 1;
        int curEnd = points[0][1];
        for (int i = 1; i < points.length;) {
            // need another arrow to shoot
            if (points[i][0] > curEnd) {
                curEnd = points[i][1];
                result++;
            } else {
                // two balloons can be shot with one arrow, merge them
                i++;
            }
        }
        
        return result;
    }
}

453. Minimum Moves to Equal Array Elements

https://leetcode.com/problems/minimum-moves-to-equal-array-elements/


n - 1个数增大1,等同于1个数减少1。因此只要找出数组最小值,再计算其他每个数跟最小值的差值总和是多少即可。

class Solution {
    public int minMoves(int[] nums) {
        int result = 0;

        // find minimum value of the array
        int min = Integer.MAX_VALUE; 
        for (int n : nums) {
            min = Math.min(min, n);
        }

        // n - 1 number increment by 1 = 1 number decrement by 1, and all numbers should match with minimum value
        for (int i = 0; i < nums.length; i++) {
            result += nums[i] - min;
        }

        return result;
    }
}

454. 4Sum II

https://leetcode.com/problems/4sum-ii/


对4sum进行拆分,先算出nums1nums2中两两组合能得到的所有sum,并统计每个sum出现的个数,把结果存到map中。再遍历nums3nums4中所有的两两组合,如果发现map中有对应的key(负值),说明当前4个数字能组成4sum为0的组合。

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0 ||
            nums3 == null || nums3.length == 0 || nums4 == null || nums4.length == 0) {
                return 0;
            }

        int result = 0;
        int len = nums1.length;
        
        // <sum of two numbers, count>
        Map<Integer, Integer> map = new HashMap<>();
        
        for (int i = 0; i < len; i++) {
            for (int j = 0; j < len; j++) {
                map.compute(nums1[i] + nums2[j], (k, v) -> v == null ? 1 : v + 1);
            }
        }     

        for (int i = 0; i < len; i++) {
            for (int j = 0; j < len; j++) {
                if (map.containsKey(-(nums3[i] + nums4[j]))) {
                    result += map.get(-(nums3[i] + nums4[j]));     
                }
            }
        } 
                    
        return result;
    }
}

455. Assign Cookies

https://leetcode.com/problems/assign-cookies/


先对两个数组进行排序。遍历s,总是尝试用当前最小size的饼干去满足当前的小孩,如果能满足的话,小孩个数+1。无论是否成功满足,都必须遍历下一块饼干。

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);

        int result = 0;
        for (int i = 0; result < g.length && i < s.length; i++) {
            // always try to use the minimum size to satisfy current child
            if (g[result] <= s[i]) {
                result++;
            }
        }

        return result;
    }
}

456. 132 Pattern

https://leetcode.com/problems/132-pattern/


单调栈,从数组右侧开始遍历,如果当前数nums[i]比栈顶的元素更大,就把栈顶所有小于等于nums[i]的数弹出,弹出的数作为s3 candidate,而栈顶作为s2 candidate。这一步的目的是确保s2 > s3。在这个前提下,如果又能在遍历时发现nums[i] < s3,则说明nums[i]是合适的s1 candidate。

class Solution {
    public boolean find132pattern(int[] nums) {
        int s3 = Integer.MIN_VALUE;
        Stack<Integer> stack = new Stack<Integer>();

        for (int i = nums.length - 1; i >= 0; i--) {
            if (nums[i] < s3) {
                // num[i] is s1 candidate
                return true;
            } else {
                // pop out all stack top numbers that are smaller than nums[i]. Left numbers are s2 candidates
                while (!stack.isEmpty() && nums[i] > stack.peek()) {
                    s3 = stack.pop();
                }
                stack.push(nums[i]); 
            }
        }
        
        return false;
    }
}

457. Circular Array Loop

https://leetcode.com/problems/circular-array-loop/


用DFS,维护每个元素的访问状态,转化为DFS检测环问题。注意需排除长度为1,以及包含前后movement的情况。

class Solution {
    private int NOT_VISITED = 0;
    private int VISITING = 1;
    private int VISITED = 2;

    public boolean circularArrayLoop(int[] nums) {
        int N = nums.length;
        int[] visited = new int[N];
        for (int i = 0; i < N; i++) {
            if (visited[i] == NOT_VISITED && dfs(i, visited, nums)) {
                return true;
            }
        }
            
        return false;
    }

    // return true if there is a cycle
    private boolean dfs(int cur, int[] visited, int[] nums) {
        int N = nums.length;
        if (visited[cur] == VISITING) {
            return true;
        }
        if (visited[cur] == VISITED) {
            return false;
        }

        visited[cur] = VISITING;

        int next = cur + nums[cur];
        next %= N;
        if (next < 0) {
            next += N;
        }

        // invalid cycle if the length is 1, or it consists of forward and backward movement
        if (next == cur || nums[next] * nums[cur] < 0) {
            visited[cur] = VISITED;
            return false;
        }

        if (dfs(next, visited, nums)) {
            return true;
        }
        visited[cur] = VISITED;
        
        return false;
    }
}

458. Poor Pigs

https://leetcode.com/problems/poor-pigs/


需要至少minutesToTest / minutesToDie + 1轮测试,每一轮测试中的每只猪都要能覆盖到一些bucket,必须要保证通过全部的测试,所有的bucket都被覆盖到。

class Solution {
    public int poorPigs(int buckets, int minutesToDie, int minutesToTest) {
        int result = 0;
        
        while (Math.pow(minutesToTest / minutesToDie + 1, result) < buckets) {
            result += 1;
        }
            
        return result;
    }
}

459. Repeated Substring Pattern

https://leetcode.com/problems/repeated-substring-pattern/


从输入字符串的中间开始,向前遍历到能整除字符串长度的下标,因为这个下标可能是重复pattern的结尾。之后双指针向前移动,确保双指针经过的内容完全一致,直到右指针到达字符串终点。

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        char [] chars = s.toCharArray();
        int len = chars.length;

        for (int i = len / 2; i > 0; i--) {
            // potential pattern start
            if (len % i == 0) {
                int left = 0;
                int right = i;

                // should always match
                while (right < len && chars[left] == chars[right]) {
                    left++;
                    right++;
                }

                if (right == len) {
                    return true;
                }
            }
        }

        return false;
    }
}

460. LFU Cache

https://leetcode.com/problems/lfu-cache/


维护3个HashMap,第一个keyToVal用于存储每个输入的(key, value)对;第二个keyToCount用于存储每个key被使用了几次;第三个countToLRUKeys用于存储对于每个使用次数count,分别有哪些key被使用了这么多次,相同使用次数的key用一个HashSet存在一起。

put新的(key, value)时,首先确保capacity有效。之后讨论情况:

  • 如果当前key已经存在,则覆盖原来keyToVal保存的值,直接返回
  • 如果这个key不存在,并且已经没有空余位置,需要从当前使用次数最少的key中踢掉一个,注意踢的时候既要从countToLRUKeys,也要从keyToVal踢,踢完之后,更新keyToVal,并且注意此时的最小使用count一定是1(要么是新key,要么之前的最小值就是1),在这个基础上更新keyToCountcountToLRUKeys

get(key)时,先确保key在keyToVal中存在。之后从countToLRUKeys中移除掉自己的记录(因为自己的count + 1了),再看情况决定是否需要更新min。最后更新keyToCount,返回keyToVal的取值。

class LFUCache {
    private int min;
    private int capacity;
    private HashMap<Integer, Integer> keyToVal;
    private HashMap<Integer, Integer> keyToCount;
    private HashMap<Integer, LinkedHashSet<Integer>> countToLRUKeys;

    public LFUCache(int capacity) {
        this.min = -1;
        this.capacity = capacity;
        this.keyToVal = new HashMap<>();
        this.keyToCount = new HashMap<>();
        this.countToLRUKeys = new HashMap<>();
    }

    public int get(int key) {
        if (!keyToVal.containsKey(key)) {
            return -1;
        }

        int count = keyToCount.get(key);
        
        // remove key from current count (since we will increase count)
        countToLRUKeys.get(count).remove(key);
        
        // no element is of min count
        if (count == min && countToLRUKeys.get(count).size() == 0) {
            min++;
        }

        updateCount(key, count + 1);
        return keyToVal.get(key);
    }

    public void put(int key, int value) {
        if (capacity <= 0) {
            return;
        }

        if (keyToVal.containsKey(key)) {
            // update key's value
            keyToVal.put(key, value);
            
            // update key's count
            get(key);
            return;
        } 

        // new element and no enough space, evict LRU from this min count bucket
        if (keyToVal.size() >= capacity) {
            int evictKey = countToLRUKeys.get(min).iterator().next();
            countToLRUKeys.get(min).remove(evictKey);
            keyToVal.remove(evictKey);
        }

        // adding new key and value
        keyToVal.put(key, value);

        // min count must be 1 now
        min = 1;
        
        // adding new key and min count
        updateCount(key, min);
    }

    private void updateCount(int key, int count) {
        keyToCount.put(key, count);
        if (!countToLRUKeys.containsKey(count)) {
            countToLRUKeys.put(count, new LinkedHashSet<>());
        }
        countToLRUKeys.get(count).add(key);
    }
}

/**
 * Your LFUCache object will be instantiated and called as such:
 * LFUCache obj = new LFUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

461. Hamming Distance

https://leetcode.com/problems/hamming-distance/


要求出不同的位数,其实就是看xy异或结果中1的个数,可以使用循环右移法,累加出1的总数。

class Solution {
    public int hammingDistance(int x, int y) {
        int xor = x ^ y;

        int result = 0;
        for (int i = 0; i < 32; i++) {
            result += (xor >> i) & 1;
        }
        
        return result;
    }
}

462. Minimum Moves to Equal Array Elements II

https://leetcode.com/problems/minimum-moves-to-equal-array-elements-ii/


将数组排序,让最大和最小的数组成一对,次大和次小的数组成一对,以此类推,累加每组数对的差值。

class Solution {
    public int minMoves2(int[] nums) {
        Arrays.sort(nums);
        int count = 0;

        int i = 0, j = nums.length - 1;
        while (i < j) {
            count += nums[j] - nums[i];
            i++;
            j--;
        }
        
        return count;
    }
}

463. Island Perimeter

https://leetcode.com/problems/island-perimeter/


检查每个方格,如果是岛,先加上4的边长;之后检查这个岛的左、上是否有相邻岛,如果有相邻就减2边长,因为相邻的两个岛都不需要交界边了。为了避免重复,检查相邻岛时只需要检查左、上即可(右、下也可以)。

class Solution {
    public int islandPerimeter(int[][] grid) {
        if (grid == null || grid.length == 0 || grid[0].length == 0) {
            return 0;
        }
        
        int result = 0;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                if (grid[i][j] == 1) {
                    result += 4;
                    
                    if (i > 0 && grid[i-1][j] == 1) {
                        result -= 2;
                    }
                    if (j > 0 && grid[i][j-1] == 1) {
                        result -= 2;
                    }
                }
            }
        }
        
        return result;
    }
}

464. Can I Win

https://leetcode.com/problems/can-i-win/


排除掉一些特殊情况,比如desiredTotal太小或者太大,或者恰好二者相差为1,其余情况使用DFS,用二进制状态码表示当前已经选择的数字,并且再推导出选择下一个数字后的新状态,逐步深入判断先手玩家能否取胜,再修改对应的DP数组。

class Solution {
    public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
        // first player directly win
        if (maxChoosableInteger >= desiredTotal) {
            return true;
        } else if (desiredTotal <= maxChoosableInteger * 2 - 1) {
            // if max + 1 = desired, first player choose x, second always choose max - x and win
            return maxChoosableInteger + 1 != desiredTotal;
        } else if (maxChoosableInteger * (maxChoosableInteger + 1) / 2 < desiredTotal) {
            // desiredTotal is too large to reach
            return false;
        }

        return dfs(maxChoosableInteger, 0, desiredTotal, new int[(1 << (maxChoosableInteger))]);
    }

    private boolean dfs(int maxChoosableInteger, int chosen, int remaining, int[] memo) {
        if (memo[chosen] != 0) {
            // win case
            return memo[chosen] == 1;
        }

        for (int i = maxChoosableInteger; i >= 1; i--) {
            // current state is chosen and we want to try number i, next state is chosen ^ (1 << i - 1).
            int mask = 1 << (i-1);
            if ((chosen & mask) != 0) {
                continue;
            }
            if (i >= remaining) {
                // reach or exceed desired total
                return true;
            }

            // first player win
            if (!dfs(maxChoosableInteger, chosen | mask, remaining - i, memo)) {
                memo[chosen] = 1;
                return true;
            }
        }
        
        // second player win
        memo[chosen] = -1;
        return false;
    }
}

465. Optimal Account Balancing

https://leetcode.com/problems/optimal-account-balancing/


首先使用两个HashMap统计出每个人的净收入/支出情况。之后使用DFS进行回溯,找到第一个债务不是0的人固定为start,然后在此人后面找到另一个债务也不是0,并且极性与之相反的人,试图让这两个人完成交易互相清算一次债务。以此类推,之后的人按照这个流程,然后根据回溯判断出哪一种方案需要的交易次数最少。

如果要进一步加快运行速度,可以对一些特殊情况进行优化,在开始DFS之前先检查一遍,找出所有双人组,每个双人组中的一个人恰好需要偿还给另一个人所应得的债务。这种交易方式一定是最优的,因为它只需要最少的交易次数,就能让两个人的最终债务情况都变成0。

class Solution {
    public int minTransfers(int[][] transactions) {
        // track each person's net balance
        HashMap<Integer, Integer> map = new HashMap();

        for (int[] transaction : transactions) {
            // Subtract from source
            map.put(transaction[0], map.getOrDefault(transaction[0], 0) - transaction[2]);

            // Add to target
            map.put(transaction[1], map.getOrDefault(transaction[1], 0) + transaction[2]);
        }

        int[] balances = new int[map.size()];
        int idx = 0;
        for (int key : map.keySet()) {
            if (map.get(key) != 0) {
                balances[idx] = map.get(key);
                idx++;
            }
        }  

        return dfs(0, balances);
    }

    public int dfs(int start, int[] balances) {
        // find first non-zero.
        while (start < balances.length && balances[start] == 0) {
            start++;
        }
        
        // all debts are cleared
        if (start == balances.length) {
            return 0;
        }

        int min = Integer.MAX_VALUE;
        
        // Avoid previous duplicate back-to-back balances. Default is 0, since we check non-zero balances anyway
        int prev = 0;

        // FIRST check all pairs of opposite balances that cancel each other out, before diving deep into DFS.
        for (int i = start + 1; i < balances.length; i++) {
            if (balances[start] + balances[i] == 0) {
                balances[i] += balances[start];
                min = Math.min(min, 1 + dfs(start + 1, balances));
                balances[i] -= balances[start];
                prev = balances[i];
                
                // early termination, since this will be the best settling of debts
                return min;
            }
        }
        
        for (int i = start + 1; i < balances.length; i++) {
            if (balances[i] != prev && balances[start] * balances[i] < 0) {
                balances[i] += balances[start];
                min = Math.min(min, 1 + dfs(start + 1, balances));
                balances[i] -= balances[start];
                prev = balances[i];
            }
        }

        // If the min is still Integer.MAX_VALUE, we've settled all balances to 0, so the fewest transactions is now 0.
        return min == Integer.MAX_VALUE ? 0 : min;
    }
}

466. Count The Repetitions

https://leetcode.com/problems/count-the-repetitions/


双指针法,移动idx1使得idx1idx2指向相同字符,分类讨论两种情况,要么是idx2走完了一个s2长度;要么是发现idx1走了一个完整的循环,并用这个idx2长度除以s2长度除以s2重复次数。

class Solution {
    class Index {
        int idx1;
        int idx2;
        public Index(int i1, int i2) {
            idx1 = i1;
            idx2 = i2;
        }
    }
    
    public int getMaxRepetitions(String s1, int n1, String s2, int n2) {
        HashMap<Integer, Index> idxMap = new HashMap();
        
        int len1 = s1.length(), len2 = s2.length();
        int idx1 = 0, idx2 = 0;
        int totalCharsOf1 = len1 * n1;
        while (idx1 < totalCharsOf1) {
            int modIdx1 = idx1 % len1;
            int modIdx2 = idx2 % len2;
            
            if (s1.charAt(modIdx1) == s2.charAt(modIdx2)) {
                int key = modIdx1 * 1000 + modIdx2;
                
                if (idxMap.containsKey(key)) {
                    Index prev = idxMap.get(key);
                    int d1 = idx1 - prev.idx1;
                    int d2 = idx2 - prev.idx2;
                    
                    int k = (totalCharsOf1 - idx1) / d1;
                    idx1 += k * d1;
                    idx2 += k * d2;
                    if (idx1 >= totalCharsOf1) {
                        break;
                    }
                } else {
                    idxMap.put(key, new Index(idx1, idx2));
                }
                
                idx2++;
            }
            
            idx1++;
        }
        
        return idx2 / len2 / n2;
    }
}

467. Unique Substrings in Wraparound String

https://leetcode.com/problems/unique-substrings-in-wraparound-string/


使用一个map,用于统计以每个字母为开头的子字符串的个数。如果给定的原字符串是连续的,那么很显然每遍历一个字符,连续字符串的size就增加1,遍历n个字符之后以起始字符开头的子字符串就是n个,第二个字符开头的子字符串就是n-1个,以此类推。

完成遍历后,对于每个字母做一下比较,比较这个字母是作为一个子字符串头,能生成的数量多,还是作为一个已有连续子字符串的中间元素,能生成的数量多。注意因为是循环数组,需要循环两次,保证map中的每个元素都能被更新。

class Solution {
    public int findSubstringInWraproundString(String p) {
        if (p.length() == 0) {
            return 0;
        }
        int[] map = new int[26];

        char[] chs = p.toCharArray();
        char head = chs[0];
        int size = 0; // current consecutive substring length
        for (int i = 0; i < chs.length; i++) {
            if (i == 0 || isAdjacent(chs[i - 1], chs[i])) {
                size++;
            } else {
                map[head - 'a'] = Math.max(map[head - 'a'], size);
                size = 1;
                head = chs[i];
            }
        }
        
        // last consecutive substring
        map[head - 'a'] = Math.max(map[head - 'a'], size);

        // repeat loop to ensure to update all elements                                                                               
        for (int i = 0; i < 26; i++) {
            map[i % 26] = Math.max(map[i % 26], map[(i + 25) % 26] - 1);
        }
            
        for (int i = 0; i < 26; i++) {
            map[i % 26] = Math.max(map[i % 26], map[(i + 25) % 26] - 1); 
        }
             
        int result = 0;
        for (int num: map) {
            result += num;
        }
        return result;
    }

    private boolean isAdjacent(char a, char b) {
        return a + 1 == b || a == 'z' && b == 'a';
    }
}

468. Validate IP Address

https://leetcode.com/problems/validate-ip-address/


使用split()函数分割输入IP,之后进行检查:

  • 对于IPv4而言,每个部分需要是整数,不能大于255,如果是0长度必须为1;总共只能有4个整数
  • 对于IPv6而言,每个部分需要是整数,不能超过65535,长度不能超过4;总共只能有8个整数

注意输入的queryIP可能在结尾包含空字符串,需要在split()中把limit参数设为-1,这样不会忽略空字符串。

class Solution {
    public String validIPAddress(String queryIP) {
        if (isIPv4(queryIP)) {
            return "IPv4";
        } else if (isIPv6(queryIP)) {
            return "IPv6";
        }
        
        return "Neither";
    }

    private boolean isIPv4(String ip) {
        // trailing empty strings will not be trimmed
        String[] arr = ip.split("\\.", -1);
        
        for (String a: arr) {
            try {
                if (Integer.parseInt(a) > 255 || (a.charAt(0) == '0' && a.length() != 1)) {
                    return false;
                }
            } catch (NumberFormatException e) {
                return false;
            }
        }
        
        return arr.length == 4;
    }

    private boolean isIPv6(String ip) {
        // trailing empty strings will not be trimmed
        String[] arr = ip.split(":", -1);
        
        for (String a: arr) {
            try {
                if (Integer.parseInt(a, 16) > 65535 || a.length() > 4) {
                    return false;
                }
            } catch (NumberFormatException e) {
                return false;
            }
        }
        
        return arr.length == 8;
    }
}

469. Convex Polygon

https://leetcode.com/problems/convex-polygon/


凸多边形任何一个内角不能超过180度,所以相邻三个点组成的两条相邻边,交点不能在多边形内部。

class Solution {
    public boolean isConvex(List<List<Integer>> points) {
        int flag = 0;
        for (int i = 0; i < points.size(); i++) {
            int angle = getAngle(points, i);
            
            // collinear
            if (angle == 0) {
                continue;
            }
            
            
            if (flag == 0) {
                flag = angle > 0 ? 1 : -1;
            } else if (flag > 0 != angle > 0) {
                // angles cannot exceed 180 degree
                return false; 
            }
        }
        
        return true;
    }

    private int getAngle(List<List<Integer>> points, int i) {
        // add modular to inspect last two elements
        List<Integer> c = points.get(i % points.size());
        List<Integer> b = points.get((i + 1) % points.size());
        List<Integer> a = points.get((i + 2) % points.size());
        
        return (a.get(1) - b.get(1)) * (b.get(0) - c.get(0)) - (b.get(1) - c.get(1)) * (a.get(0) - b.get(0));
    }
}

470. Implement Rand10() Using Rand7()

https://leetcode.com/problems/implement-rand10-using-rand7/


rand7()是1~7,所以rand7() - 1是0~6,用(rand7() - 1) * 7 + rand7() - 1凑出0~48,丢弃40~48便能得到0~39,对这个结果%10 + 1就是0~10。

一般情况下,如果要用randN()实现randM()M > N,则应该先生成一个中间的X = N^(b + 1),且randX() = N^b * (randN() - 1) + N^(b - 1) * (randN() - 1) + N^(b - 2) * (randN() - 1) + ... + N^0 * (randN() - 1)。有了randX()之后再舍弃一部分值使得randX()的范围是randM()的倍数,这样就能按比例缩小范围生成randM()

/**
 * The rand7() API is already defined in the parent class SolBase.
 * public int rand7();
 * @return a random integer in the range 1 to 7
 */
class Solution extends SolBase {
    public int rand10() {
        int rand40 = 40;
        
        while (rand40 >= 40) {
            rand40 = (rand7() - 1) * 7 + rand7() - 1;
        }

        return rand40 % 10 + 1;
    }
}

471. Encode String with Shortest Length

https://leetcode.com/problems/encode-string-with-shortest-length/


使用HashMap存储每个子字符串的encode结果,加快中间过程推导。循环遍历每个可能子字符串长度,并尝试对于每个子字符串,是子字符串本身更长,还是压缩编码后更长,不断累加起来。

class Solution {
    private Map<String, String> map = new HashMap<>();
    
    public String encode(String s) {
        if (map.containsKey(s)) {
            return map.get(s);
        }
        String result = s;

        for (int l = 1; l <= result.length(); l++) {
            int count = countRepeat(s, l);

            // minimize temp string's length
            String temp = s.substring(0, l);
            if (count > 1) {
                temp = encode(temp);
            }
            int tempLength = temp.length();

            // determine whether encoding process does not make the string shorter
            for (int r = 1; r <= count; r++) {
                StringBuilder builder = new StringBuilder();

                // digit + [ + ], or raw temp string
                if (tempLength + 3 < tempLength * r) {
                    builder.append(r).append("[").append(temp).append("]");
                } else {
                    builder.append(s, 0, r * l);
                }

                builder.append(encode(s.substring(r * l)));
                if (builder.length() < result.length()) {
                    result = builder.toString();
                }
            }
        }
        
        map.put(s, result);
        return result;
    }

    private int countRepeat(String input, int max) {
        int i = 1;
        while ((i + 1) * max <= input.length()) {
            for (int j = 0; j < max; j++) {
                if (input.charAt(j) != input.charAt(max * i + j)) {
                    return i;
                }
            }
            i++;
        }
        
        return i;
    }
}

472. Concatenated Words

https://leetcode.com/problems/concatenated-words/


使用Trie,先把所有words按长短排序,把短词优先插入Trie中。之后的长词进来可以尝试进行匹配,长词匹配完到一个短词之后,检查是否之后的部分还能匹配上其他短词,直到整个词都匹配完毕。

class Solution {
    public List<String> findAllConcatenatedWordsInADict(String[] words) {
        Arrays.sort(words, Comparator.comparingInt(String::length));
        
        Trie trie = new Trie();
        List<String> result = new ArrayList<String>();
        for (String word : words) {
            if (word.length() == 0) {
                continue;
            }
            if (trie.search(word)) {
                result.add(word);
            } else {
                trie.insert(word);
            }
        }
        
        return result;
    }
}

class Trie {
    private static class Node {        
        Node[] children;
        boolean isEnd;
        
        Node() {
            children = new Node[26];
            isEnd = false;
        }
    }
    
    private Node root = new Node();
    
    public void insert(String key) {
        Node curr = root;
        for (int i = 0; i < key.length(); i++) {
            int index = key.charAt(i) - 'a';
            if (curr.children[index] == null) {
                curr.children[index] = new Node();
            }
            curr = curr.children[index];
        }
        curr.isEnd = true;
    }
    
    public boolean search(String key) {
        return search(key, 0);
    }
    
    private boolean search(String key, int pos) {
        if (pos == key.length()) {
            return true;
        }
        
        Node curr = root;
        for (int i = pos; i < key.length(); i++) {
            int index = key.charAt(i) - 'a';
            if (curr.children[index] == null) {
                return false;
            }
            curr = curr.children[index];
            if (curr.isEnd && search(key, i + 1)) {
                return true;
            }
        }
        
        return false;
    }
}

473. Matchsticks to Square

https://leetcode.com/problems/matchsticks-to-square/


使用回溯算法,首先可以把所有火柴的长度加起来,只有总长是4的倍数才能组成正方形,并且如果能组成正方形其边长一定是总和的1/4。有了这个目标就能回溯进行尝试,把所有火柴按长度排序之后,从最长边开始尝试把四边都填满,如果已经当前边已有长度 + 当前火柴太长,或者是发现已有的两条边一样长,就跳过尝试填下一条边。

class Solution {
    public boolean makesquare(int[] nums) {
        if (nums == null || nums.length == 0) {
            return false;
        }

        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        
        // must be 4's multiples to form a square
        if (sum % 4 != 0) {
            return false;
        }

        Arrays.sort(nums);
        
        // start from the longest matchstick
        return backtrack(nums, new int[4], nums.length - 1, sum / 4);
    }

    boolean backtrack(int[] nums, int[] sums, int index, int target) {
        if (index == -1) {
            return true;
        }
        
        for (int i = 0; i < 4; i++) {
            // current sum + edge is too big, or there are already two same edges
            if ((sums[i] + nums[index] > target) || (i > 0 && sums[i] == sums[i-1])) {
                continue;
            }

            // try to fill current sums[i]
            sums[i] += nums[index];
            if (backtrack(nums, sums, index - 1, target)) {
                return true;
            }
            sums[i] -= nums[index];
        }
        
        return false;
    }
}

474. Ones and Zeroes

https://leetcode.com/problems/ones-and-zeroes/


动态规划,dp[i][j]表示能用i个0,j个1构成的最大子集大小。遍历每个字符串,每个字符串必然有zero个0和one个1。如果用了当前这个字符串,那么就剩下i - zero个0,j - one个字符串,最大子集就变成了dp[i - zero][j - one] + 1,此处的1指的是当前这个字符串本身也在最大子集内;而如果不用当前这个字符串,最大子集仍然是由dp[i][j]推出。二者的最大值就是目标解。

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        int[][] dp = new int[m + 1][n + 1];
        
        for (String str : strs) {
            int one = 0;
            int zero = 0;
            
            for (char c : str.toCharArray()) {
                if (c == '1') {
                    one++;
                } else {
                    zero++;
                }
            }
            
            for (int i = m; i >= zero; i--) {
                for (int j = n; j >= one; j--) {
                    if (zero <= i && one <= j) {
                        // dp[i][j] is the max number of str we can pick with i "0"s and j "1"s
                        dp[i][j] = Math.max(dp[i][j], dp[i - zero][j - one] + 1);
                    }
                }
            }      
        }
        
        return dp[m][n];
    }
}

475. Heaters

https://leetcode.com/problems/heaters/


先把househeater排序,之后双指针法,对于每个house找到第一个比它大的heater,有三种情况:

  • heater是全部heaters里最小的,那么这台heater必须要覆盖到当前house,这段距离要被更新到result
  • heater是全部heaters里最大的,那么这台heater也必须要覆盖到当前house,这段距离要被更新到result
  • heater位于house[i-1]house[i]之间,那么比较heater和这两个房子之间的距离,短的那段就是最小需覆盖距离
class Solution {
    public int findRadius(int[] houses, int[] heaters) {
        Arrays.sort(houses);
        Arrays.sort(heaters);

        int result = 0;
        for (int i = 0, j = 0; i < houses.length; i++) {
            while (j < heaters.length && heaters[j] <= houses[i]) {
                j++;
            }

            if (j == 0) {
                // the leftmost house must be covered by the first heaters
                result = Math.max(result, heaters[0] - houses[i]);
            } else if (j == heaters.length) {
                // the rightmost house must be covered by the last heaters
                result = Math.max(result, houses[i] - heaters[j-1]);
            } else {
                // heaters[j-1] < houses[i] < heaters[j], choose the min distance between them
                result = Math.max(result, Math.min(houses[i] - heaters[j-1], heaters[j] - houses[i]));
            }    
        }
        
        return result;
    }
}

476. Number Complement

https://leetcode.com/problems/number-complement/


按照补码公式推导。

class Solution {
    public int findComplement(int num) {
        int n = 0;
        while (n < num) {
            n = 2*n + 1;
        }
        return n - num;
    }
}

477. Total Hamming Distance

https://leetcode.com/problems/total-hamming-distance/


对于n个数,因为每个数都是32位,遍历每一位,如果n个数中有k个数这一位是1,n-k个数这一位是0,那么这一位就会让总的hamming distance增加k * (n - k)

class Solution {
    public int totalHammingDistance(int[] nums) {
        int result = 0, n = nums.length;

        for (int j = 0; j < 32; j++) {
            int bitCount = 0;

            // if there are n integers, k of them have a particular bit set and (n-k) do not, 
            // then that bit contributes k*(n-k) hamming distance to the total
            for (int i = 0; i < n; i++) {
                bitCount += (nums[i] >> j) & 1;
            }
            result += bitCount*(n - bitCount);
        }

        return result;
    }
}

478. Generate Random Point in a Circle

https://leetcode.com/problems/generate-random-point-in-a-circle/


用极坐标形式去理解,因为圆的半径是radius,与x轴形成夹角最大是2 * pi,所以这二者分别乘以一个[0, 1]之间的随机数,就能得到一个圆内的点。

class Solution {
    double radius, x_center, y_center;
    
    public Solution(double radius, double x_center, double y_center) {
        this.radius = radius;
        this.x_center = x_center;
        this.y_center = y_center;
    }

    public double[] randPoint() {
        double len = Math.sqrt(Math.random()) * radius;
        double deg = Math.random() * 2*Math.PI;
        double x = x_center + len * Math.cos(deg);
        double y = y_center + len * Math.sin(deg);
        return new double[]{x, y};
    }
}

/**
 * Your Solution object will be instantiated and called as such:
 * Solution obj = new Solution(radius, x_center, y_center);
 * double[] param_1 = obj.randPoint();
 */

479. Largest Palindrome Product

https://leetcode.com/problems/largest-palindrome-product/


对于每个n,确定上下界,从大到小穷举回文数pal,再检查sqrt(pal)与上界之间的数是否存在可以被pal整除的i

class Solution {
    public int largestPalindrome(int n) {
        if (n == 1) {
            return 9;
        }
        long upper = (long)(Math.pow(10, n) - 1);
        long lower = upper/10 + 1;
        long half = upper * upper / (long)Math.pow(10, n);

        while (half > 0) {
            // For each palindrome number pal, it checks if the number can be divided by a number i with n digits
            long pal = Long.parseLong(half + new StringBuilder().append(half--).reverse().toString());

            // As it's in descending order, the first number that meets this check is the largest palindrome number we want
            for (long i = upper; i * i >= pal && i > lower; i--) {
                if (pal % i == 0)  {
                    return (int) (pal % 1337);
                }
            }
            half--;
        }
        
        return 0;
    }
}

480. Sliding Window Median

https://leetcode.com/problems/sliding-window-median/


使用两个优先队列,一个small用于降序存窗口中较小一半的数,一个large用于升序存窗口中较大一半的数,这样可以保证无论窗口长度是奇数还是偶数,中位数都可以通过优先队列的头元素快速求出。之后向前移动窗口时,需要把即将进入窗口的数和当前small中的最大值进行比较,进而判断这个数应该加到small还是large中。因为新加入的数可能会导致两个优先队列长度失衡,因此维护一个balance变量用于动态平衡两个队列的长度。

class Solution {
    public double[] medianSlidingWindow(int[] nums, int k) {
        if (k > nums.length || k == 0) {
            return new double[0];
        }

        // priority queue will store [array index, array element]
        Queue<int[]> small = new PriorityQueue<>((n1, n2) -> n1[1] == n2[1] ? 0 : n2[1] > n1[1] ? 1 : -1); // descending
        Queue<int[]> large = new PriorityQueue<>((n1, n2) -> n1[1] == n2[1] ? 0 : n1[1] > n2[1] ? 1 : -1); // ascending

        double[] result = new double[nums.length - k + 1];
        for (int i = 0; i < k; i++) {
            large.offer(new int[]{i, nums[i]});
        }
        for (int i = 0; i < (k + 1) / 2; i++) {
            small.offer(large.poll());
        }

        int balance = 0;
        for (int i = 0; i < result.length; i++) {
            // get current median
            result[i] = small.peek()[1];
            if (k % 2 == 0) {
                result[i] = (result[i] + large.peek()[1]) / 2;
            }

            if (i == result.length - 1) {
                break;
            }

            // decide the incoming number should go to small or large
            balance += nums[i] <= small.peek()[1] ? -1 : 1;
            if (!small.isEmpty() && nums[i + k] < small.peek()[1]) {
                small.add(new int[]{i + k, nums[i + k]});
                balance++;
            } else {
                large.add(new int[]{i + k, nums[i + k]});
                balance--;
            }

            // how many numbers should one queue offers to another queue
            if (balance > 0) {
                large.offer(small.poll());
                balance -= 2;
            }
            if (balance < 0) {
                small.offer(large.poll());
                balance += 2;
            }

            // move forward the sliding window
            while (!small.isEmpty() && small.peek()[0] <= i) {
                small.poll();
            }
            while (!large.isEmpty() && large.peek()[0] <= i) {
                large.poll();
            }
        }
        
        return result;
    }
}

481. Magical String

https://leetcode.com/problems/magical-string/


双指针 + 数组,具体使用参考注释。

class Solution {
    public int magicalString(int n) {
        if (n <= 0) {
            return 0;
        }
        if (n <= 3) {
            return 1;
        }

        // +1 to avoid overflow because the last round head might points to a number 2
        int[] a = new int[n + 1];
        a[0] = 1;
        a[1] = 2;
        a[2] = 2;

        // head points to the number which will be used to generate new numbers
        // tail points to the next empty position to put the new number
        int head = 2, tail = 3, num = 1, result = 1;

        while (tail < n) {
            for (int i = 0; i < a[head]; i++) {
                a[tail] = num;
                if (num == 1 && tail < n) {
                    result++;
                }
                tail++;
            }
            
            // flip number back and forth between 1 and 2
            num = num ^ 3;
            head++;
        }

        return result;
    }
}

482. License Key Formatting

https://leetcode.com/problems/license-key-formatting/


把原始字符串中的'-'去掉,之后计算长度,把mod K的余数部分放在第一部分,其他每个部分都是K的长度,中间用'-'间隔。

class Solution {
    public String licenseKeyFormatting(String S, int K) {
        String s = S.replace("-", "");
        int len = s.length();
        if (S.length() == 0 || len == 0) {
            return "";
        }

        StringBuilder sb = new StringBuilder();

        // the first group could be shorter than K
        int firstLen = len % K;
        if (firstLen >= 1) {
            for (int i = 0; i < firstLen; i++) {
                char ch = s.charAt(i);
                sb.append(Character.toUpperCase(ch));
            }
            sb.append("-");
        }

        for (int i = firstLen; i < len; i = i + K) {
            // after K characters, add a hyphen
            for (int j = 0; j < K; j++) {
                char ch = s.charAt(i + j);
                sb.append(Character.toUpperCase(ch));
            }
            sb.append("-");
        }

        // remove last hyphen
        sb.setLength(sb.length() - 1);
        return sb.toString();
    }
}

483. Smallest Good Base

https://leetcode.com/problems/smallest-good-base/


算出最大的可能次幂,然后向下循环缩小范围。

class Solution {
    public String smallestGoodBase(String n) {
        long num = Long.parseLong(n);
        
        // smallest base is 2 so our m must be between 2 and log2n
        int maxPower = (int)(Math.log((double)num) / Math.log(2));

        for (int i = maxPower; i >= 2; i--) {
            // ⌊i-th root of num⌋ is the only candidate that needs to be tested
            int root = (int)Math.pow(num, 1.0 / i);
            long sum = 1;
            long term = 1;
            for (int j = 1; j <= i; j++) {
                term *= root;
                sum += term;
            }

            // (i^(j+1) - 1) / (i - 1) = num
            if (sum == num) {
                return Integer.toString(root);
            }
        }

        // else result is num - 1
        return Long.toString(num - 1);
    }
}

484. Find Permutation

https://leetcode.com/problems/find-permutation/


先初始化一个有序的序列,之后把所有含'D'的序列区间进行颠倒,颠倒时注意是头尾双指针两两配对交换,交换完后移动指针。

class Solution {
    public int[] findPermutation(String s) {
        if (s == null || s.isEmpty()) {
            return new int[0];
        }

        int n = s.length();
        int[] sorted = new int[n + 1];
        for (int i = 0; i <= n; i++) {
            sorted[i] = i + 1;
        }

        int right = 0;
        while (right < n) {
            if (s.charAt(right) == 'I') {
                right++;
                continue;
            }

            // find first 'D' and set as left
            int left = right;
            while (right < n && s.charAt(right) == 'D') {
                right++;
            }

            // swap intervals with all 'D's
            reverse(sorted, left, right);
        }

        return sorted;
    }

    // swap all pairs of number in [left, right]
    private void reverse(int[] arr, int left, int right) {
        while (left < right) {
            int tmp = arr[left];
            arr[left] = arr[right];
            arr[right] = tmp;
            
            left++;
            right--;
        }
    }
}

485. Max Consecutive Ones

https://leetcode.com/problems/max-consecutive-ones/


遇到1就计数增加,反之检查是否需要更新最大值。注意循环结束后仍然要检查一次。

class Solution {
    public int findMaxConsecutiveOnes(int[] nums) {
        int max = 0, count = 0;

        for (int i: nums) {
            if (i == 1) {
                count++;
            } else {          
                max = Math.max(max, count);
                count = 0;
            }
        }

        return Math.max(max, count);
    }
}

486. Predict the Winner

https://leetcode.com/problems/predict-the-winner/


使用动态规划,dp[i][j]表示如果第一个玩家先手,能够从nums[i, j]之间比第二个玩家多拿多少数字。如果先手玩家拿了nums[i],剩下的nums[i+1, j]里面就会生成dp[i+1][j],则第一个玩家比第二个玩家多的部分就应该是nums[i] + dp[i+1][j]

然而,注意到一旦第一个玩家拿了nums[i]nums[i+1, j]中就变成了第二个玩家先手,因此此时我们看到的dp[i+1][j]实际上是第二个玩家在nums[i+1, j]中比第一个玩家多拿了多少数字(因为两个人都是用最优策略在玩游戏,因此如果把两个人视为一个整体,取数字的顺序一定是固定的,只是谁先手的差异)。

因此,如果先手玩家拿nums[i]dp[i][j]nums[i] - dp[i+1][j],即抵消了子区间内对方多拿的数字。同理,如果先手玩家拿nums[j]dp[i][j]nums[j] - dp[i][j-1],这两种取法选择较大的一种作为最终策略。

class Solution {
    public boolean PredictTheWinner(int[] nums) {
        int n = nums.length;

        // dp[i][j]: how much more scores that the first player will get from i to j than the second player
        int[][] dp = new int[n][n];
        for (int i = 0; i < n; i++) {
            dp[i][i] = nums[i];
        }

        for (int len = 1; len < n; len++) {
            for (int i = 0; i < n - len; i++) {
                int j = i + len;

                // first player takes num[i], and second player starts first to result dp[i + 1, j]
                // or similarily, first player takes num[j], and second player starts first to result dp[i, j - 1]
                dp[i][j] = Math.max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1]);
            }
        }
        
        return dp[0][n - 1] >= 0;
    }
}

487. Max Consecutive Ones II

https://leetcode.com/problems/max-consecutive-ones-ii/


使用两个变量zeroLeftzeroRightzeroRight表示当前遍历时连续遇到的1的个数,遇到0就清零;而zeroLeft则在遇到0时才更新,把此前zeroRight赋值给zeroLeft,这个值包含了此前连续的1的个数 + 1(当前的0可以翻转成1)。最大值由zeroLeft + zeroRight得到。

class Solution {
    public int findMaxConsecutiveOnes(int[] nums) {
        int result = 0, zeroLeft = 0, zeroRight = 0;

        for (int i = 0; i < nums.length; i++) {
            zeroRight++;
            if (nums[i] == 0) {
                zeroLeft = zeroRight;
                zeroRight = 0;
            }
            result = Math.max(result, zeroLeft + zeroRight); 
        }
        
        return result;
    }
}

对于follow-up,需要使用一个queue存储所有0的下标,然后用双指针lowhigh计算最大的连续1区间长度。只要还在flip的上限范围内,就只需要更新high,不需要更新low;而一旦超出上限,就必须把最早的0的下标弹出,更新low

public int findMaxConsecutiveOnes(int[] nums) {     
    // flip at most k zero            
    int result = 0, k = 1;

    Queue<Integer> zeroIndex = new LinkedList<>(); 
    for (int low = 0, high = 0; high < nums.length; high++) {
        if (nums[high] == 0) {
            zeroIndex.offer(high);
        }

        // eject the earliest 0's index
        if (zeroIndex.size() > k) {
            low = zeroIndex.poll() + 1;
        }
        
        result = Math.max(result, high - low + 1);
    }
    
    return result;                     
}

488. Zuma Game

https://leetcode.com/problems/zuma-game/


分类讨论,单球+2/双球+1/双球之间插1异色球的情况。

class Solution {
    private int result = Integer.MAX_VALUE;
    private int[] map = new int[26];
    private char[] colors = {'R', 'Y', 'B', 'G', 'W'};

    public int findMinStep(String board, String hand) {
        for (int i = 0; i < hand.length(); i++) {
            map[hand.charAt(i) - 'A']++;
        }
        dfs(new StringBuilder(board), 0);
        return result == Integer.MAX_VALUE ? -1 : result;
    }

    private void dfs(StringBuilder board, int step) {
        // skip rest of the dfs if step is already too big, or update the min step
        if (step >= result) {
            return;
        }
        
        if (board.length() == 0) {
            result = Math.min(step, result);
            return;
        }

        for (int i = 0; i < board.length(); i++) {
            char c = board.charAt(i);
            int j = i;
            while (j + 1 < board.length() && board.charAt(j + 1) == c) {
                j++;
            }

            if (j == i && map[c - 'A'] >= 2) {
                // single ball, insert two same color balls
                StringBuilder tmp = new StringBuilder(board);
                tmp.insert(i, c + "" + c);
                map[c - 'A'] -= 2;
                dfs(eliminate(tmp), step + 2);
                map[c - 'A'] += 2;
            } else if (j == i + 1) {
                // two adjacent balls with same color
                // insert a same color ball
                if (map[c - 'A'] >= 1) {
                    StringBuilder tmp = new StringBuilder(board);
                    tmp.insert(i, c);
                    map[c - 'A']--;
                    dfs(eliminate(tmp), step + 1);
                    map[c - 'A']++;
                }

                // insert a different color ball between these two adjacent balls
                for (char color : colors) {
                    if (color == c) {
                        continue;
                    }
                    if (map[color - 'A'] >= 1) {
                        StringBuilder tmp = new StringBuilder(board);
                        tmp.insert(i + 1, color);
                        map[color - 'A']--;
                        dfs(eliminate(tmp), step + 1);
                        map[color - 'A']++;
                    }
                }
            }
        }
    }

    private StringBuilder eliminate(StringBuilder sb) {
        boolean flag = true;
        while (flag) {
            flag = false;
            for (int i = 0; i < sb.length(); i++) {
                int j = i + 1;
                while (j < sb.length() && sb.charAt(j) == sb.charAt(i)) {
                    j++;
                }

                // remove consecutive balls
                if (j - i >= 3) {
                    sb.delete(i, j);
                    flag = true;
                }
            }
        }
        
        return sb;
    }
}

489. Robot Room Cleaner

https://leetcode.com/problems/robot-room-cleaner/


回溯算法 + BFS,然后使用一个HashSet存储所有已经走过的方格。

/**
 * // This is the robot's control interface.
 * // You should not implement it, or speculate about its implementation
 * interface Robot {
 *     // Returns true if the cell in front is open and robot moves into the cell.
 *     // Returns false if the cell in front is blocked and robot stays in the current cell.
 *     public boolean move();
 *
 *     // Robot will stay in the same cell after calling turnLeft/turnRight.
 *     // Each turn will be 90 degrees.
 *     public void turnLeft();
 *     public void turnRight();
 *
 *     // Clean the current cell.
 *     public void clean();
 * }
 */

class Solution {
    private static int[][] DIR = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} }; // UP, RIGHT, DOWN, LEFT
    Set<Pair<Integer, Integer>> visited = new HashSet();
    Robot robot;

    public void cleanRoom(Robot robot) {
        this.robot = robot;
        backtrack(0, 0, 0);   
    }

    // dir: 0: up, 1: right, 2: down, 3: left
    public void backtrack(int x, int y, int dir) { 
        visited.add(new Pair(x, y));
        robot.clean();

        for (int i = 0; i < 4; i++) {
            int newDir = (dir + i) % 4;
            int[] move = DIR[newDir];

            int newX = x + move[0];
            int newY = y + move[1];

            if (!visited.contains(new Pair(newX, newY)) && robot.move()) {
                backtrack(newX, newY, newDir);

                // go back
                robot.turnRight();
                robot.turnRight();
                robot.move();
                robot.turnRight();
                robot.turnRight();
            }

            // turn the robot following chosen direction : clockwise
            robot.turnRight();
        }
    }
}

490. The Maze

https://leetcode.com/problems/the-maze/


使用DFS + BFS,先用BFS尝试一个方向,然后在这个方向上一条路走到黑直到撞墙(即判断是否还在迷宫范围内),之后再使用DFS从新的位置开始下一次尝试,直到小球抵达终点。

class Solution {
    private static final int[][] DIRS = { {1,0},{-1,0},{0,1},{0,-1} };
    
    public boolean hasPath(int[][] maze, int[] start, int[] destination) {
        boolean[][] visited = new boolean[maze.length][maze[0].length];
        return dfs(maze, start, destination, visited);
    }

    private boolean dfs(int[][] maze, int[] start, int[] end, boolean[][] visited) {
        // start and end points are the same
        if (start[0] == end[0] && start[1] == end[1]) {
            return true;
        }
        
        // already visited grid
        if (visited[start[0]][start[1]]) {
            return false; 
        }

        visited[start[0]][start[1]] = true;
        for (int[] dir : DIRS) {
            int x = dir[0] + start[0];
            int y = dir[1] + start[1];

            // ball always runs until it hit a wall
            while (isValid(maze, x, y) && maze[x][y] == 0) {
                x += dir[0];
                y += dir[1];
            }
            x -= dir[0];
            y -= dir[1];

            // current grid is unvisited, try subsequent dfs
            if (!visited[x][y]) {
                if (dfs(maze, new int[]{x, y}, end, visited)) {
                    return true;
                }
            }
        }
        
        return false;
    }

    // still in maze's range
    private boolean isValid(int[][] maze, int x, int y) {
        return x >= 0 && y >= 0 && x < maze.length && y < maze[0].length;
    }
}

491. Increasing Subsequences

https://leetcode.com/problems/increasing-subsequences/


回溯算法,分类讨论。如果当前数字比此前的数字更大,可以把它添加到当前list中,然后dfs尝试之后的数组区间,直到访问到数组尾。而另一种情况下,即便当前数字比此前数字更大,我们也不一定用这个数字,而是向后继续搜索找到另一个递增数字。所以综合起来,共有2种触发dfs的方式。

class Solution {
    public List<List<Integer>> findSubsequences(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        dfs(result, nums, 0, Integer.MIN_VALUE, new ArrayList<>());
        return result;
    }

    private void dfs(List<List<Integer>> result, int[] nums, int index, int pre, List<Integer> cur) {
        // reach the end of nums, add current list to result
        if (index == nums.length) {
            if (cur.size() > 1) {
                result.add(new ArrayList(cur));
            }
            return;
        }

        // current number is larger than previous number, add to current list
        if (nums[index] >= pre) {
            cur.add(nums[index]);
            dfs(result, nums, index + 1, nums[index], cur);
            cur.remove(cur.size() - 1);
        }

        // may or may not use current number, so try next number
        if (nums[index] != pre) {
            dfs(result, nums, index + 1, pre, cur);
        }
    }
}

492. Construct the Rectangle

https://leetcode.com/problems/construct-the-rectangle/


通过开方可以快速定位到离sqrt(area)最近的质因数。

class Solution {
    public int[] constructRectangle(int area) {
        int width = (int)Math.sqrt(area);
        while (area % width != 0) {
            width--;
        }
        return new int[]{area / width, width};
    }
}

493. Reverse Pairs

https://leetcode.com/problems/reverse-pairs/


归并排序,在归并的同时搜索reverse pairs。

class Solution {
    public int reversePairs(int[] nums) {        
        return nums == null ? 0 : mergeSort(nums, 0, nums.length - 1);        
    }

    public int mergeSort(int[] nums, int start, int end) {
        if (start >= end) {
            return 0;
        }

        int mid = start + ((end - start) >> 1);
        int result = mergeSort(nums, start, mid) + mergeSort(nums, mid + 1, end);
        int[] cache = new int[end - start + 1];

        int k = mid + 1, c = 0;
        for (int i = start, j = mid + 1; i <= mid; i++, c++) {
            // find pairs
            while (j <= end && nums[i] > 2 * (long)nums[j]) {
                j++;
            }

            // find numbers smaller than nums[i] and copy to cache
            while (k <= end && nums[k] < nums[i]) {
                cache[c] = nums[k];
                c++;
                k++;
            }
            cache[c] = nums[i];

            // update result
            result += j - (mid + 1);
        }

        while (k <= end) {
            cache[c] = nums[k];
            c++;
            k++;
        }

        System.arraycopy(cache, 0, nums, start, end - start + 1);
        return result;
    }
}

494. Target Sum

https://leetcode.com/problems/target-sum/


nums可以拆成两部分来看,一部分用于添加加号,另一部分用于添加负号。如果把这两部分分别用posnegative两个集合表示,很显然有sum(pos) - sum(negative) = s,化简得到2*sum(pos) = s + sum。根据这个推论,我们可以把问题转化为典型01背包问题,即从nums中找出一个子集(组成pos),使得子集里所有元素的和为(s + sum) / 2

寻找子集可以使用动态规划,其中dp[i][j]表示使用前i个数得到目标数j共有几种方式。我们遍历nums中的每个数i,对0到target之间的状态j进行穷举,并分类讨论:

  • 当前的目标j太小,第i个数太大,肯定没有办法选择它,因此dp[i][j]的值仍然与dp[i-1][j]相同
  • 否则,第i个数可选或者可不选。如果不选,与此前的情况一样;如果选了,就相当于之前的i-1个数字总和必须为j - 第i个数的值

最后,二维dp可以进一步优化成一维dp,详见注释。

class Solution {
    public int findTargetSumWays(int[] nums, int s) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }

        // too big, too small, or mismatched parity
        if (Math.abs(s) > sum || (s+sum) % 2 != 0) {
            return 0;
        }

        // sum(pos) - sum(negative) = s
        // 2 * sum(pos) = s + sum(negative) + sum(pos) = s + sum
        return subsetSum(nums, (s+sum) / 2);
    }

    public int subsetSum(int[] nums, int target) {
//         int[] dp = new int[target + 1];
//         dp[0] = 1;

//         for (int num : nums) {
//             for (int i = target; i >= num; i--) {
//                 dp[i] += dp[i - num];
//             }
//         }

//         return dp[target];

        int[][] dp = new int[nums.length + 1][target + 1];      
        dp[0][0] = 1;

        for (int i = 1; i <= nums.length; i++) {
            for (int j = 0; j <= target; j++) {
                if (j < nums[i - 1]) {
                    // current j is too small, so only possible to repeating all the ways to get sum j by using first i-1 elements
                    dp[i][j] = dp[i - 1][j];
                } else {
                    // otherwise, could also add nums[i] on top of each way to get sum j-nums[i-1] using first i elements
                    dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i - 1]]; 
                }
            }          
        }          
        
        return dp[nums.length][target];
    }
}

495. Teemo Attacking

https://leetcode.com/problems/teemo-attacking/


除了最后一个数,前面所有数两两之间的间隔都要和duration进行比较,取较小值累加;最后一个数直接累加duration即可。

class Solution {
    public int findPoisonedDuration(int[] timeSeries, int duration) {
        if (timeSeries == null || timeSeries.length == 0) {
            return 0;
        }

        int result = 0;
        for (int i = 0; i < timeSeries.length - 1; i++) {
            if (timeSeries[i + 1] - timeSeries[i] > duration) {
                result += duration;
            } else {
                result += timeSeries[i + 1] - timeSeries[i];
            }
        }
        result += duration;
        return result;
    }
}

496. Next Greater Element I

https://leetcode.com/problems/next-greater-element-i/


使用一个HashMap,先储存nums2中所有数字的下标,之后遍历nums1,获取到每个数在nums2中的下标,然后向后搜索看能不能找到比他大的数。

class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        // <number, number index>
        Map<Integer, Integer> indexMap = new HashMap<>();
        for (int i = 0; i < nums2.length; i++) {
            indexMap.put(nums2[i], i);
        }

        int[] result = new int[nums1.length];
        for (int i = 0; i < result.length; i++) {
            result[i] = getNext(nums2, indexMap.get(nums1[i]));
        }

        return result;
    }

    // start from current number's index in nums2, and search afterwards
    private int getNext(int[] nums, int index) {
        int curr = nums[index];

        for (int i = index + 1; i < nums.length; i++) {
            if (nums[i] > curr) {
                return nums[i];
            }
        }

        return -1;
    }
}

497. Random Point in Non-overlapping Rectangles

https://leetcode.com/problems/random-point-in-non-overlapping-rectangles/


计算每个矩形的面积,注意因为边界上的点也需要用到,因此边长需要+1。得到每个矩形的面积后用数组存储,然后以总的面积和为上限,得到一个范围,从这个范围内生成一个随机数,利用二分查找找到这个随机数对应的是哪个矩形(即,找到数组内的最小下标left,使得这个随机数落在arr[left - 1]arr[left]的区间之内)。找到矩形后,再提取这个矩形的坐标,生成范围内的随机坐标点。

class Solution {
    private Random rand;
    private int[][] re;
    private int N;
    private int sum;
    private int[] arr;

    public Solution(int[][] rects) {
        rand = new Random();
        re = rects;
        N = rects.length;
        arr = new int[N];
        for (int i = 0; i < N; i++) {
            arr[i] = area(rects[i]);
        }

        // each i in the arr represents a range of (arr[i-1], arr[i]]
        sum = arr[0];
        for (int i = 1; i < N; i++) {
            sum += arr[i];
            arr[i] = arr[i-1] + arr[i];
        }
    }

    // boundaries are used, so + 1
    public int area(int[] rect) {
        return (rect[2] - rect[0] + 1) * (rect[3] - rect[1] + 1);
    }

    public int[] pick() {
        int num = rand.nextInt(sum) + 1;

        // find the index l, which is at the left most idx that makes arr[l] >= num (arr[l-1] < num <= arr[l])
        int left = 0;
        int right = arr.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;

            if (arr[mid] >= num) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }

        int x1 = re[left][0];
        int y1 = re[left][1];
        int x2 = re[left][2];
        int y2 = re[left][3];

        int x = rand.nextInt(x2 - x1 + 1) + x1;
        int y = rand.nextInt(y2 - y1 + 1) + y1;
        return new int[]{x, y};
    }
}

/**
 * Your Solution object will be instantiated and called as such:
 * Solution obj = new Solution(rects);
 * int[] param_1 = obj.pick();
 */

498. Diagonal Traverse

https://leetcode.com/problems/diagonal-traverse/


观察奇偶性,发现向上移动时,行与列下标相加总是偶数;向下移动时,总是奇数。运用此规律分类讨论,并注意单独分析边界掉头转向的情况。

class Solution {
    public int[] findDiagonalOrder(int[][] matrix) {
        if (matrix.length == 0) {
            return new int[0];
        }
        int r = 0, c = 0, m = matrix.length, n = matrix[0].length, result[] = new int[m * n];

        for (int i = 0; i < result.length; i++) {
            result[i] = matrix[r][c];

            if ((r + c) % 2 == 0) {
                if (c == n - 1) {
                    // move to next row, go downward
                    r++;
                } else if (r == 0) {
                    // move to next column, go downward
                    c++;
                } else {
                    r--;
                    c++;
                }
            } else {
                if (r == m - 1) {
                    // move to next column, go upward
                    c++;
                } else if (c == 0) {
                    // move to next row, go upward
                    r++;
                } else {
                    r++;
                    c--;
                }
            }   
        }
        
        return result;
    }
}

499. The Maze III

https://leetcode.com/problems/the-maze-iii/


从起始点开始,遍历4个可能的方向,对任何一个方向,尝试在这个方向上一条路走到黑,如果撞墙了就尝试左右转向。不断递归尝试,看最后是否有情况下能到达hole。维护两个变量minSmin,分别表示已走过的最短成功路径字符串和最短成功路径的长度,再对应维护两个变量表示当前路径的详情和长度,每走一步就更新当前变量,如果到达hole看是否能更新最小值。

class Solution {
    private int min; // min distance to hole
    private String minS; // min distance's path string
    private int[] hole;
    private int[][] maze; 
    private int[][] map; // shortest distant traveling from ball to this point
    private int[][] dirs = { {0, 1}, {-1, 0}, {1, 0}, {0, -1} }; // right, up, down, left

    public String findShortestWay(int[][] maze, int[] ball, int[] hole) {
        this.min = Integer.MAX_VALUE;
        this.minS = null;
        this.hole = hole;
        this.maze = maze;
        this.map = new int[maze.length][maze[0].length];
        for (int i = 0; i < map.length; i++) {
            Arrays.fill(map[i], Integer.MAX_VALUE);
        }

        move(ball[0], ball[1], 0, "", -1);
        return minS == null ? "impossible" : minS;
    }

    private void move(int r, int c, int cnt, String path, int dir) {
        // current path's length is not shortest
        if (cnt > min || cnt > map[r][c]) {
            return;
        }

        // exclude start point
        if (dir != -1) {
            // add current diretions to path
            if (dir == 0) {
                path += 'r';
            } else if (dir == 1) {
                path += 'u';
            } else if (dir == 2) {
                path += 'd';
            } else {
                path += 'l';
            }

            while (r >= 0 && r < maze.length && c >= 0 && c < maze[0].length && maze[r][c] == 0) {
                map[r][c] = Math.min(map[r][c], cnt);

                // reach the hole
                if (r == hole[0] && c == hole[1]) {
                    // lexicographically smallest way
                    if (cnt == min && path.compareTo(minS) < 0) {
                        minS = path;
                    } else if (cnt < min) {
                        min = cnt; 
                        minS = path; 
                    }
                    return; 
                }

                // roll along direction
                r += dirs[dir][0];
                c += dirs[dir][1];
                cnt++;
            }

            // [r, c] is wall, need to walk back 1 step
            r -= dirs[dir][0];
            c -= dirs[dir][1];
            cnt--;
        }

        // hit wall (or start) -> try to turn
        for (int i = 0; i < dirs.length; i++) {
            // don't keep going or go back
            if (dir == i || dir == 3 - i) {
                continue;
            }
            
            int newR = r + dirs[i][0];
            int newC = c + dirs[i][1];

            // new space is still valid
            if (newR >= 0 && newR < maze.length && newC >= 0 && newC < maze[0].length && maze[newR][newC] == 0) {
                move(r, c, cnt, path, i);
            }  
        }
    }
}

500. Keyboard Row

https://leetcode.com/problems/keyboard-row/


统计每个单词中的字母分别在每行出现了几次,如果只在一行出现,就把它添加到结果中。

class Solution {
    public String[] findWords(String[] words) {
        String[] strs = {"qwertyuiop","asdfghjkl","zxcvbnm"};
        List<String> results = new ArrayList();

        for (String word : words) {
            int count1 = 0, count2 = 0, count3 = 0;

            for (char c : word.toCharArray()) {
                if (strs[0].indexOf(c) != -1) {
                    count1++;
                }
                if (strs[1].indexOf(c) != -1) {
                    count2++;
                }
                if (strs[2].indexOf(c) != -1) {
                    count3++;
                }             
            }

            // only comes from one row
            if ((count1 == 0 && count2 == 0) || (count1 == 0 && count3 == 0) || (count2 == 0 && count3 == 0)) {
                results.add(word);  
            }                
        }

        return results.toArray(new String[results.size()]);
    }
}