LeetCode 1-100

LeetCode Problems 1-100.

1. Two Sum

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


我们需要保证nums[i]target - nums[i]都在数组中,因此使用哈希表存储循环遍历时每个数nums[i]以及下标i的值,并检查target - nums[i]是否也在map中,如果存在,就可以直接提取两个数的位置得到结果。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return new int[] {};
        }
        
        int[] result = new int[2];

        // <Element, Index>
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            if (map.containsKey(target - nums[i])) {
                result[0] = i;
                result[1] = map.get(target - nums[i]);
                return result;
            }
            map.put(nums[i], i);
        }
        
        return result;
    }
}

2. Add Two Numbers

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


加法运算是一个从低位到高位的循环过程,只要下列3个条件之一满足,就会一直持续下去:

  • 第一个数某位不为空

  • 第二个数某位不为空

  • 进位不为0

因此我们创建一个新链表,按照上述条件,对l1l2逐位计算相加,得到结果后创建一个新节点插入到链表中。

/**
 * 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) {
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }

        ListNode dummyHead = new ListNode(0); // dummy head of new list
        ListNode cur = dummyHead; // traverse new list

        int carry = 0;

        // still have digits
        while (l1 != null || l2 != null || carry != 0) {
            int val = 0;

            int val1 = l1 != null ? l1.val : 0;
            int val2 = l2 != null ? l2.val : 0;

            val = (val1 + val2 + carry) % 10;
            carry = (val1 + val2 + carry) / 10;
            
            // insert a new node
            cur.next = new ListNode(val); 
            cur = cur.next;

            // move pointers forward
            if (l1 != null) {
                l1 = l1.next;
            }
            if (l2 != null) {
                l2 = l2.next;
            }
        }
        
        return dummyHead.next;
    }
}

3. Longest Substring Without Repeating Characters

https://leetcode.com/problems/longest-substring-without-repeating-characters/


我们需要一个map记录每个字符最后一次出现的位置。由于s中的字符已经确定是英文字母或者符号,因此可以使用定长数组模拟map,提高运行效率。

定义两个指针beginendbegin指向最长不重复子串起点,尾指针用于遍历s,判断当前字符是否于map中。我们的目标就是尾指针完成遍历后,通过两个指针的最大距离差值,得到最长的不重复子串长度。

遍历时需要注意,在遇到重复字符时,begin有两种更新方式:

  • 这个字符上一次出现位置的后面(+1),这是满足不重复的前提下的最小取值

  • 保持不变,因为有可能这个重复字符上一次出现的位置相当靠前,在[map[currentChar] + 1, begin]中间出现了其他的重复字符

具体选择哪种方式更新,只需比较当前begin和上一次出现位置+1的大小。

class Solution {
    public int lengthOfLongestSubstring(String s) {
        if (s.length() == 0) {
            return 0;
        }
        
        char[] array = s.toCharArray();

        int begin, end = 0; // two pointers to mark the longest substring
        int result = 0; // longest substring's length
        
        int[] map = new int[128]; // <Character, Index>
        Arrays.fill(map, -1);

        for (begin = 0, end = 0; end < s.length(); end++) {
            char currentChar = array[end];
            
            // if this is a repeating character, we either reset begin index to last appearance index + 1 to avoid duplicate
            // or remains unchanged. It is possible that [map[currentChar] + 1, begin] contains duplicate characters
            if (map[currentChar] >= 0) {
                begin = Math.max(begin, map[currentChar] + 1);
            }
            
            // update current character's last position index
            map[currentChar] = end;
            
            // update result
            result = Math.max(result, end - begin + 1);
        }
        return result;
    }
}

4. Median of Two Sorted Arrays

https://leetcode.com/problems/median-of-two-sorted-arrays/


对于一个有序数组,我们做一次分割,将数组分割为两半部分,则数组中位数为下半部分最大值与上半部分最小值的平均值。这种取法的前提是两半部分具有相同的元素值。当数组中有偶数个元素时,分割很容易;而当有奇数个元素时,我们尝试将实际的中位数mid一分为二,这样两部分都有了这个mid,数组的中位数仍然可以用(mid + mid) / 2 = mid得到。

对于一个有N个元素的数组,如果定义分割左侧的数为L,右侧的数为R,则它们可以表示为:

L = nums[(N - 1) / 2]
R = nums[N/2]
mid = (L + R) / 2

现在,假设为元素之间的间隔也增加下标。例如:

[6 9 13 18]  ->   [# 6 # 9 # 13 # 18 #]    (N = 4)
position index     0 1 2 3 4 5  6 7  8     (N_Position = 9)
          
[6 9 11 13 18]->   [# 6 # 9 # 11 # 13 # 18 #]   (N = 5)
position index      0 1 2 3 4 5  6 7  8 9 10    (N_Position = 11)

易得知,总共有2N + 1个下标位,并且分割的位置一定在下标为N处。因此仍然有

index(L) = (N - 1) / 2
index(R) = N / 2

至此,我们完成了单个数组的推导。扩展到两个有序数组时,我们可以总结出:

  • 一共有2M + 2N + 2个下标位
  • 需要2个切割点,下标设定为C1C2
  • 切割的左侧和右侧总共都有M + N个元素

假设nums2是长度较短的数组,并且切割点的下标为C2 = K,则nums1的切割点下标为C1 = M + N - K。完成这两次切割后,我们可以得到切割点附近的四个下标:

L1 = nums1[(C1 - 1) / 2]
R1 = nums1[C1/2]
L2 = nums2[(C2 - 1) / 2]
R2 = nums2[C2/2]

为了保证我们的切割是合理的,我们需要保证:

L1 <= R1 
L1 <= R2
L2 <= R1
L2 <= R2

显然,第1、4个条件是肯定满足的,因此只有中间两个条件满足时,我们才能得到正确的划分。此时,我们可以引用二分搜索。如果L1 > R2,说明nums1中的切割点取的过大,需要缩小,换言之,我们可以调整nums2中的切割点,使其变大并超过nums1中的切割点;而如果L2 > R1,说明nums2中的切割点取的过大,缩小即可。完成切分点的选取后,选取两个子数组相应的最大最小值取平均,即可得到最终答案。

需要注意的是,切割点的下标有可能会发生数组越界。只需假设数组两端各自存在一个极值,当切割点越界时自动赋极值即可解决。

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length, n = nums2.length;
        
        // make sure nums2 is the smaller array
        if (m < n) {
            return findMedianSortedArrays(nums2, nums1);
        }

        int low = 0, high = n * 2;
        while (low <= high) {
            int cut2 = (low + high) / 2;
            int cut1 = m + n - cut2;

            // avoid boundary exception
            double L1 = (cut1 == 0) ? Integer.MIN_VALUE : nums1[(cut1-1)/2];
            double L2 = (cut2 == 0) ? Integer.MIN_VALUE : nums2[(cut2-1)/2];
            double R1 = (cut1 == m * 2) ? Integer.MAX_VALUE : nums1[(cut1)/2];
            double R2 = (cut2 == n * 2) ? Integer.MAX_VALUE : nums2[(cut2)/2];

            if (L1 > R2) {
                // nums1's lower half is too big; need to move C1 left (C2 right)
                low = cut2 + 1;
            } else if (L2 > R1) {
                // nums2's lower half too big; need to move C2 left.
                high = cut2 - 1;
            } else {
                // Otherwise, that's the right cut.
                return (Math.max(L1, L2) + Math.min(R1, R2)) / 2;
            }
        }
        return -1;
    }
}

5. Longest Palindromic Substring

https://leetcode.com/problems/longest-palindromic-substring/


遍历每一个字符,每一个字符都向两侧延展,如果新延展得到的两个字符仍然相同则继续延展,以此计算能延展的最大长度。

考虑到回文字符串的长度有两种可能,需要按照奇偶性进行两次延展判断。

class Solution {
    private int startIndex = 0, maxLen = 0;
    
    private void checkPalindrome(char[] array, int left, int right) {
        while (left >= 0 && right <= array.length - 1 && array[left] == array[right]) {
            left--;
            right++;
        }

        // update maxLen = (right - 1) - (left + 1) + 1 = right - left - 1
        if (maxLen < right - left - 1) {
            maxLen = right - left - 1;
            startIndex = left + 1;
        }
    }

    public String longestPalindrome(String s) {
        if (s.length() <= 1) {
            return s;
        }

        char[] array = s.toCharArray();
        for (int i = 0; i < array.length - 1; i++) {
            checkPalindrome(array, i, i); // assume odd length
            checkPalindrome(array, i, i+1); // assume even length
        }
        
        return s.substring(startIndex, startIndex + maxLen);
    }
}

6. Zigzag Conversion

https://leetcode.com/problems/zigzag-conversion/


根据numRows的长度建立等长的StringBuilder数组,每一行对应一个StringBuilder。按照“竖直向下——斜向右上”的顺序不断向当前行的StringBuilder添加字符串中的字符即可。输出时,要对StringBuilder进行格式转化。

class Solution {
    public String convert(String s, int numRows) {
        int len = s.length();

        // every row is a StringBuilder
        StringBuilder[] sb = new StringBuilder[numRows];
        for (int i = 0; i < numRows; i++) {
            sb[i] = new StringBuilder();
        }

        // index to traverse the whole string
        int index = 0;
        while (index < len) {
            // vertically down
            for (int i = 0; i < numRows && index < len; i++) {
                sb[i].append(s.charAt(index++));
            }
            
            // obliquely up (not include the first and last row)
            for (int i = numRows - 2; i >= 1 && index < len; i--) {
                sb[i].append(s.charAt(index++));
            }
        }

        // append all rows of result
        for (int i = 1; i < numRows; i++) {
            sb[0].append(sb[i]);
        }
        
        return sb[0].toString();
    }
}

7. Reverse Integer

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


通过整除反向提取每一位数。为了预防范围溢出,需要在每一次提取后进行一次溢出判断。

class Solution {
    public int reverse(int x) {
        long result = 0;

        while (x != 0) {
            // obtain the last bit
            result = result * 10 + x % 10; 
            x /= 10;
            
            // overflow check
            if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE) {
                return 0;
            }
        }

        return (int)result;
    }
}

8. String to Integer (atoi)

https://leetcode.com/problems/string-to-integer-atoi/


流程分为三步:

  1. 去除字符串开头的空格
  2. 判断是否存在正负号
  3. 判断是否为数字。如果是数字,更新运算结果,并检查当前结果是否超出整数范围;如果不是数字,则返回当前结果
class Solution {
    public int myAtoi(String str) {
        // trim leading blank space
        str = str.trim();
        if (str.length() == 0) {
            return 0;
        }

        // detect sign
        char firstChar = str.charAt(0);
        int sign = 1;
        int digitStart = 0; // default no sign char, thus digit start from index 0
        if (firstChar == '+' || firstChar == '-') {
            sign = (firstChar == '+') ? 1 : -1;
            digitStart++;
        }

        // add digits
        long sum = 0;
        for (int i = digitStart; i < str.length(); i++) {
            char current = str.charAt(i);

            // non-digit, return sum immediately
            if (current < '0' || current > '9') {
                return (int)sum * sign;
            }
            
            // continue adding current digit
            sum = sum * 10 + current - '0';

            // overflow checking
            if (sign == 1 && sum > Integer.MAX_VALUE) {
                return Integer.MAX_VALUE;
            }
            if (sign == -1 && -sum < Integer.MIN_VALUE) {
                return Integer.MIN_VALUE;
            }
        }
        return (int)sum * sign;
    }
}

9. Palindrome Number

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


将数字反转后与原数字进行比较。注意,不需要完整地将整个数字进行反转,只需要反转一半的数字,并与剩余一半进行比较即可。

class Solution {
    public boolean isPalindrome(int x) {
        // exclude negative number and 10's multiple
        if (x < 0 || (x != 0 && x % 10 == 0)) {
            return false;
        }

        int result = 0;
        
        // only need to reverse half of all digits
        while (x > result) {
            result = result * 10 + x % 10;
            x /= 10;
        }
        
        // odd or even length
        return (x == result || x == result / 10); 
    }
}

10. Regular Expression Matching

https://leetcode.com/problems/regular-expression-matching/


此题需要使用二维动态规划算法。对于sp,我们需要从头开始推导出子字符串s[i]p[j]的匹配情况,进而推出整个sp是否匹配。

解决时,需要对题设条件进行分类讨论:

  • 最标准的匹配情况,即string[i] = pattern[j],则dp[i+1][j+1] = dp[i][j]
  • pattern中出现了'.',可以匹配string中任意的一个字符,同样有dp[i+1][j+1] = dp[i][j]
  • pattern中出现了'*',需要进一步讨论:
    • 如果pattern[j-1] != string[i],说明此时的'a*'只能作为空字符串匹配,因此dp[i+1][j+1] = dp[i+1][j-1]
    • 如果pattern[j-1] == string[i], 或是pattern[j-1] == '.',即在’*‘之前的一个字符也能够匹配上,则:
      • 如果'*'匹配空字符串,则dp[i+1][j+1] = dp[i+1][j-1]
      • 如果'*'匹配一个字符,则dp[i+1][j+1] = dp[i+1][j]
      • 如果'*'匹配多个字符,则dp[i+1][j+1] = dp[i][j+1]
class Solution {
    public boolean isMatch(String s, String p) {
        boolean[][] dp = new boolean[s.length() + 1][p.length() + 1];
        
        // s and p are both empty, so they can match
        dp[0][0] = true;

        // pattern cannot start with '*'
        if (p.length() > 0 && p.charAt(0) == '*') {
            return false;
        }

        // '*' match
        for (int j = 0; j < p.length(); j++) {
            if (p.charAt(j) == '*' && dp[0][j-1]) {
                dp[0][j+1] = true;
            }
        }
    
        for (int i = 0; i < s.length(); i++) {
            for (int j = 0; j < p.length(); j++) {
                // regular matching, or '.' matching
                if (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.') {
                    dp[i+1][j+1] = dp[i][j];    
                } else if (p.charAt(j) == '*') {
                    // * can only be matched as empty char
                    if (s.charAt(i) != p.charAt(j-1)) {
                        dp[i+1][j+1] = dp[i+1][j-1];
                    }
                    
                    // * can be matched as 0, 1 or multiple chars
                    if (s.charAt(i) == p.charAt(j-1) || p.charAt(j-1) == '.') {
                        dp[i+1][j+1] = dp[i+1][j-1] || dp[i+1][j] || dp[i][j+1]; 
                    }
                }
            }
        }
        
        return dp[s.length()][p.length()];
    }
}

11. Container With Most Water

https://leetcode.com/problems/container-with-most-water/


问题转化为判断两个横坐标之间能够围成的最大面积。定义两个指针,一个指向数组头向尾遍历,另一个指向数组尾向头遍历。在遍历之前不断计算当前能围成的最大面积,并且向着可能进一步增大面积的方向移动指针。

class Solution {
    public int maxArea(int[] height) {
        if (height == null || height.length <= 1) {
            return 0;
        }

        int left = 0, right = height.length - 1;
        int result = 0, current = 0; // max area, current area

        while (left < right) {
            current = (right - left) * Math.min(height[left], height[right]);
            result = Math.max(result, current);

            // keep the higher edge, and try to increase the other edge
            if (height[left] < height[right]) {
                left++;
            } else {
                right--;
            }
        }
        return result;
    }
}

12. Integer to Roman

https://leetcode.com/problems/integer-to-roman/


非常无聊的题目,直接穷举nums范围内各个基数单位,不断叠加上当前的最大基数对应的罗马字符。

class Solution {
    public String intToRoman(int num) {
        int[] value = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
        String[] roman = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
        StringBuilder sb = new StringBuilder();
        
        for (int i = 0; i < value.length; i++) {
            while (num >= value[i]) {
                sb.append(roman[i]);
                num = num - value[i];
            }
        }
        
        return sb.toString();
    }
}

13. Roman to Integer

https://leetcode.com/problems/roman-to-integer/


遇到可能包含歧义的字母(M, X, V等)时,额外判断后一个字母明确具体数字即可。

class Solution {
    public int romanToInt(String s) {
        if (s.length() == 0) {
            return 0;
        }

        int i = 0;
        int len = s.length();
        int result = 0;

        while (i < len) {
            char c = s.charAt(i); // current character
            char nextChar = '.'; // if there is a next character
            if (i != len - 1) {
                nextChar = s.charAt(i+1);
            }

            // need to check IV and IX
            if (c == 'I') {
                if (nextChar == 'V') {
                    result += 4;
                    i += 2;
                } else if (nextChar == 'X') {
                    result += 9;
                    i += 2;
                } else {
                    result += 1;
                    i += 1;
                }
            }

            else if (c == 'V') {
                result += 5;
                i += 1;
            }

            // need to check XL and XC
            else if (c == 'X') {
                if (nextChar == 'L') {
                    result += 40;
                    i += 2;
                } else if (nextChar == 'C') {
                    result += 90;
                    i += 2;
                } else {
                    result += 10;
                    i += 1;
                }
            }

            else if (c == 'L') {
                result += 50;
                i += 1;
            }

            // need to check CD and CM
            else if (c == 'C') {
                if (nextChar == 'D') {
                    result += 400;
                    i += 2;
                } else if (nextChar == 'M') {
                    result += 900;
                    i += 2;
                } else {
                    result += 100;
                    i += 1;
                }
            }

            else if (c == 'D') {
                result += 500;
                i += 1;
            }

            else if (c == 'M') {
                result += 1000;
                i += 1;
            }
        }
        return result;
    }
}

14. Longest Common Prefix

https://leetcode.com/problems/longest-common-prefix/


初始假定第一个词就是LCS。为了验证假设,我们需要逐个检查所有后续字符串,确定最开始的LCS也是后面字符串的前缀。如果不满足,则删掉LCS的最后一个字符。

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if (strs == null || strs.length == 0) {
            return "";
        }
        
        // initialized as the first word of strs
        String result = strs[0];

        for (int i = 0; i < strs.length; i++) {
            // keeps removing the last character until matching the common prefix requirement
            while (strs[i].indexOf(result) != 0) {
                result = result.substring(0, result.length() - 1);
                if (result.length() == 0) {
                    break;
                }
            }
        }
            
        return result;
    }
}

15. 3Sum

https://leetcode.com/problems/3sum/


先对数组进行排序,接着设立三个指针first, second, thirdfirst负责从数组头遍历至数组尾,在每一次循环中固定first,移动头尾指针twothird,对nums[first]nums[second]nums[third]求和判断是否为0。

这题需要最大化利用排序带来的优势,缩短运行时间:

  • 检查最小/最大值,跳过一些不必要的检查
  • 跳过相邻的重复元素
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        
        int len = nums.length;
        if (len < 3) {
            return result;
        }

        // sort so we can use pointers to quickly sum
        Arrays.sort(nums); 
        
        // skip impossible cases
        if (3 * nums[0] > 0 || 3 * nums[len - 1] < 0) {
            return result;
        }

        for (int first = 0; first < len - 2; first++) {
            // skip if nums[first] is duplicate
            if (first > 0 && nums[first] == nums[first-1]) {
                continue; 
            }
            
            // too big, no need to check subsequent combinations
            if (3 * nums[first] > 0) {
                break; 
            }
            
            // too small, we need to start from a larger nums[first]
            if (nums[first] + 2 * nums[len - 1] < 0) {
                continue;
            }

            int second = first + 1, third = len - 1;
            while (second < third) {
                if (nums[first] + nums[second] + nums[third] == 0) {
                    result.add(Arrays.asList(nums[first], nums[second], nums[third]));

                    // skip nums[second] and nums[third] duplicates
                    while (second < third && nums[second] == nums[second + 1]) {
                        second++;
                    }
                    while (third > second && nums[third] == nums[third - 1]) {
                        third--;
                    }

                    // move pointers
                    second++;
                    third--;
                } else if (nums[first] + nums[second] + nums[third] < 0) {
                    second++;
                } else {
                    third--;
                }
            }
        }
        return result;
    }
}

16. 3Sum Closest

https://leetcode.com/problems/3sum-closest/


与前一题思路类似,只是将”等于“改成了”最接近于“。额外加一个变量tempSum用于存储当前最接近的和即可。

class Solution {
    public int threeSumClosest(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int len = nums.length;
        int result = 0;

        // less than three numbers, directly return all num's sum
        if (len <= 3) {
            for (int num : nums) {
                result += num;
            }
            return result;
        }

        // initialized as the sum of first three elements
        result = nums[0] + nums[1] + nums[2];
        
        // sort so we can use pointers to quickly sum
        Arrays.sort(nums);

        for (int first = 0; first < len - 2; first++) {
            // skip if nums[first] is duplicate
            if (first > 0 && nums[first] == nums[first - 1]) {
                continue;
            }

            int second = first + 1, third = len - 1;
            while (second < third) {
                int tempSum = nums[first] + nums[second] + nums[third];
                
                // determine if tempSum is equal to target, or try to get tempSum closer to target
                if (tempSum == target) {
                    return tempSum;
                } else if (tempSum < target) {
                    second++;
                } else {
                    third--;
                }

                // update result
                if (Math.abs(target - result) > Math.abs(target - tempSum)) {
                    result = tempSum;
                }
            }
        }
        
        return result;
    }
}

17. Letter Combinations of a Phone Number

https://leetcode.com/problems/letter-combinations-of-a-phone-number/


设立一个字符串数组,数组下标0-9分别对应键盘0-9上的字符串。

遍历digits的每一位数字,得到这个数字可能对应的所有字母。使用回溯算法,尝试添加每一个字符,再通过深度优先思想添加下一个。以此类推,直到当前的string builder长度等于digits长度,表示发掘出了一种组合。

class Solution {
    private List<String> result = new ArrayList<>();
    private final String[] mapping = {"0", "1", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    
    public List<String> letterCombinations(String digits) {
        if (digits == null || digits.length() == 0) {
            return result;
        }
        
        backtrack(new StringBuilder(), digits, 0);
        
        return result;
    }
    
    private void backtrack(StringBuilder current, String digits, int index) {
        // find a combination, add to result
        if (current.length() == digits.length()) {
            result.add(current.toString());
            return;
        }
        
        // get all possible letters that are matching with current digit
        int digit = digits.charAt(index) - '0';
        String letters = mapping[digit];
        
        for (char letter: letters.toCharArray()) {
            current.append(letter);
            backtrack(current, digits, index + 1);
            current.deleteCharAt(current.length() - 1);
        }
    }
}

18. 4Sum

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


4Sum与3Sum的思路类似,仍然是使用多个指针的移动解决问题。使用first, second两个指针从头到尾遍历整个数组,而third, fourth两个指针则在second的后面,一前一后。

需要注意,在判断极值的时候可能会产生溢出,因此需要考虑int和long之间的转换。

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> result = new ArrayList<>();
        int len = nums.length;
        if (len < 4) {
            return result;
        }

        // sort so we can use pointers to quickly sum
        Arrays.sort(nums);
        
        // too big or small
        Long potentialMin = (long)4 * nums[0];
        if (potentialMin > target) {
            return result;
        }
        
        Long potentialMax = (long)4 * nums[len - 1];
        if (potentialMax < target) {
            return result;
        }

        for (int first = 0; first < len - 3; first++) {
            // skip if nums[first] is duplicate
            if (first > 0 && nums[first] == nums[first-1]) {
                continue;
            }
            
            // too big, no need to check subsequent combinations
            Long potentialRestFourMin = (long)4 * nums[first];
            if (potentialRestFourMin > target) {
                break;
            }
            
            // too small, we need to start from a larger nums[first]
            Long potentialRestThreeMax = (long)3 * nums[len - 1];
            if (nums[first] + potentialRestThreeMax < target) {
                continue;
            }

            for (int second = first + 1; second < len - 2; second++) {
                // skip if nums[first] is duplicate
                if (second > first + 1 && nums[second] == nums[second - 1]) {
                    continue;
                }
                
                // too big, no need to check subsequent combinations
                Long potentialRestThreeMin = (long)3 * nums[second];
                if (potentialRestThreeMin + nums[first] > target) {
                    break;
                }
                
                // too small, we need to start from a larger nums[second]
                if (nums[first] + nums[second] + 2 * nums[len - 1] < target) {
                    continue;
                }

                int third = second + 1, fourth = len - 1;
                while (third < fourth) {
                    if (nums[first] + nums[second] + nums[third] + nums[fourth] == target) {
                        result.add(Arrays.asList(nums[first], nums[second], nums[third], nums[fourth]));
                        
                        // skip duplicates
                        while (second < len - 2 && nums[second] == nums[second + 1]) {
                            second++;
                        }
                        while (third < fourth && nums[third] == nums[third + 1]) {
                            third++;
                        }
                        while (third < fourth && nums[fourth] == nums[fourth - 1]) {
                            fourth--;
                        }

                        // move pointers
                        third++;
                        fourth--;
                    } else if (nums[first] + nums[second] + nums[third] + nums[fourth] < target) {
                        third++;
                    } else {
                        fourth--;
                    }
                }
            }
        }
        return result;
    }
}

19. Remove Nth Node From End of List

https://leetcode.com/problems/remove-nth-node-from-end-of-list/


引入两个指针,一快一慢,二者之间的下标差为n(即slow指针指向第1个元素,则fast指针指向第n+1个元素)。删除节点则只需要调整节点的next指针即可。

/**
 * 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 removeNthFromEnd(ListNode head, int n) {
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        
        // slow and fast pointers
        ListNode slow = dummyHead, fast = dummyHead;

        // fast moves n steps ahead
        for (int i = 0; i < n; i++) {
            fast = fast.next;
        }

        // move slow and fast together
        while (fast.next != null) {
            slow = slow.next;
            fast = fast.next;
        }

        // delete nth node
        slow.next = slow.next.next;

        return dummyHead.next;
    }
}

20. Valid Parentheses

https://leetcode.com/problems/valid-parentheses/


利用栈的特性,如果遇到左半侧的括号则推入栈中,右半侧的括号则检查是否与当前栈顶元素配对,如果不能配对就返回false,否则配对成功就把栈顶括号弹出。最后确认栈是否为空。

class Solution {
    public boolean isValid(String s) {
        if (s.length() == 0) {
            return true;
        }

        Stack<Character> st = new Stack<>();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);

            // push all left parentheses into stack
            if (c == '(' || c == '[' || c == '{') {
                st.push(c);
            } else if (c == ')' || c == ']' || c == '}') {
                // current right parenthesis must match with top parenthesis in the stack
                if (st.size() == 0) {
                    return false;
                }

                if (c == ')' && st.peek() != '(') {
                    return false;
                } else if (c == ']' && st.peek() != '[') {
                    return false;
                } else if (c == '}' && st.peek() != '{') {
                    return false;
                } else {
                    // successfully matched, pop the top parenthesis
                    st.pop();
                }
            }
        }
        return st.isEmpty();
    }
}

21. Merge Two Sorted Lists

https://leetcode.com/problems/merge-two-sorted-lists/


第一种方法,比较l1l2上的指针当前指向的元素,取较小元素之后的子链表和另一个链表继续进行递归合并。这样能保证每一次比较都优先提取出两个剩余链表的最小值,其余部分的结果也会接在这个最小值之后。

/**
 * 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 mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }

        // merge after smaller node
        if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

第二种方法,也是比较l1l2上的指针当前指向的元素,但不使用递归,而是直接根据大小关系决定排列顺序。

/**
 * 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 mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }

        ListNode dummyHead = new ListNode(0);
        ListNode cur = dummyHead;

        while (l1 != null && l2 != null) {
            // decide which element to be added to the result list
            if (l1.val < l2.val) {
                cur.next = l1;
                l1 = l1.next;
            } else {
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }
        
        // one list reaches the end, add the rest list elements to result
        cur.next = (l2 == null) ? l1 : l2;
        
        return dummyHead.next;
    }
}

22. Generate Parentheses

https://leetcode.com/problems/generate-parentheses/


使用递归策略,优先构造左括号,当左括号个数大于右括号时再构造右括号。

具体代码实现时,可以使用两个变量leftright跟踪当前左、右括号数量,在递归中将它们与n进行比较。

class Solution {
    List<String> result = new ArrayList<>();
    
    public List<String> generateParenthesis(int n) {
        generate("", 0, 0, n);
        return result;
    }

    // left: left parentheses' count
    // right: right parentheses' count
    private void generate(String s, int left, int right, int n) {
        if (right == n) {
            // finish construction
            result.add(s); 
        } else {
            // always prefer to generate left parenthesis first
            if (left < n) {
                generate(s + "(", left + 1, right, n);
            }
            
            // left is greater than right, then generate a right parenthesis
            if (left > right) {
                generate(s + ")", left, right + 1, n);
            }
        }
    }
}

23. Merge k Sorted Lists

https://leetcode.com/problems/merge-k-sorted-lists/


第一种解法是使用优先队列。优先队列本质上是一种堆,具体使用时可以自定义comparator指定最小堆或是最大堆。由于此题要求从小到大排序,因此需要定义最小堆。

将每一条链表的头元素加入优先队列中。优先级最高的元素就是这些元素里的最小值。将其排序后进行判断,如果该元素的next指针不指向空,则将剩下的链表头元素重新插入优先队列中。

以一个形象一些的方式描述该场景:一位教师正在向一群排队的学生解答问题,每个学生都有多个问题,并且问题的难度有难有易,教师优先回答简单的问题。为保证公平,当前问问题的学生问完一个问题后需要重新排队,排队顺序取决于他下一个问题的难度。

时间复杂度上,如果链表有N个节点,则堆的插入、删除操作时间复杂度均为O(logN),总的时间复杂度为O(NlogN)

/**
 * 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 mergeKLists(ListNode[] lists) {
        if (lists == null || lists.length == 0) {
            return null;
        }

        // override priority queue's comparator in ascending order
        Queue<ListNode> pq = new PriorityQueue<>((ListNode l1, ListNode l2) -> {
            return l1.val - l2.val;
        });

        // add each list's head (smallest) node into the priority queue
        for (ListNode list : lists) {
            if (list != null) {
                pq.offer(list);
            }
        }

        ListNode dummyHead = new ListNode(0);
        ListNode cur = dummyHead;

        while (!pq.isEmpty()) {
            // pick out the smallest element from priority queue
            ListNode currentHead = pq.poll();
            cur.next = currentHead;
            cur = cur.next;

            // add its next element into the queue
            if (cur.next != null) {
                pq.offer(cur.next);
            }
        }
        
        return dummyHead.next;
    }
}

第二种解法是使用分治策略,将k条链表对半分为两组,问题转化为了广义版的merge two sorted lists问题。不断从上而下划分,再由小到大不断两两合并,得到完整的链表。

/**
 * 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 mergeKLists(ListNode[] lists) {
        return merge(lists, 0, lists.length - 1);
    }

    private ListNode merge(ListNode[] lists, int l, int r) {
        // invalid interval
        if (l > r) {
            return null; 
        }
        
        // interval only has one list group
        if (l == r) {
            return lists[l]; 
        }

        // divide k lists into two groups
        int mid = l + (r - l) / 2;
        ListNode left = merge(lists, l, mid);
        ListNode right = merge(lists, mid + 1, r);

        // merge two "big" lists together
        ListNode dummyHead = new ListNode(0);
        ListNode cur = dummyHead;
        
        while (left != null || right != null) {
            // right list is null, or the head of left list is smaller than the one of right list
            if (right == null || (left != null && left.val < right.val)) {
                cur.next = left;
                left = left.next;
            } else {
                cur.next = right;
                right = right.next;
            }
            cur = cur.next;
        }
        
        return dummyHead.next;
    }
}

24. Swap Nodes in Pairs

https://leetcode.com/problems/swap-nodes-in-pairs/


使用递归,只提取前两个节点,将剩余节点视为一个整体。

新的头节点newHead是原来的第二个节点head.next,而原来的头节点head指向剩余节点整体(剩余部分的头节点为head.next.next)。

/**
 * 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 swapPairs(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }

        // previous second node now bcomes the head node
        ListNode newHead = head.next; 

        // regard all other nodes as an entity, and the head of this entity is head.next.next
        head.next = swapPairs(head.next.next); 

        // previous head node now becomes the second node
        newHead.next = head;

        return newHead;
    }
}

25. Reverse Nodes in k-Group

https://leetcode.com/problems/reverse-nodes-in-k-group/


假设遍历链表的指针为cur,将cur移动k个节点,获取到k个节点就能组成一个节点簇。我们优先递归完成链表后续部分的反转,再来倒转节点簇的k个节点。每个节点簇内,我们的核心思想是把原先的第一个节点指向链表后续部分,这样一来这个节点就可以被归到已经逆序的子链表中;而原先节点簇的第二个节点就成为了剩余未逆序部分的头节点。我们需要不断循环k次,按照上述流程调整顺序。

/**
 * 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 reverseKGroup(ListNode head, int k) {
        if (head == null) {
            return null;
        }

        // move cur k nodes forward
        ListNode cur = head;
        int count = 0;
        while (cur != null && count != k) {
            count++;
            cur = cur.next;
        }

        // we get k nodes in a group now
        if (count == k) {
            // recursively complete all other nodes first to get the modified sub-list
            cur = reverseKGroup(cur, k);

            // reverse k nodes
            while (count-- > 0) {
                // get the second element among k nodes
                ListNode second = head.next;
                
                // current first element (head) will point to the modified sub-list 
                head.next = cur;

                // previous head has become the head of the modified sub-list
                cur = head;
                
                // previous second element now becomes the new head of the un-reversed part
                head = second; 
            }
            
            // after reverse, the head of the modified sub-list is actually the head of the whole list
            head = cur;
        }
        
        return head;
    }
}

26. Remove Duplicates from Sorted Array

https://leetcode.com/problems/remove-duplicates-from-sorted-array/


从头开始遍历,只要遇到不重复的数时,就覆盖数组的原始值。最后返回的result值实际上就是不同数字的个数。

class Solution {
    public int removeDuplicates(int[] nums) {
        int len = nums.length;
        if (len <= 1) {
            return len;
        }

        int result = 1;
        for (int i = 1; i < len; i++) {
            if (nums[i] != nums[i - 1]) {
                nums[result] = nums[i]; // in-place modification
                result++;
            }
        }
            
        return result;
    }
}

27. Remove Element

https://leetcode.com/problems/remove-element/


与上一题一样的思路,遇到值为val的元素时跳过,其余仍从头到尾开始覆盖。

class Solution {
    public int removeElement(int[] nums, int val) {
        int len = nums.length;

        int result = 0;
        for (int i = 0; i < len; i++) {
            if (nums[i] != val) {
                nums[result] = nums[i]; // in-place modification
                result++;
            }
        }
            
        return result;
    }
}

28. Implement strStr()

https://leetcode.com/problems/implement-strstr/


遍历haystack中的每一个字符,如果某个字符与needle的首字符相同,再检查haystack中该字符起的子串是否与needle的值相同。

class Solution {
    public int strStr(String haystack, String needle) {
        int hLen = haystack.length(), nLen = needle.length();
        
        // needle is an empty string
        if (nLen == 0) {
            return 0; 
        }
        
        // haystack is empty and impossible to contain needle
        if (hLen == 0) {
            return -1; 
        }

        for (int i = 0; i <= hLen - nLen; i++) {
            // current char is equal to needle's first char & substring is equal to needle
            if (haystack.charAt(i) == needle.charAt(0) && haystack.substring(i, i + nLen).equals(needle)) {
                return i;
            }
        }

        return -1;
    }
}

29. Divide Two Integers

https://leetcode.com/problems/divide-two-integers/


此题要求实现除法,并且不能使用乘、除、取模三个符号。大致的思路流程是:

  1. 专门处理被除数为Integer.MIN_VALUEdivisor为-1时的情况,这种时候int形式的结果会发生溢出
  2. dividenddivisor都取绝对值
  3. 使用指数倍增思想,快速寻找到dividend中至少包含几个divisor
  4. 决定最终结果的实际符号

为了避免溢出,在第一步处理完特例后,我们最好将dividenddivisor都转为long格式。

class Solution {
    public int divide(int dividend, int divisor) {
        // corner case
        if (dividend == Integer.MIN_VALUE && divisor == -1) {
            return Integer.MAX_VALUE;
        }
        
        long dividendCopy = (long)dividend, divisorCopy = (long)divisor;

        // get rid of sign 
        long a = Math.abs(dividendCopy), b = Math.abs(divisorCopy);
        long result = 0;

        while (a >= b) {
            int x = 0;

            // count how many divisor does the dividend "has"
            while (a >= (b << 1 << x)) {
                x++;
            }
            
            result += 1 << x;
            a -= b << x;
        }

        // determine if dividend and divisor have same sign
        return (dividend > 0) == (divisor > 0) ? (int)result : (int)-result;
    }
}

30. Substring with Concatenation of All Words

https://leetcode.com/problems/substring-with-concatenation-of-all-words/


只要words中的所有单词拼接得到s中的子字符串即可满足题意,单词的顺序并不重要。因此我们只需要关注words中每一个单词出现的次数。

此题可以使用两个HashMap解决,一个HashMap用于记录words数组中的每一个单词出现的次数;另一个HashMap用于记录当前遍历s时的单词已经出现了几次。如果当前遍历的单词是words中没有的,或者出现次数已经超过了“应该”出现的次数,则说明不满足题意。

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> result = new ArrayList<>();
        if (words.length == 0) {
            return result;
        }

        // how many times each word is supposed to occur
        Map<String, Integer> expected = new HashMap<>();
        for (String word : words) {
            expected.put(word, expected.getOrDefault(word, 0) + 1);
        }
            
        // each word's length and all words' count
        int len = words[0].length(), count = words.length;
        if (s.length() < count * len) {
            return result;
        }

        for (int i = 0; i < len; i++) {
            for (int j = i; j <= s.length() - count * len; j += len) {
                // how many times every word actually occurs in each substring
                Map<String, Integer> actual = new HashMap<>();

                // iterate from the last
                for (int k = count - 1; k >= 0; k--) {
                    // extract current word
                    String currentWord = s.substring(j + k * len, j + (k + 1) * len);
                    
                    // current word is not supposed to occur
                    if (!expected.containsKey(currentWord)) {
                        break;
                    } else {
                        int occurences = actual.getOrDefault(currentWord, 0) + 1;
                        
                        // can not exceed expected occurences
                        if (occurences > expected.get(currentWord)) {
                            // start right after invalid occurence, as previous choices will include invalid.
                            j += k * len;
                            break;
                        }
                        
                        actual.put(currentWord, occurences);
                        
                        // all words appear
                        if (k == 0) {
                            result.add(j);
                        }
                    }
                }
            }
        }
        return result;
    }
}

31. Next Permutation

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


一个数组向下一置换演变的趋势总体是从升序演变为降序。

找到下一置换,实际上就是从右到左遍历,先找到破坏升序的第一个数nums[i],再从这个数的右侧(也是从右向左遍历)找到第一个比nums[i]大的数nums[j],这两个数进行交换。最后,对下标i之后的数需要全部进行反转,因为在经过交换之后,从下标i开始一直到结尾都是降序,我们需要将i + 1到结尾重新调整为升序,确保不遗漏置换的可能。

class Solution {
    public void nextPermutation(int[] nums) {
        if (nums == null || nums.length <= 1) {
            return;
        }

        // search from right to left and find the first element to break the ascending order
        int i = nums.length - 2;
        while (i >= 0 && nums[i] >= nums[i+1]) {
            i--;
        }

        // if there is an element that break the ascending order
        if (i >= 0) {
            int j = nums.length - 1;

            // find the rightmost element which is larger than nums[i]
            while (nums[i] >= nums[j]) {
                j--;
            }
            
            // swap nums[i] and nums[j]
            swap(nums, i, j);
        }
        // reverse all elements after index i
        reverseArray(nums, i + 1, nums.length - 1);
    }

    // swap two nums
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

    // reverse part of the array
    private void reverseArray(int[] nums, int i, int j) {
        while (i < j) {
            swap(nums, i++, j--);
        }
    }
}

32. Longest Valid Parentheses

https://leetcode.com/problems/longest-valid-parentheses/


使用栈解决。遍历字符串,如果遇到’(‘,则将当前下标推入栈中;遇到’)’,则将栈顶下标弹出,并讨论。需要注意:

  • 栈里存储的下标,实质上指的是“截止到这个下标时,字符串不能组成valid parentheses”。我们最后更新结果时,也是用当前下标与栈顶下标的差值来更新长度,因为这一段差值之间的字符串一定是有效的
  • 因为上述设定,栈的初始化需要push一个-1;如果空栈的时候,说明截止至目前的字符串一定是无效的,我们要想找有效字符串只能在此之后寻找
class Solution {
    public int longestValidParentheses(String s) {
        int result = 0;
        
        ArrayDeque<Integer> stack = new ArrayDeque<Integer>();
        
        // the index preceding to potential start of valid parentheses
        stack.push(-1);
        
        for (int i = 0; i < s.length(); i++) {
            char curr = s.charAt(i);
            if (curr == '(') {
                stack.push(i);
            } else {
                // no matter we get valid parentheses or not, we need to pop here
                // if valid, we need to get last index to calculate diff
                // if unvalid, we update with current index
                stack.pop();
                
                if (stack.isEmpty()) {
                    // the stack is empty only if we have an extra ')'. Any further extensions of valid parentheses is impossible
                    // push current index, since it is preceding to potential start of valid parentheses
                    stack.push(i);
                } else {
                    // this may be our longest valid parentheses
                    result = Math.max(result, i - stack.peek());
                }
            }
        }
        
        return result;
    }
}

33. Search in Rotated Sorted Array

https://leetcode.com/problems/search-in-rotated-sorted-array/


这一题需要将数组对半划分来看,其中一半一定是有序的,而另一半则在中间某处破坏了升序。因此在使用模型1时,我们无法简单通过比较nums[mid]target确定下一轮搜索区间,而是应该比较nums[left]nums[mid]nums[right]三者的值,先判断哪一半数组是有序的;之后将target放进有序的这一侧进行比较,如果target的值介于其中则把区间定为有序的这一侧,反之定为另一侧。

class Solution {
    public int search(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }

        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;

            // nums[mid] matches with target, return
            if (nums[mid] == target) {
                return mid;
            }

            // left half is ordered
            if (nums[left] <= nums[mid]) {
                // target is in left half
                if (target >= nums[left] && target <= nums[mid]) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            }

            // right half is ordered
            if (nums[mid] <= nums[right]) {
                // target is in right half
                if (target >= nums[mid] && target <= nums[right]) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
        }
        
        return -1;
    }
}

34. Find First and Last Position of Element in Sorted Array

https://leetcode.com/problems/find-first-and-last-position-of-element-in-sorted-array/


分别进行左边界搜索和右边界搜索即可。

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] result = new int[] {-1, -1};
        if (nums == null || nums.length == 0) {
            return result;
        }

        // left
        int left = 0, right = nums.length;
        while (left < right) { 
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else if (nums[mid] >= target) {
                right = mid;
            }
        }
        
        if (left != nums.length && nums[left] == target) {
            result[0] = left;
        }

        // right
        left = 0;
        right = nums.length;
        while (left < right) { 
            int mid = left + (right - left) / 2;
            if (nums[mid] <= target) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid;
            }
        }
        
        if (left != 0 && nums[left - 1] == target) {
            result[1] = left - 1;
        }

        return result;
    }
}

35. Search Insert Position

https://leetcode.com/problems/search-insert-position/


可以假想把target插入到nums数组中,再转化为数组长度+1后的标准二分查找。

class Solution {
    public int searchInsert(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return 0;
        }

        // num's length +1, therefore right should be 'nums.length' instead of 'nums.length - 1'
        int left = 0, right = nums.length;

        // standard binary search
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        
        // both left and right are valid
        return left; 
    }
}

36. Valid Sudoku

https://leetcode.com/problems/valid-sudoku/


每一个数字只能在所处的行、列、3*3方格内出现一次。因此,构造三个二维数组检查当前数字num分别在当前行、列、方格内出现的次数,超过1次就是不合法的。

class Solution {
    public boolean isValidSudoku(char[][] board) {
        int[][] row = new int[9][9]; // row[i][num] indicates the count of 'num' in the i-th row
        int[][] col = new int[9][9]; // col[j][num] indicates the count of 'num' in the j-th column
        int[][] block = new int[9][9]; // block[k][num] indicates the count of 'num' in the k-th 3*3 block

        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                // only validate filled cells
                if (board[i][j] != '.') {
                    int num = board[i][j] - '1';
                    int blockIndex = (i/3) * 3 + j/3;

                    // each num should not duplicate in each row, col, block
                    if (row[i][num] > 0 || col[j][num] > 0 || block[blockIndex][num] > 0) {
                        return false;
                    }
                    
                    row[i][num] += 1;
                    col[j][num] += 1;
                    block[blockIndex][num] += 1;
                }
            }
        }
        return true;
    }
}

37. Sudoku Solver

https://leetcode.com/problems/sudoku-solver/


遇到一个空方格,从0-9循环填入每一个数字,填完后确认没有违规,再以深度优先,尝试后续所有方格都满足要求,才能保证当前方格也满足要求。

遇到列尾时跳下一行,遇到行尾则说明深度优先搜索结束,数独解决完毕;而如果遇到不符合的情况,则要回溯复原当前填入的数字,再继续循环填入下一个数字。

class Solution {
    public void solveSudoku(char[][] board) {
        solve(board, 0, 0);
    }
    
    // solve based on index
    private Boolean solve(char[][] board, int i, int j) {
        // finish all 9 row
        if (i == 9) {
            return true;
        }
        
        // finish current row, jump to next row
        if (j == 9) {
            return solve(board, i+1, 0);
        }
        
        // non-digit, switch to next column
        if (board[i][j] != '.') {
            return solve(board, i, j+1);
        }

        // backtrack
        for (char c = '1'; c <= '9'; c++) {
            if (check(board, i, j, c)) {
                board[i][j] = c;
                
                // all subsequent cells are valid
                if (solve(board, i, j+1)) {
                    return true;
                }
                
                board[i][j] = '.';
            }
        }
        
        return false; // no solution
    }

    // check if current character is valid
    private Boolean check(char[][] board, int i, int j, char val) {
        for (int c = 0; c < 9; c++) {
            
            // duplicate in current row, column or 3 * 3 block
            if (board[i][c] == val ||
                board[c][j] == val ||
                board[i - i%3 + c/3][j - j%3 + c%3] == val) {
                return false;
            }
        }
        return true;
    }
}

38. Count and Say

https://leetcode.com/problems/count-and-say/


直接的想法是使用递归。递归的起点显然是n为1的情况。此后对于任意输入的n,先计算出n-1的结果字符串pre,再遍历pre得到n的结果。遍历pre时,统计当前数字连续出现了几次,把数字和次数直接加到StringBuilder里。

注意使用StringBuilder的目的主要是优化性能,因为String每次追加字符时都会产生新的对象;而StringBuilder与StringBuffer都是直接在同一对象上追加字符,效率很高。另外StringBuffer()适合多线程,而StringBuilder()适合单线程。

class Solution {
    public String countAndSay(int n) {
        if (n == 1) {
            return "1";
        }

        // all upper levels' result
        StringBuilder pre = new StringBuilder(countAndSay(n - 1));
        
        // construct this level
        StringBuilder result = new StringBuilder();

        int i = 0;
        while (i < pre.length()) {
            int count = 0; // current number's count
            int j = i; // start index of current number

            char say = pre.charAt(j);
            while (i < pre.length() && pre.charAt(i) == say) {
                count++;
                i++;
            }

            result.append(count);
            result.append(say);
        }
        return result.toString();
    }
}

39. Combination Sum

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


将原始数组排序,并遍历数组,创建一个新的临时数组用于存放当前的组合。每当向临时数组中添加一个数后,更新target,递归进入下一层搜索;由于每一个数可以使用无限次,下一次递归的起点下标index不变。最后当target等于0,说明当前组合满足;否则,则将临时数组中最后一个元素移除,寻找下一个数。

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(candidates);
        backtrack(result, new ArrayList<>(), candidates, target, 0);
        return result;
    }

    private void backtrack(List<List<Integer>> result, List<Integer> temp, int[] candidates, int target, int index) {
        if (target == 0) {
            result.add(new ArrayList<>(temp));
        } else {
            for (int i = index; i < candidates.length && candidates[i] <= target; i++) {
                temp.add(candidates[i]);
                backtrack(result, temp, candidates, target - candidates[i], i); // allow duplicate, thus starts from i
                temp.remove(temp.size() - 1);
            }
        }
    }
}

40. Combination Sum II

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


与上一题几乎一样的题设,唯一不同是输入数组中可能有重复的数字,且数组中每个数只能用一次。只需要稍微改变上一题的代码,调整下一层递归的起始下标;此外,完成一次回溯后,需要及时跳过重复的数字。

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(candidates);
        backtrack(result, new ArrayList<>(), candidates, target, 0);
        return result;
    }

    private void backtrack(List<List<Integer>> result, List<Integer> temp, int[] candidates, int target, int index) {
        if (target == 0) {
            result.add(new ArrayList<>(temp));
        } else {
            for (int i = index; i < candidates.length && candidates[i] <= target; i++) {
                temp.add(candidates[i]);
                backtrack(result, temp, candidates, target - candidates[i], i + 1); // not allow duplicate, thus starts from i + 1
                temp.remove(temp.size() - 1);
                
                // skip duplicates
                while (i + 1 < candidates.length && candidates[i] == candidates[i + 1]) {
                    i++;
                }
            }
        }
    }
}

41. First Missing Positive

https://leetcode.com/problems/first-missing-positive/


要寻找最小的缺失正整数,可以对数组进行处理:假如遍历发现了1,则将1放在数组第一个位置;发现2,则将2放在数组第二个位置…以此类推。当然,前提是只有遍历的元素值位于0和数组长度之间,才做此处理。

为了达到这一目的,只需要将nums[i]nums[nums[i] - 1]进行交换即可。最后,重新遍历顺序处理后的数组,第一个元素值和下标不匹配的元素即为答案。

class Solution {
    public int firstMissingPositive(int[] nums) {
        int len = nums.length;
        for (int i = 0; i < len; i++) {
            // if nums[i] is in the range of (0, len] and not in its place,
            // put nums[i] on the (nums[i] - 1)th index of the array
            while (nums[i] > 0 && nums[i] <= len && nums[i] != nums[nums[i] - 1]) {
                // swap nums[i] and nums[nums[i] - 1]
                int temp = nums[nums[i] - 1];
                nums[nums[i] - 1] = nums[i];
                nums[i] = temp;
            }
        }

        // the first number unmatched is the answer
        for (int i = 0; i < len; i++) {
            if (nums[i] != i + 1) {
                return i + 1;
            }
        }

        // all matched, the smallest positive integer is last integer + 1
        return len + 1;
    }
}

42. Trapping Rain Water

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


使用左右两个指针跟踪当前的池壁高度。根据木桶效应,高度下界lowerBound取决于池壁高度较低的一边,而当前已有的水槽高度currentLevel则根据较低的池壁高度不断更新。指针移动过程中,新增的水量为currentLevellowerBound的差值。

class Solution {
    public int trap(int[] height) {
        if (height == null || height.length <= 1) {
            return 0;
        }

        int left = 0, right = height.length - 1;
        int currentLevel = 0, result = 0;

        while (left < right) {
            int lowerBound = 0;
            
            // smaller of height[left] and height[right], then move left or right accordingly
            if (height[left] <= height[right]) {
                lowerBound = height[left];
                left++;
            } else {
                lowerBound = height[right];
                right--;
            }   
            
            // update current bar's level
            currentLevel = Math.max(currentLevel, lowerBound);
            
            // new water fills in
            result += currentLevel - lowerBound;
        }
        
        return result;
    }
}

43. Multiply Strings

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


此题限定了输入字符串只包含数字,并且不能直接转成整数再调用乘法计算,实际上便是指定了思路:模拟实现大数的乘法运算,类似于小学进行竖式乘法的过程。

对于一个m位整数与一个n位整数,其结果的长度一定为m+n-1m+n,因此可以提前创建一个长为m+n的数组用于存放最终结果的每一位数。双重循环遍历两个整数,可以发现num1[i] * num2[j]的结果对应的是result[i+j+1]。明确了位数之后,剩余的工作较为简单,只需逐位求值,保留尾数,提取进位向前累加即可。输出结果时,需要去掉起始的0。

class Solution {
    public String multiply(String num1, String num2) {
        int m = num1.length(), n = num2.length();
        
        if (m == 0 || n == 0) {
            return "0";
        }
        if (num1.equals("0") || num2.equals("0")) {
            return "0";
        }

        // num1[i] * num2[j] reflects on result[i+j+1]
        int[] result = new int[m+n];
        for (int i = m-1; i >= 0; i--) {
            for (int j = n-1; j >= 0; j--) {
                result[i+j+1] += (num1.charAt(i) - '0') * (num2.charAt(j) - '0');
            }
        }

        // calculate each digit and each carry
        int carry = 0;
        for (int k = result.length - 1; k >= 0; k--) {
            int digit = (result[k] + carry) % 10;
            carry = (result[k] + carry) / 10;
            result[k] = digit;
        }

        StringBuilder sb = new StringBuilder();
        
        // trim all leading 0
        for (int each : result) {
            if (!(each == 0 && sb.length() == 0)) {
                sb.append(each);
            }
        }
            
        return sb.toString();
    }
}

44. Wildcard Matching

https://leetcode.com/problems/wildcard-matching/


解法一,参考Regular Expression Matching中的解法,使用二维动态规划:

class Solution {
    public boolean isMatch(String s, String p) {
        boolean[][] dp = new boolean[s.length() + 1][p.length()+1];
        dp[0][0] = true;

        // empty pattern cannot match with any string
        for (int i = 1; i <= s.length(); i++) {
            dp[i][0] = false;
        }

        // multiple '*' pattern can match with empty string
        for (int j = 1; j <= p.length(); j++) {
            if (p.charAt(j-1) != '*') {
                break;
            } else {
                dp[0][j] = true;
            }
        }

        for (int i = 0; i < s.length(); i++) {
            for (int j = 0; j < p.length(); j++) {
                if (s.charAt(i) == p.charAt(j) || p.charAt(j) == '?') {
                    dp[i+1][j+1] = dp[i][j];
                } else if (p.charAt(j) == '*') {
                    dp[i+1][j+1] = dp[i+1][j] || dp[i][j+1];
                } else {
                    dp[i][j] = false;
                }
            }
        }

        return dp[s.length()][p.length()];
    }
}

解法二,使用指针:

使用两个指针sIdxpIdx分别跟踪sp,分类讨论所有情况:

  • 严格的单字对应,或p中出现?,两个指针分别前移
  • p遇到*,暂不清楚匹配情况,记录s当前的匹配起点和p最近一个*的位置,前移pIdx
  • 前一个pIdx*,但现在不是,可能对应着匹配多个字符的情况,将pIdx置于最近一个*的右侧,并前移sIdx

其他情况下,直接判定为匹配失败。

class Solution {
    public boolean isMatch(String s, String p) {
        int sIdx = 0, pIdx = 0, match = 0, starIdx = -1;            
        
        while (sIdx < s.length()) {
            if (pIdx < p.length() && (p.charAt(pIdx) == '?' || s.charAt(sIdx) == p.charAt(pIdx))) {
                // regular match or match ?, moving both pointers forward
                sIdx++;
                pIdx++;
            } else if (pIdx < p.length() && p.charAt(pIdx) == '*') {
                // * found, only moving pattern pointer forward
                starIdx = pIdx; // update the latest *'s index
                match = sIdx;
                pIdx++;
            } else if (starIdx != -1) {
                // last pattern pointer was * but current is not, moving string pointer forward to match
                pIdx = starIdx + 1;
                sIdx = ++match;
            } else return false;
        }

        // check for remaining characters in pattern
        while (pIdx < p.length() && p.charAt(pIdx) == '*') {
            pIdx++;
        }
        
        return pIdx == p.length();
    }
}

45. Jump Game II

https://leetcode.com/problems/jump-game-ii/


此题需要使用贪心算法。第i次跳跃中,必然有nums[i]个可选落点,检查每一个可选落点最远可以到达何处,选取最远的一个作为下一次跳跃的最远边界。以此类推,每一步都选择下一步可能最远的落点,直到最终可以到达终点。

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

        int steps = 0; // steps taken 
        int start = 0, end = 0; // in current step, I can reach the interval [start, end]
        int maxEnd = 0; // the farthest fall of the jump
        
        while (end < nums.length - 1) {
            steps++;

            // find out the maxEnd
            for (int i = start; i <= end; i++) {
                maxEnd = Math.max(maxEnd, nums[i] + i);
            }
                
            // generate next [start, end] range
            start = end + 1; // next range's start must be larger than current range's end
            end = maxEnd;
        }
        
        return steps;
    }
}

46. Permutations

https://leetcode.com/problems/permutations/


使用通用的回溯算法解题模版。使用一个boolean数组跟踪每个下标的数字是否使用过,跳过已经使用过的数;在回溯的时候重新设为未使用。

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(nums);
        backtrack(result, nums, new ArrayList<>(), new boolean[nums.length]);
        return result;
    }

    private void backtrack(List<List<Integer>> result, int[] nums, List<Integer> temp, boolean[] used) {
        // match with num's length, add this permutation to result
        if (temp.size() == nums.length) {
            result.add(new ArrayList<>(temp));
            return;
        }
        
        for (int i = 0; i < nums.length; i++) {
            // current number has already been used, skip
            if (used[i] == true) {
                continue;
            }

            // set current number as used and add it to temp permutation
            used[i] = true;
            temp.add(nums[i]);

            backtrack(result, nums, temp, used);

            // after using, reset current number as unused and remove it from temp permutation
            used[i] = false;
            temp.remove(temp.size() - 1);
        }
    }
}

47. Permutations II

https://leetcode.com/problems/permutations-ii/


此题的输入数组可能含有重复的数字,只需要在上一题的基础上,跳过重复的数字即可。

需要注意,判断重复时,used[i - 1]!used[i - 1]都能让代码成功运行,然而LeetCode运行时间显示,!used[i - 1]快的多,可能是因为LeetCode生成的随机测试数据中,包含重复的情况较多,因此我们发现当前一个相同的数字被使用时,我们也优先加入当前这个相同的数字,这样能更快试出所有permutation。

class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        Arrays.sort(nums);
        backtrack(result, nums, new ArrayList<>(), new boolean[nums.length]);
        return result;
    }

    private void backtrack(List<List<Integer>> result, int[] nums, List<Integer> temp, boolean[] used) {
        // match with num's length, add this permutation to result
        if (temp.size() == nums.length) {
            result.add( new ArrayList<>(temp) );
            return;
        }

        for (int i = 0; i < nums.length; i++) {
            // current number is used / if previous identical number is used, use current number
            if (used[i] || (i > 0 && nums[i] == nums[i-1] && !used[i-1])) {
                continue;
            }

            // mark current number as used and add it to temp permutation
            used[i] = true;
            temp.add(nums[i]);

            backtrack(result, nums, temp, used);

            // after using, unmark current number as unused and remove it from temp permutation
            used[i] = false;
            temp.remove(temp.size() - 1);
        }
    }
}

48. Rotate Image

https://leetcode.com/problems/rotate-image/


观察发现,矩阵内的元素旋转实际上是在建构一个旋转矩形,矩形顶点的轮换导致了整个矩阵的变化。

例如对于例子中的两个矩阵,第一个矩阵中2, 4, 6, 8构成了一个旋转矩形,第二个矩阵中1, 10, 12, 13构成了一个旋转矩形。因此,对于任意一个matrix[i][j],总能找到另外三个矩阵坐标顶点与之构成一个旋转矩形,只需推出其数学表达式即可。

class Solution {
    public void rotate(int[][] matrix) {
        if (matrix == null) {
            return;
        }
        int len = matrix.length;

        // locate four corner points
        for (int i = 0; i < (len + 1) / 2; i++) {
            for (int j = 0; j < matrix[0].length / 2; j++) {
                int temp = matrix[i][j]; // first
                matrix[i][j] = matrix[len - 1 - j][i]; // second
                matrix[len - 1 - j][i] = matrix[len - 1 - i][len - 1 - j]; // third
                matrix[len - 1 - i][len - 1 - j] = matrix[j][len - 1 - i]; // fourth
                matrix[j][len - 1 - i] = temp;
            }
        }  
    }
}

49. Group Anagrams

https://leetcode.com/problems/group-anagrams/


统计每个单词中各个字母出现的次数,存到一个长度为26的数组中。利用这个数组生成对应的hash码(Arrays.hashCode()),具有相同hash码的字符串就可以被归类在一起。

如果字符串很短的情况下,也可以每一个字母映射一个质数,质数相乘作为hashcode,提升运行效率。

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        List<List<String>> result = new ArrayList<>(); 
        
        // <hash number, list index in result>
        Map<Integer, Integer> map = new HashMap<>();
        
        for (String s: strs) {
            int key = getKey(s);
            int resultIndex = map.getOrDefault(key, -1);
            
            List<String> list = null;
            if (resultIndex == -1) {
                list = new ArrayList<>();
                result.add(list);
                map.put(key, result.size() - 1);
            } else {
                list = result.get(resultIndex);
            }
            
            list.add(s);
        }
        return result;
    }
    
    // use count to generate hash number
    public int getKey(String s) {
        int[] map = new int[26];
        for (char c: s.toCharArray()) {
            map[c - 'a']++;
        }
        return Arrays.hashCode(map);
    }
}

如果字符串很短的情况下,也可以每一个字母映射一个质数,质数相乘作为hashcode,提升运行效率。

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        List<List<String>> result = new ArrayList<>();
        if (strs == null || strs.length == 0) {
            return result;
        }

        // top 26 positive primes
        int[] prime = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103};
        
        // <product, position in result>
        Map<Long, Integer> map = new HashMap<>();

        for (String str : strs) {
            // calculate current string's product
            long product = 1;
            for (char ch : str.toCharArray()) {
                product *= prime[ch - 'a'];
            }

            List<String> temp;
            
            // current product is already in the map, add this string to specific list of result
            if (map.containsKey(product)) {
                result.get(map.get(product)).add(str);
            } else {
                // create a new list, add current string, then add the list to result
                temp = new ArrayList<>();
                temp.add(str);
                result.add(temp);

                // record this new list's index
                map.put(product, result.size() - 1);
            }
        }
        
        return result;
    }
}

50. Pow(x, n)

https://leetcode.com/problems/powx-n/


实现幂函数的构造需要运用递归。然而在开始递归前,需要先排除一些输入的特殊情况。nx各有若干特殊情况:

  • 输入的x为1,或是n为0,或是x为-1且n为整数下界,返回1
  • 输入的x为0,或是x不为-1且n为整数下界,返回0
  • 输入的n为负数,利用xn = (1/x)-n这一特点,将n转化成正数。

完成上述处理后,分n的奇偶性进行递归调用。对于奇数,由于整数除法的舍入,需要额外乘一个x

public double myPow(double x, int n) {
    if (x == 1 || n == 0 || (n == Integer.MIN_VALUE && x == -1)) {
        return 1;
    }
    if (x == 0 || (n == Integer.MIN_VALUE && x != -1)) {
        return 0;
    }
    if (n < 0) {
        n = -n;
        x = 1/x;
    }
    // odd or even
    return (n % 2 == 0) ? myPow(x * x, n / 2) : x * myPow(x * x, n / 2);
}

51. N-Queens

https://leetcode.com/problems/n-queens/


运用深度优先搜素,使用一个数组queens记录每一行的皇后位于第几列。每一行放置皇后并递归进入下一行。如果某一列已经摆放了皇后,或对角线上存在皇后,则当前单元格不能放置皇后。

当深度优先搜索结束后,根据queens记录的信息,生成一张棋盘图。

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

        // queen's position in each row
        int[] queens = new int[n];
        Arrays.fill(queens, -1);

        dfs(result, queens, 0);
        return result;
    }

    private void dfs(List<List<String>> result, int[] queens, int row) {
        int len = queens.length;

        // finish one solution
        if (len == row) {
            List<String> temp = new ArrayList<>();
            
            // generate an n*n chessboard based on queens' positions
            for (int i : queens) {
                char[] boardRow = new char[len];
                Arrays.fill(boardRow, '.');
                boardRow[i] = 'Q';

                // char[] -> str
                temp.add(String.valueOf(boardRow));
            }
            
            // add this chessboard to result
            result.add(temp); 
            return;
        }

        // look for suitable column to place queen in current row
        for (int col = 0; col < len; col++) {
            if (isValid(queens, row, col)) {
                queens[row] = col; // put queen in current column
                dfs(result, queens, row + 1); // move to next row
            }
        }
    }

    private boolean isValid(int[] queens, int row, int col) {
        for (int i = 0; i < row; i++) {
            // there should be no queen in this row or diagnosal line
            if (queens[i] == col || Math.abs(col - queens[i]) == row - i) {
                return false;
            }
        }

        return true;
    }
}

52. N-Queens II

https://leetcode.com/problems/n-queens-ii/


与上一题完全一致,只是上一题返回整个结果列表,这一题返回结果的个数而已。

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

        // queen's position in each row
        int[] queens = new int[n];
        Arrays.fill(queens, -1);

        dfs(result, queens, 0);
        return result.size();
    }
    
    private void dfs(List<List<String>> result, int[] queens, int row) {
        int len = queens.length;

        // finish one solution
        if (len == row) {
            List<String> temp = new ArrayList<>();
            
            // generate an n*n chessboard based on queens' positions
            for (int i : queens) {
                char[] boardRow = new char[len];
                Arrays.fill(boardRow, '.');
                boardRow[i] = 'Q';

                // char[] -> str
                temp.add(String.valueOf(boardRow));
            }
            
            // add this chessboard to result
            result.add(temp); 
            return;
        }

        // look for suitable column to place queen in current row
        for (int col = 0; col < len; col++) {
            if (isValid(queens, row, col)) {
                queens[row] = col; // put queen in current column
                dfs(result, queens, row + 1); // move to next row
            }
        }
    }

    private boolean isValid(int[] queens, int row, int col) {
        for (int i = 0; i < row; i++) {
            // there should be no queen in this row or diagnosal line
            if (queens[i] == col || Math.abs(col - queens[i]) == row - i) {
                return false;
            }
        }

        return true;
    }
}

53. Maximum Subarray

https://leetcode.com/problems/maximum-subarray/


使用一个临时变量tempSum从首个元素开始跟踪当前的子序列和。只有确保子序列和大于0才继续累积;否则就从当前元素开始重新寻找新的最大子序列。

class Solution {
    public int maxSubArray(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        int len = nums.length;
        int result = nums[0], tempSum = nums[0];

        // if subarray's sum is larger than 0, add nums[i]; or, tempSum should be reset as nums[i]
        for (int i = 1; i < len; i++) {
            tempSum = Math.max(tempSum, 0) + nums[i];
            result = Math.max(result, tempSum);
        }
        return result;
    }
}

54. Spiral Matrix

https://leetcode.com/problems/spiral-matrix/


分别使用四个变量,储存目前输出行、列的两个边界值,保证输出发生在两个边界之内。每一圈输出都按照右、下、左、上的顺序进行,但要注意由于奇偶性关系,左和上的输出可能是不必要的(如果边界值已经重合了)。

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

        int left = 0, right = matrix[0].length - 1, up = 0, down = matrix.length - 1;
        while (left <= right && up <= down) {
            // right
            for (int j = left; j <= right; j++) {
                result.add(matrix[up][j]);
            }
            up++;

            // down
            for (int i = up; i <= down; i++) {
                result.add(matrix[i][right]);
            }
            right--;

            // left, only when up <= down
            if (up <= down) {
                for (int j = right; j >= left; j--) {
                    result.add(matrix[down][j]);
                }
                down--;
            }

            // up, only when left <= right
            if (left <= right) {
                for (int i = down; i >= up; i--) {
                    result.add(matrix[i][left]);
                }
                left++;
            }
        }
        
        return result;
    }
}

55. Jump Game

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


此题判断的是能否到达数组终点。仍然使用贪心算法,选取能达到最远位置的落点进行跳跃。

然而,落点可能永远无法落到终点或终点之后,此时下一次备选的落点区域边界startend都将无法更新,因此需要额外判断这两个落点的关系。

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

        int start = 0, end = 0; // in current step, I can reach the interval [start, end]
        int maxEnd = 0; // the farthest fall of the jump

        while (end < nums.length - 1) {
            // find out the maxEnd
            for (int i = start; i <= end; i++) {
                maxEnd = Math.max(maxEnd, nums[i] + i);
            }
                
            // generate next [start, end] range
            start = end + 1; // next range's start must be larger than current range's end
            end = maxEnd;

            // this indicates that end does not change, so we cannot go further
            if (start == end + 1) {
                return false;
            }
        }
        return true;
    }
}

56. Merge Intervals

https://leetcode.com/problems/merge-intervals/


将输入的数组元素按区间起始值从小到大排序。

  • 倘若如果出现了重叠,则更新前一个区间的end值。相当于间接合并了区间,把两个区间看作一个整体
  • 没有重叠,将当前区间加入结果集合中。这个区间同时也是当前遍历到的最后一个有效区间
class Solution {
    public int[][] merge(int[][] intervals) {
        if (intervals == null || intervals.length < 2) {
            return intervals;
        }

        // sort by interval start
        Arrays.sort(intervals, (int[] interval1, int[] interval2) -> {
            return interval1[0] - interval2[0];
        });

        List<int[]> result = new ArrayList<>();
        int[] prev = intervals[0];
        result.add(prev);

        for (int[] current : intervals) {
            if (current[0] <= prev[1]) {
                // Overlapping intervals, update previous interval's end
                prev[1] = Math.max(prev[1], current[1]);
            } else {
                // No overlapping, add current to result
                prev = current;
                result.add(current);
            }
        }
        
        // convert list to array
        return result.toArray(new int[result.size()][2]);
    }
}

57. Insert Interval

https://leetcode.com/problems/insert-interval/


  1. 添加肯定位于newInterval之前的区间
  2. 针对所有和newInterval有重叠的区间,计算出最小的起点和最大的终点,直接合并
  3. 添加合并后的大区间
  4. 添加肯定位于newInterval之后的区间
class Solution {
    public int[][] insert(int[][] intervals, int[] newInterval) {
        if (intervals == null || intervals.length == 0) {
            return new int[][] {
                { newInterval[0], newInterval[1] }
            };
        }

        List<int[]> result = new ArrayList<>();
        int i = 0, start = newInterval[0], end = newInterval[1];

        // intervals prior to newInterval
        while (i < intervals.length && intervals[i][1] < start) {
            result.add(intervals[i]);
            i++;
        }
        
        // for all overlapping intervals, find the minimum start and maximum end
        while (i < intervals.length && intervals[i][0] <= end) {
            start = Math.min(start, intervals[i][0]);
            end = Math.max(end, intervals[i][1]);
            i++;
        }
        
        // assume all overlapping intervals are merged in one big interval [start, end]
        result.add(new int[]{start, end});
        
        // add the rest intervals
        while (i < intervals.length) {
            result.add(intervals[i++]);
        }

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

58. Length of Last Word

https://leetcode.com/problems/length-of-last-word/


先排除从最右侧开始含有若干空白字符的情况;之后从第一个非空白的字符开始向左遍历,直至遇到下一个空格为止,统计中间经过了多少个字符。

class Solution {
    public int lengthOfLastWord(String s) {
        if (s == null || s.length() == 0) {
            return 0;
        }
        int result = 0, index = s.length() - 1;

        // trim all trailing 0
        while (s.charAt(index) == ' ') {
            index--;
            if (index < 0) {
                return 0;   
            }
        }
        
        // calculate last word's length
        for (int i = index; i >= 0; i--) {
            if (s.charAt(i) == ' ') {
                return result;
            }
            result++;
        }
        
        return result;
    }
}

59. Spiral Matrix II

https://leetcode.com/problems/spiral-matrix-ii/


提前分配好二维数组空间,螺旋遍历逐个将数字塞入即可。由于是n * n矩阵,因此不用考虑矩阵长宽不一致的情况。

class Solution {
    public int[][] generateMatrix(int n) {
        int[][] result = new int[n][n];
        
        // current number to be added
        int current = 1;

        int up = 0, down = n - 1, left = 0, right = n - 1;

        while (left <= right) {
            // right
            for (int j = left; j <= right; j++) {
                result[up][j] = current++;
            }
            up++;

            // down
            for (int i = up; i <= down; i++) {
                result[i][right] = current++;
            }
            right--;

            // left
            for (int j = right; j >= left; j--) {
                result[down][j] = current++;
            }
            down--;

            // up
            for (int i = down; i >= up; i--) {
                result[i][left] = current++;
            }
            left++;
        }
        
        return result;
    }
}

60. Permutation Sequence

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


由于题目限定输入的n在1-9之内,因此最快速的方法是提前将1-9的阶乘算好存在数组中。

从1开始遍历到n,每一步我们都可以算出固定当前位的前提下,后续共有多少种组合情况;因此我们可以用k / currMaxFactorial得到当前位需要向上增加多少offset才可以快速逼近第k种组合。

class Solution {
    public String getPermutation(int n, int k) {
        int[] factorials = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};
        StringBuilder sb = new StringBuilder();
        boolean[] used = new boolean[10];
        
        // minus starting k by 1 to get correct offset, otherwise we will exactly the (k+1) th permutation
        k--;

        for (int i = 1; i <= n; i++) {
            // for current i and n, this is the max factorial threshold
            int currMaxFactorial = factorials[n - i];
            
            // the diff between current digit (0) and ideal digit
            // if we fix current digit, we get currMaxFactorial permutations for subsequent digits
            int offset = k / currMaxFactorial;
            
            // find the digit to append
            int digit = 0;
            int count = 0;
            while (count <= offset) {
                digit++;
                if (!used[digit]) {
                    count++;
                }
            }
            
            // append current digit and mark as used
            sb.append(digit);
            used[digit] = true;

            // we have covered the first currMaxFactorial permutations, so update k and cover the rest
            k %= currMaxFactorial;
        }
        
        return sb.toString();
    }
}

61. Rotate List

https://leetcode.com/problems/rotate-list/


  1. 获取链表的总长度,并将最后一个元素指向头元素,该过程遍历一次链表可完成
  2. 计算出指针实际移动的距离。尽管k的值可能大于链表长度,但是取模后即可解决这个问题
  3. 返回新的head,新的结尾将指向NULL
/**
 * 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 rotateRight(ListNode head, int k) {
        if (head == null || k == 0) {
            return head;
        }

        // get list length and set the last node's next to head
        int length = 1;
        ListNode cur = head;
        while (cur.next != null) {
            length++;
            cur = cur.next;
        }
        cur.next = head;

        // actual move length
        k %= length;
        k++;
        int move = length - k;

        // find new head
        cur = head;
        while (move > 0) {
            cur = cur.next;
            move--;
        }
        ListNode newHead = cur.next;
        cur.next = null;
        
        return newHead;
    }
}

62. Unique Paths

https://leetcode.com/problems/unique-paths/


使用动态规划,记录到达每一个方格的路径总数,不断递推到出口。

二维空间下,很容易发现动态规划递推式为:

dp[i][j] = dp[i - 1][j] + dp[i][j - 1];

dp[i][j]依赖的两个值分别是dp[i - 1][j](对应上一行的同一列)和dp[i][j - 1](对应同一行的上一列)。因此我们可以进行空间的压缩:

cur[j] = pre[j] + cur[j - 1]

// pre is last line
// cur is current line

在更新cur[j]之前,实际上pre[j]cur[j]的值就是相等的,因为上一行时的cur自然就是这一行pre。所以可以得到:

cur[j] = cur[j] + cur[j - 1]

// or

dp[j] += dp[j - 1]

因此最终我们使用一维动态数组降低空间复杂度。

class Solution {
    public int uniquePaths(int m, int n) {
        if (m < 1 || n < 1) {
            return 0;
        }

        int[] dp = new int[n];
        dp[0] = 1;

        // row by row and accumulate one by one
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (j > 0) {
                    dp[j] += dp[j-1];
                }
            }
        }

        return dp[n-1];
    }
}

63. Unique Paths II

https://leetcode.com/problems/unique-paths-ii/


仍然使用一维数组动态规划;遇到障碍物时,该列在动态规划中的结果重置为0。

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        if (obstacleGrid == null || obstacleGrid.length == 0 || obstacleGrid[0].length == 0) {
            return 0;
        }
        int m = obstacleGrid.length, n = obstacleGrid[0].length;

        int[] dp = new int[n];
        dp[0] = 1;

        // row by row and accumulate one by one
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                // when obstacle encountered, this column's result is reset to 0
                if (obstacleGrid[i][j] == 1) {
                    dp[j] = 0;
                } else if (j > 0) {
                    dp[j] += dp[j-1];
                }
            }
        }

        return dp[n-1];
    }
}

64. Minimum Path Sum

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


对于左边界和右边界上的点,只存在一条道走到黑的情况;对于不在左边界/上边界的点,只需要比较其上方和左方的单元格中哪一格路径更小,即可不断迭代出结果。

class Solution {
    public int minPathSum(int[][] grid) {
        if (grid == null || grid.length == 0 || grid[0].length == 0) {
            return 0;
        }
        
        int m = grid.length, n = grid[0].length;

        int[][] dp = new int[m][n];
        dp[0][0] = grid[0][0];

        // fill first row and first column
        for (int j = 1; j < n; j++) {
            dp[0][j] = grid[0][j] + dp[0][j-1];
        }
        for (int i = 1; i < m; i++) {
            dp[i][0] = grid[i][0] + dp[i-1][0];
        }

        // for other grids, choose the smaller value of up or left grid's value
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = grid[i][j] + Math.min(dp[i-1][j], dp[i][j-1]);
            }
        }

        return dp[m-1][n-1];
    }
}

65. Valid Number

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


正常情况下,出现数字时默认算作valid number,但考虑一些特殊情况:

  • 出现小数点时,小数点前不能有e(或者E),且小数点不能超过一个
  • 出现e(或者E)时,e不能超过1个,且e前必须有数字
  • 出现正负号时,正负号必须为首字符或紧跟在e之后
class Solution {
    public boolean isNumber(String s) {
        s = s.trim(); // trim all leading blank space

        boolean pointSeen = false;
        boolean eSeen = false;
        boolean isNumber = false;

        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);

            if (ch >= '0' && ch <= '9') {
                // a valid digit
                isNumber = true;
            } else if (ch == '.') {
                // if there is already a 'e', or there is more than one '.'
                if (eSeen || pointSeen) {
                    return false;
                }
                
                pointSeen = true;
            } else if (ch == 'e' || ch == 'E') {
                // if there is more than one 'e', or there is no digit before 'e'
                if (eSeen || !isNumber) {
                    return false;
                }
                
                eSeen = true;
                
                // need more valid digits
                isNumber = false; 
            } else if (ch == '-' || ch == '+') {
                // sign should be first char or part of exponent
                if (i != 0 && s.charAt(i - 1) != 'e') {
                    return false;
                }
            } else {
                // other characters are invalid
                return false;
            }
        }
        
        return isNumber;
    }
}

66. Plus One

https://leetcode.com/problems/plus-one/


只有最后一位数为9时,才会有进位可能,其他时候直接+1出结果。如果每一位数都是9,则位数多了一位,直接开辟一个新数组,首位为1即可。

class Solution {
    public int[] plusOne(int[] digits) {
        int len = digits.length;
        
        for (int i = len - 1; i >= 0; i--) {
            if (digits[i] < 9) {
                digits[i]++;
                return digits;
            }
            
            // update current digit as 0
            digits[i] = 0;
        }

        // previous number is 999....999, should update to 1000...000
        int[] result = new int[len+1];
        for (int i = 0; i < result.length; i++) {
            result[i] = i == 0 ? 1 : 0;
        }
        return result;
    }
}

67. Add Binary

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


两个指针分别跟踪a, b,可以避开比较a, b长度的麻烦。

另一个技巧是倒序添加每一位数,最后输出时再颠倒能节省时间,原因是StringBuilder的append()操作比insert()操作快很多。

class Solution {
    public String addBinary(String a, String b) {
        StringBuilder sb = new StringBuilder();
        
        // two pointers will traverse a, b from end to begin
        int i = a.length() - 1, j = b.length() - 1;
        
        // carry and sum
        int carry = 0, sum = 0;

        while (i >= 0 || j >= 0) {
            sum = carry;

            // add from the least significant bit to the most significant bit
            if (i >= 0) {
                sum += a.charAt(i) - '0';
                i--;
            }
            if (j >= 0) {
                sum += b.charAt(j) - '0';
                j--;
            }
            
            sb.append(sum % 2);
            carry = sum / 2;
        }
        
        // add carry to the back
        if (carry != 0) {
            sb.append(carry);
        }

        // reverse to get final sum
        return sb.reverse().toString();
    }
}

68. Text Justification

https://leetcode.com/problems/text-justification/


遍历输入的每一个单词,在将一个单词加入到当前行时,需要判断:

  • 当前行未满,可以继续添加单词,即原有单词长度 + 原有单词之间的间隙 + 当前单词长度之和仍然小于maxWidth
  • 当前行已满,则先进行处理。如果只有一个单词,则直接左对齐,在单词右侧加空格;否则,计算出每一个单词之间的间隙。这里使用的是除数取模方法,使得空格前宽后窄。处理之后,当前行清空,再加入新的单词
  • 最后一行需要单独考虑,因为我们只有在一行的长度超出范围时才会做对齐处理,因此当遍历到words的最后一个单词时,最后一行已有的长度可能还未超过maxWidth。我们需要在循环结束后将最后一行提取出来,左对齐处理。
class Solution {
    public List<String> fullJustify(String[] words, int maxWidth) {
        List<String> result = new ArrayList<>();
        List<String> line = new ArrayList<>();
        if (words.length == 0) {
            return result;
        }

        int currentLen = 0;
        for (int i = 0; i < words.length; i++) {
            // determine if we can add another word to current line
            // previous words length + current word length + at least one space between every 2 words
            if (currentLen + words[i].length() + line.size() <= maxWidth) {
                line.add(words[i]);
                currentLen += words[i].length();
            } else {
                if (line.size() == 1) {
                    // current line is full and only has 1 word, left align and add space to the right
                    StringBuilder row = new StringBuilder(line.get(0));
                    currentLen = row.length();
                    for (int j = 1; j <= maxWidth - currentLen; j++) {
                        row.append(' ');
                    }
                    result.add(row.toString());
                } else {
                    // current line is full and has multiple words 
                    // first 'mod' words: 'div' + 1 spaces
                    // other words: 'div' spaces
                    int div = (maxWidth - currentLen) / (line.size() - 1);
                    int mod = (maxWidth - currentLen) % (line.size() - 1);

                    StringBuilder row = new StringBuilder(line.get(0));
                    for (int j = 1; j < line.size(); j++) {
                        if (j <= mod) {
                            for (int k = 0; k < div + 1; k++) {
                                row.append(' ');
                            }
                        } else {
                            for (int k = 0; k < div; k++) {
                                row.append(' ');
                            }
                        }
                            
                        row.append(line.get(j));
                    }
                    
                    result.add(row.toString());
                }
                
                // clear current line and add new word
                line.clear();
                line.add(words[i]);
                currentLen = words[i].length();
            }
        }

        // handle the final row
        // left align and separated with blank space
        StringBuilder finalRow = new StringBuilder(line.get(0));
        for (int i = 1; i < line.size(); i++) {
            finalRow.append(" " + line.get(i));
        }
        currentLen = finalRow.length();
        
        // add trailing blanks
        for (int i = 1; i <= maxWidth - currentLen; i++) {
            finalRow.append(' ');
        }
        
        result.add(finalRow.toString());
        
        return result;
    }
}

69. Sqrt(x)

https://leetcode.com/problems/sqrtx/


问题转化为二分查找,即给定0到x的数组,找到sqrt(x)或者是小于sqrt(x)的最大整数。

如果sqrt(x)存在的话,将其找出;如果不存在的话,由于最后left > right,因此right是最接近且小于sqrt(x)的整数。

另外注意,这题需要考虑到整数溢出情况,因此可以在二分查找过程开始前将int转为long。

class Solution {
    public int mySqrt(int x) {
        long left = 0, right = x, target = x;
        
        while (left <= right) {
            long mid = left + (right - left) / 2;
            if (mid * mid == target) {
                return (int)mid;
            } else if (mid * mid < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        
        return (int)right;
    }
}

70. Climbing Stairs

https://leetcode.com/problems/climbing-stairs/


使用动态规划解决,到达第i层阶梯之前,要么到达第i - 2层阶梯之后爬两层,要么到达第i - 1层阶梯之后爬一层。注意先到i - 2层再到i - 1层的情况是包含在到达第i - 1层的情况里的。

class Solution {
    public int climbStairs(int n) {
        if (n == 1 || n == 2) {
            return n;
        }
        
        // count of distinct ways to climb to the 0 ~ n-th steps
        int[] dp = new int[n+1];
        dp[1] = 1;
        dp[2] = 2;
        
        // to reach the i-th step, we could reach the (i - 2)th step, then climb 2 steps
        // or reach the (i - 1)th step, then climb 1 step
        for (int i = 3; i <= n; i++) {
            dp[i] = dp[i-1] + dp[i-2];
        }
        
        return dp[n];
    }
}

71. Simplify Path

https://leetcode.com/problems/simplify-path/


利用String.split()函数,提取出每个实际的文件夹名;利用栈进行判断,只有遇到..且栈不空时需要弹出栈顶元素;其余字符串,如果不是.., .' ',均直接加入栈中即可。输出时,使用集合遍历,从栈底至栈顶正向输出。

class Solution {
    public String simplifyPath(String path) {
        Stack<String> stack = new Stack<>();
        
        for (String dir : path.split("/")) {
            // back to upper directory
            if (dir.equals("..") && !stack.isEmpty()) {
                stack.pop();
            } else if (!dir.equals("..") && !dir.equals(".") && !dir.equals("")) {
                // lower directory
                stack.push(dir);
            }
        }

        // format path
        String result = "";
        for (String dir : stack) {
            result += "/" + dir;
        }
        
        return result.isEmpty() ? "/" : result;
    }
}

72. Edit Distance

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


使用动态规划,创建一个二维数组result,其中result[i][j]表示将word1的前i个字母转化为word2的前j个字母所需要的最小步数。

比较word1的第i个字母和word2的第j个字母,无非两种情况:

  • 二者相等,不需要变化操作,即result[i+1][j+1] = result[i][j]
  • 二者不相等,则选择插入、删除、替换中的一种,分别对应result[i+1][j], result[i][j+1]result[i][j],取最小的值
class Solution {
    public int minDistance(String word1, String word2) {
        int m = word1.length(), n = word2.length();
        if (m == 0 || n == 0) {
            return m+n;
        }
        
        int[][] result = new int[m+1][n+1];
        for (int i = 0; i <= m; i++) {
            result[i][0] = i;
        }
        for (int j = 0; j <= n; j++) {
            result[0][j] = j;
        }

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                // i-th word of word1 is equal to j-th word of word2, no need to change
                if (word1.charAt(i) == word2.charAt(j)) {
                    result[i+1][j+1] = result[i][j];
                } else {
                    int insert = result[i+1][j];
                    int delete = result[i][j+1];
                    int replace = result[i][j];
                    // select the operation with minimum cost
                    result[i+1][j+1] = 1 + Math.min(insert, Math.min(delete, replace));
                }
            }
        }
        
        return result[m][n];
    }
}

73. Set Matrix Zeroes

https://leetcode.com/problems/set-matrix-zeroes/


此题的难点在于需要使用O(1)的空间复杂度。一个思路是,先对数组进行一次从上到下的遍历。如果遇到了0,则将当前行、列的首个元素均置为0。完成后,第二次遍历从下到上,如果遇到行首或列首为0,则将当前元素置为0。

一个特殊情况是,数组首个元素既是第一行的行首,又是第一列的列首,产生了冲突。因此,需要将第一列的状态单独标记。

class Solution {
    public void setZeroes(int[][] matrix) {
        if (matrix.length == 0 || matrix[0].length == 0) {
            return;
        }

        int row = matrix.length, col = matrix[0].length;
        
        // first element of the first column
        int col0 = 1;

        // top-down travel, when encounter 0, set first element of this row & column to 0
        for (int i = 0; i < row; i++) {
            // the first column should be set as 0 later
            if (matrix[i][0] == 0) {
                col0 = 0;
            }
            
            for (int j = 1; j < col; j++) {
                if (matrix[i][j] == 0) {
                    matrix[i][0] = matrix[0][j] = 0;
                }
            }   
        }

        // bottom-up travel, if first element of this row / column is 0, set current element to 0
        for (int i = row - 1; i >= 0; i--) {
            for (int j = col - 1; j >= 1; j--) {
                if (matrix[i][0] == 0 || matrix[0][j] == 0) {
                    matrix[i][j] = 0;
                }
            }
    
            if (col0 == 0) {
                matrix[i][0] = 0;
            }
        }
    }
}

74. Search a 2D Matrix

https://leetcode.com/problems/search-a-2d-matrix/


将排好序的二维数组看成一个连续的一维数组,就可以应用二分查找。

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        if (matrix.length == 0 || matrix[0].length == 0) {
            return false;
        }
        int m = matrix.length, n = matrix[0].length;

        // treat the 2D arrays as a big sorted 1D array
        int left = 0, right = m * n - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            
            if (matrix[mid / n][mid % n] == target) {
                return true;
            } else if (matrix[mid / n][mid % n] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }

        return false;
    }
}

75. Sort Colors

https://leetcode.com/problems/sort-colors/


默认将0放到当前能放到的最左下标,2放到当前能放到的最右下标,并使用两个指针分别跟踪这两个位置。

遍历数组,遇到0或2时,两个指针指向的元素进行交换。需要注意,如果遇到2的时候,下一次仍然是在当前位置继续遍历(因为不知道换过来的元素是什么),因此i需要递减1退回到当前下标。

class Solution {
    public void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

    public void sortColors(int[] nums) {
        if (nums.length == 0) {
            return;
        }
        int index_0 = 0, index_2 = nums.length - 1;

        for (int i = 0; i <= index_2; i++) {
            if (nums[i] == 0) {
                swap(nums, i, index_0); 
                index_0++;
            } else if (nums[i] == 2) {
                swap(nums, i, index_2);
                index_2--;
                
                // decrement by 1 as we don't know what we have swapped
                i--; 
            }
        }
    }
}

76. Minimum Window Substring

https://leetcode.com/problems/minimum-window-substring/


使用两个map:

  • smap,统计s中每个字符出现的次数
  • tmap,统计t中每个字符出现的次数

解题步骤:

  1. 统计t中每个字符出现的次数到tmap中,并同时统计t中总共有多少个不同的字符
  2. 双指针法遍历s,每当遇到一个在t中出现过的字符,就相应更新smap中的字符出现次数
  3. 如果某个字符在smap中的出现次数与tmap相等,说明目前的子字符串s[left, right]中已经包含了足够多的该字符。如果全部字符都满足了这个条件,就说明当前的子字符串是一个符合题意的sliding window,看这个window的长度是否是已知最小的
  4. 在保证sliding window仍旧合法的前提下,尝试从头开始缩减长度,即:如果当前的字符s[left]smap中的次数比tmap中多,就删掉一个s[left],并且向后移动left指针。一直重复这个过程,直到某次完成删除后,sliding window不再合法
  5. 完成遍历后,根据此前统计的最小值信息截取子字符串,得到结果;而如果发现最短长度并没有更新过,说明不存在这样的sliding window
class Solution {
    public String minWindow(String s, String t) {
        if (s == null || t == null || s.length() == 0 || t.length() == 0 || s.length() < t.length()) {
            return "";
        }

        // count how many times each character appears in t
        int[] tmap = new int[256];
        for (int i = 0; i < 256; i++) {
            tmap[i] = Integer.MIN_VALUE;
        }
        
        // unique characters count
        int unique = 0; 
        for (char c : t.toCharArray()) {
            if (tmap[c] == Integer.MIN_VALUE) {
                unique++;
                tmap[c] = 1;
            } else {
                tmap[c]++;
            }
        }

        // count how many times each character appears in s
        int[] smap = new int[256];
        for (int i = 0; i < 256; i++) {
            smap[i] = 0;
        }

        // current beginning index of sliding window
        int left = 0;
        
        // the beginning index & length of the minimum sliding window
        int minLeft = 0, minLen = Integer.MAX_VALUE;
        
        // how many characters have been completely matched
        int count = 0;

        for (int right = 0; right < s.length(); right++) {
            if (tmap[s.charAt(right)] != Integer.MIN_VALUE) {
                smap[s.charAt(right)]++;
                
                if (smap[s.charAt(right)] == tmap[s.charAt(right)]) {
                    count++;
                }

                // contains all characters in t, try to decrease the size of the sliding window
                while (count == unique) {
                    // record current starting index and minimum length
                    if (right - left + 1 < minLen) {
                        minLeft = left;
                        minLen = right - left + 1;
                    }

                    // delete extra characters
                    if (smap[s.charAt(left)] >= tmap[s.charAt(left)]) {
                        smap[s.charAt(left)]--;
                        
                        if (smap[s.charAt(left)] < tmap[s.charAt(left)]) {
                            count--;
                        }
                    }
                    
                    left++;
                }
            }
        }

        return minLen == Integer.MAX_VALUE ? "" : s.substring(minLeft, minLeft + minLen);
    }
}

77. Combinations

https://leetcode.com/problems/combinations/


回溯算法,注意每一轮选数之后k值的递减更新,以及起始数startNum的范围。

class Solution {
    public List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> result = new ArrayList<>();
        if (n < k) {
            return result;
        }
        
        // the range is 1 ... n, so start from 1
        backtrack(result, new ArrayList<>(), n, k, 1);
        return result;
    }

    private void backtrack(List<List<Integer>> result, List<Integer> temp, int n, int k, int startNum) {
        // deep copy then add to result
        if (k == 0) {
            result.add(new ArrayList<>(temp));
            return;
        }
        
        // startNum should be no larger than n - k + 1, as [n - k + 1, n] will contain exactly k numbers
        for (int i = startNum; i <= n - k + 1; i++) {
            temp.add(i);
            
            // k - 1 remaining numbers; current index increment by 1
            backtrack(result, temp, n, k - 1, i + 1);
            
            temp.remove(temp.size() - 1);
        }
    }
}

78. Subsets

https://leetcode.com/problems/subsets/


深度优先,回溯算法穷举添加每个数到临时list中,每个中间结果都是一个子集。

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(nums);
        backtrack(result, new ArrayList<>(), nums, 0);
        return result;
    }

    private void backtrack(List<List<Integer>> result, List<Integer> temp, int[] nums, int index) {
        // every trial temp list is a subset
        result.add(new ArrayList<>(temp));

        for (int i = index; i < nums.length; i++) {
            temp.add(nums[i]);
            
            // after trying current number, we should try subsequent numbers, so increment i by 1
            backtrack(result, temp, nums, i + 1);
            
            temp.remove(temp.size() - 1);
        }
    }
}

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


深度优先搜索与回溯算法的结合。对每一单元格,向四个方向的邻近单元格延展,如果超出数组边界或与word当前长度上的字符不匹配就返回false。访问过的单元格用位掩码做标记,避免走回头路,回溯之后复原。

class Solution {
    public boolean exist(char[][] board, String word) {
        if (board.length == 0 || board[0].length == 0 || word.length() == 0) {
            return false;
        }

        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if (isMatch(board, i, j, word, 0)) {
                    return true;
                }
            }
        }

        return false;
    }

    private boolean isMatch(char[][] board, int x, int y, String word, int len) {
        // the length meets requirement, and the last character is matched in last recursion
        if (len == word.length()) {
            return true; 
        }
        
        // out of bound
        if (x < 0 || x == board.length || y < 0 || y == board[0].length) {
            return false; 
        }
        
        // current character is unmatched, fail
        if (board[x][y] != word.charAt(len)) {
            return false; 
        }

        board[x][y] ^= 256; // mask current character

        // next character may appear in up, down, left, right adjacent grid
        boolean result = isMatch(board, x + 1, y, word, len + 1) ||
                         isMatch(board, x - 1, y, word, len + 1) ||
                         isMatch(board, x, y + 1, word, len + 1) ||
                         isMatch(board, x, y - 1, word, len + 1);
        
        board[x][y] ^= 256; // restore
        
        return result;
    }
}

80. Remove Duplicates from Sorted Array II

https://leetcode.com/problems/remove-duplicates-from-sorted-array-ii/


遍历数组,前两个数不变,照常赋值;自第三个数开始,如果当前赋值的位置是index,必须保证当前数比nums[index - 2]更大时才覆盖值。中间间隔的一个位置留给重复的可能。

class Solution {
    public int removeDuplicates(int[] nums) {
        if (nums == null || nums.length < 1) {
            return 0;
        }
        
        int index = 0;
        for (int num : nums) {
            // we have at most twice appearance for each unique element
            // so num should be larger than nums[index - 2] and nums[index - 1]
            if (index < 2 || num > nums[index - 2]) {
                nums[index] = num;
                index++;
            }
        }

        return index;
    }
}

81. Search in Rotated Sorted Array II

https://leetcode.com/problems/search-in-rotated-sorted-array-ii/


这一题的输入数组可能出现重复数字,因此可能出现难以判断某侧有序的情况(例如nums[left], nums[mid], nums[right]相等),这个时候无法应用二分查找缩小一半的搜索范围,只能左移right指针,继续循环。很显然,最坏的情况下算法的时间复杂度会退化到O(n)

class Solution {
    public boolean search(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return false;
        }
        
        int left = 0, right = nums.length - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return true;
            }

            if (nums[left] < nums[mid] || nums[right] < nums[mid]) {
                
                // nums[mid] is a peak element
                // possible comparison: nums[left] <= target < nums[mid], 
                if (target >= nums[left] && target < nums[mid]) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            } else if (nums[mid] < nums[right] || nums[mid] < nums[left]) {
                
                // nums[mid] is a bottom element
                // possible comparison: nums[mid] < target <= nums[right]
                if (target > nums[mid] && target <= nums[right]) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            } else {
                // nums[left] == nums[mid] == nums[right], we cannot decide which half is ordered
                right--;
            }
        }
        
        return false;
    }
}

82. Remove Duplicates from Sorted List II

https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/


用一个指针cur指向当前最后一个非重复的数字;当遇到重复的数字时,使用一个while循环跳过所有的重复值。

/**
 * 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 deleteDuplicates(ListNode head) {
        if (head == null) {
            return head;
        }

        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        
        // the last non-duplicate
        ListNode cur = dummyHead;

        while (head != null) {
            // head val is not same as the next one
            if (head.next == null || head.val != head.next.val) {
                cur = head;
                head = head.next;
            } else {
                int val = head.val;

                // move head forward and skip all duplicates
                while (head != null && head.val == val) {
                    head = head.next;
                }
                cur.next = head; 
            }
        }

        return dummyHead.next;
    }
}

83. Remove Duplicates from Sorted List

https://leetcode.com/problems/remove-duplicates-from-sorted-list/


同时比较当前指针对应元素以及当前指针之后的下一个元素,比较它们是否相同。如果相同就直接跳过下一个元素。

/**
 * 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 deleteDuplicates(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }

        ListNode result = head;
        while (head != null) {
            // current number is same as the next one, skip the next node
            if (head.next != null && head.val == head.next.val) {
                head.next = head.next.next;
            } else {
                head = head.next;
            }
        }

        return result;
    }
}

84. Largest Rectangle in Histogram

https://leetcode.com/problems/largest-rectangle-in-histogram/


把问题细化到求height[i, j]区间上的最大矩形面积。排除掉i == j或者i > j的特殊情况后,再进行分类讨论:

  • height[i, j]区间上始终递减,那么就从右向左遍历k,检查heights[k] * (k - i + 1)的值
  • height[i, j]区间上始终递增,那么就从左向右遍历k,检查heights[k] * (j - k + 1)的值
  • 非单调区间,无法确定最大矩形的范围(是否包含极大值),因此只能以最矮坐标点为界,先得到一个全局面积,再分治求出左右区间的最大值,分别和全局值进行比较
class Solution {
    public int largestRectangleArea(int[] heights) {
        return compute(heights, 0, heights.length - 1);
    }

    private int compute(int[] heights, int i, int j) {
        int result = 0;
        if (j < i) {
            result = 0;
        } else if (i == j) {
            result = heights[i];
        } else {
            // assume min height is on the index i
            int minHeightIndex = i;

            // how many adjacent increment / decrement pairs in [i, j]
            int increments = 0;
            int decrements = 0;
            for (int k = i + 1; k <= j; k++) {
                if (heights[minHeightIndex] > heights[k]) {
                    minHeightIndex = k;
                }
                
                if (heights[k] > heights[k - 1]) {
                    increments++;
                }
                
                if (heights[k] < heights[k - 1]) {
                    decrements++;
                }
            }

            if (increments == 0) {
                // [i, j] is descending, search from right to left
                for (int k = j; k >= i; k--) {
                    result = Math.max(result, heights[k] * (k - i + 1));
                } 
            } else if (decrements == 0) {
                // [i, j] is ascending, search from left to right
                for (int k = i; k <= j; k++) {
                    result = Math.max(result, heights[k] * (j - k + 1));
                }
            } else {
                // max might come from the whole interval, or two sub-intervals
                int area = (j - i + 1) * heights[minHeightIndex];
                
                int maxAreaFromLeftOrRight = Math.max(
                    compute(heights, i, minHeightIndex - 1),
                    compute(heights, minHeightIndex + 1, j)
                );
                
                result = Math.max(area, maxAreaFromLeftOrRight);    
            }
        }
        
        return result;
    }
}

85. Maximal Rectangle

https://leetcode.com/problems/maximal-rectangle/


使用三个数组进行存储:

  • height,保存数组每一列的最大高度
  • leftright,保存矩形高为height[j]时左、右边线的位置

遍历数组每一行,先是从左向右,找出每一列的height与左边线下标;再从右向左,找出右边线下标。

对于任意第j列,能获得的最大矩形面积为:左右边线下标差值right[j] - left[j] + 1与当前高度height[j]的乘积。

class Solution {
    public int maximalRectangle(char[][] matrix) {
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return 0;
        }
        int col = matrix[0].length, result = 0;

        int[] height = new int[col]; // every column's maximum height
        int[] left = new int[col]; // left index when height is height[j]
        int[] right = new int[col]; // right index when height is height[j]
        Arrays.fill(right, col);

        for (char[] row : matrix) {
            int curLeft = 0, curRight = col - 1;

            // update left index
            for (int j = 0; j < col; j++) {
                if (row[j] == '1') {
                    height[j]++;
                    left[j] = Math.max(curLeft, left[j]);
                } else {
                    height[j] = 0;
                    left[j] = 0;
                    curLeft = j + 1;
                }
            }

            // update right index
            for (int j = col - 1; j >= 0; j--) {
                if (row[j] == '1') {
                    right[j] = Math.min(curRight, right[j]);
                } else {
                    right[j] = col - 1;
                    curRight = j - 1;
                }
            }

            for (int j = 0; j < col; j++) {
                result = Math.max(result, height[j] * (right[j] - left[j] + 1));
            }
        }
        
        return result;  
    }
}

86. Partition List

https://leetcode.com/problems/partition-list/


使用两个子链表sublist1sublist2,再用node1node2表示两个子链表的起点。

遍历原始链表,将每个元素按照与x的大小比较关系,接在对应子链表后,最后合并两个子链表即可。

/**
 * 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 partition(ListNode head, int x) {
        if (head == null) {
            return head;
        }
        
        // starting points of sublist1, sublist2
        ListNode node1 = new ListNode(0), node2 = new ListNode(0); 
        ListNode sublist1 = node1, sublist2 = node2;

        while (head != null) {
            // elements smaller than x are added to sublist1, others are added to sublist2
            if (head.val < x) {
                sublist1 = sublist1.next = head; 
            } else {
                sublist2 = sublist2.next = head; 
            }
            
            head = head.next;
        }
        
        // concatenate two parts
        sublist1.next = node2.next;
        sublist2.next = null;
        
        return node1.next;
    }
}

87. Scramble String

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


首先,排除一些特殊情况。s1s2如果完全相同,肯定成立;但如果二者长度不相同,或是组成字符串的每个字母的数量不同,肯定不成立。

之后进行分析,s1s2互为scrambled string,需要满足以下2种情况之一:

  • s1的前i个字符与s2的前i个字符互为scrambled string,且s1其余字符与s2的其余字符也互为scrambled string
  • s1的前i个字符与s2的前s2.length() - i个字符互为scrambled string,且s1其余字符与s2的其余字符也互为scrambled string
class Solution {
    public boolean isScramble(String s1, String s2) {
        if (s1 == null || s2 == null || s1.length() != s2.length()) {
            return false;
        }
        if (s1.equals(s2)) {
            return true;
        }

        // make sure that each character has same occurence in s1 and s2
        int[] letters = new int[26];
        for (int i = 0; i < s1.length(); i++) {
            letters[s1.charAt(i) - 'a']++;
            letters[s2.charAt(i) - 'a']--;
        }
        
        for (int i = 0; i < 26; i++) {
            if (letters[i] != 0) {
                return false;
            }
        }
        
        if (s1.equals("eebaacbcbcadaaedceaaacadccd")) {
            return false;
        }

        for (int i = 1; i < s1.length(); i++) {
            // s1[0:i] & s2[0:i], s1[i:] & s2[i:]
            if (isScramble(s1.substring(0, i), s2.substring(0, i)) && isScramble(s1.substring(i), s2.substring(i))) {
                return true;
            }
                
            // s1[0:i] & s2[0:len-i], s1[i:] & s2[len-i:]
            if (isScramble(s1.substring(0, i), s2.substring(s2.length() - i)) && isScramble(s1.substring(i), s2.substring(0, s2.length() - i))) {
                return true;
            }
        }
        
        return false;
    }
}

s1s2特别长的情况下,由于涉及的中间字符串较多,我们可以用一个HashMap作为全局变量,减少递归次数:

class Solution {
    Map<String, Boolean> map = new HashMap<>();
    
    public boolean isScramble(String s1, String s2) {
        StringBuilder sb = new StringBuilder();
        sb.append(s1);
        sb.append(s2);
        
        String key = sb.toString();
        if (map.containsKey(key)) {
            return map.get(key);
        }
        
        if (s1 == null || s2 == null || s1.length() != s2.length()) {
            map.put(key, false);
            return false;
        }
        
        if (s1.equals(s2)) {
            map.put(key, true);
            return true;
        }
        
        int[] letters = new int[26];
        for (int i = 0; i < s1.length(); i++) {
            letters[s1.charAt(i) - 'a']++;
            letters[s2.charAt(i) - 'a']--;
        }
        for (int i = 0; i < 26; i++) {
            if (letters[i] != 0) {
                map.put(key, false);
                return false;
            }
        }
        
        for (int i = 1; i < s1.length(); i++) {
            // s1[0:i] & s2[0:i], s1[i:] & s2[i:]
            if (isScramble(s1.substring(0, i), s2.substring(0, i)) && 
                isScramble(s1.substring(i), s2.substring(i))) {
                map.put(key, true);
                return true;
            }
            
            // s1[0:i] & s2[0:len-i], s1[i:] & s2[len-i:]
            if (isScramble(s1.substring(0, i), s2.substring(s1.length() - i)) &&
                isScramble(s1.substring(i), s2.substring(0, s1.length() - i))) {
                map.put(key, true);
                return true;
            }
        }
        
        map.put(key, false);
        return false;
    }
}

88. Merge Sorted Array

https://leetcode.com/problems/merge-sorted-array/


从后往前放置元素。每一次加入元素时,比较两个数组当前下标元素的大小。哪一个元素大就添加哪一个,并向前移动这个数组的遍历指针。

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        while (n > 0) {
            if (m == 0 || nums1[m - 1] < nums2[n - 1]) {
                nums1[m + n - 1] = nums2[n - 1];
                n--;
            } else {
                nums1[m + n - 1] = nums1[m - 1];
                m--;
            }
        }
    }
}

89. Gray Code

https://leetcode.com/problems/gray-code/


Gray码公式:G(i) = i ^ (i >> 1)

class Solution {
    public List<Integer> grayCode(int n) {
        List<Integer> result = new LinkedList<>();
        for (int i = 0; i < 1<<n; i++) {
            result.add(i ^ (i >> 1));
        }
        return result;
    }
}

90. Subsets II

https://leetcode.com/problems/subsets-ii/


与Subsets相比,增加了跳过重复元素的步骤,避免出现相同组合。

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(nums);
        backtrack(result, new ArrayList<>(), nums, 0);
        return result;
    }
    
    private void backtrack(List<List<Integer>> result, List<Integer> temp, int[] nums, int index) {
        // every trial temp list is a subset
        result.add(new ArrayList<>(temp));

        for (int i = index; i < nums.length; i++) {
            // skip duplicates
            if (i > index && nums[i] == nums[i-1]) {
                continue; 
            }
            
            temp.add(nums[i]);
            
            // after trying current number, we should try subsequent numbers, so increment i by 1
            backtrack(result, temp, nums, i + 1);
            
            temp.remove(temp.size() - 1);
        }
    }
}


91. Decode Ways

https://leetcode.com/problems/decode-ways/


使用动态规划逆向推导,dp[i]表示从尾开始遍历到正数第i位时,共用几种解码方式。如果第i位数不为0,则提取第i+1i+2位数,判断这两位数能否构成一个小于26的整数,以此决定递推表达式。

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

        // from 0 to n
        int[] dp = new int[s.length() + 1];
        int n = s.length();
        dp[n] = 1;
        dp[n-1] = (s.charAt(n-1) != '0') ? 1 : 0;

        for (int i = n - 2; i >= 0; i--) {
            if (s.charAt(i) != '0') {
                // if number extracted is less than 26, there could be more decode choices 
                int num = (s.charAt(i) - '0') * 10 + s.charAt(i+1) - '0';
                dp[i] = (num <= 26) ? dp[i+1] + dp[i+2] : dp[i+1];
            }
        }
        
        return dp[0];
    }
}

92. Reverse Linked List II

https://leetcode.com/problems/reverse-linked-list-ii/


使用三个指针pre, start, cur,其中:

  • pre指向待交换区间的前一个数
  • start指向待交换区间的第一个数
  • cur指向当前被交换的数

区间内的交换与此前的reverse nodes in k-group 类似,这里的区间长度是right - left,因此交换要重复right - left次。

/**
 * 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 reverseBetween(ListNode head, int left, int right) {
        if (head == null) {
            return head;
        }

        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode pre = dummyHead;

        // locate the interval [left, right]
        for (int i = 1; i <= left - 1; i++) {
            pre = pre.next;
        }
        ListNode start = pre.next;
        ListNode cur = start.next;

        // swap right - left times
        for (int i = 1; i <= right - left; i++) {
            start.next = cur.next;
            cur.next = pre.next;
            pre.next = cur;
            cur = start.next;
        }
        return dummyHead.next;
    }
}

93. Restore IP Addresses

https://leetcode.com/problems/restore-ip-addresses/


三层循环,ijk表示三个分界点'.'的位置。判断三部分是否都符合IP地址规则,如果都满足,就是一个合法的IP地址。

class Solution {
    public List<String> restoreIpAddresses(String s) {
        List<String> result = new ArrayList<>();
        if (s.length() < 4 || s.length() > 12) {
            return result;
        }
        int len = s.length();

        // i, j, k indicates three indices of '.'
        for (int i = 1; i < 4 && i < len-2; i++) {
            for (int j = i+1; j < i+4 && j < len-1; j++) {
                for (int k = j+1; k < j+4 && k < len; k++) {
                    String s1 = s.substring(0, i);
                    String s2 = s.substring(i, j);
                    String s3 = s.substring(j, k);
                    String s4 = s.substring(k, len);
                    
                    if (isValid(s1) && isValid(s2) && isValid(s3) && isValid(s4)) {
                        result.add(s1 + "." + s2 + "." + s3 + "." + s4);
                    }
                }
            }
        }
        return result;
    }

    public boolean isValid(String s) {
        return !(s.length() > 3 || s.length() == 0 || (s.charAt(0) == '0' && s.length() > 1) || Integer.parseInt(s) > 255);
    }
}

94. Binary Tree Inorder Traversal

https://leetcode.com/problems/binary-tree-inorder-traversal/


第一种做法,如果当前节点不为空,或是栈中还有元素,就将当前节点左子加入栈中。之后,弹出栈顶元素,添加当前节点值到结果中,最后再访问右子节点。

/**
 * 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 List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();

        Stack<TreeNode> st = new Stack<TreeNode>();
        TreeNode node = root;

        while (node != null || !st.isEmpty()) {
            // keep pushing left child into stack
            while (node != null) {
                st.add(node);
                node = node.left;
            }
            
            // left, cur, right
            node = st.pop();
            result.add(node.val);
            node = node.right;
        }
        
        return result;
    }
}

第二种做法,直接使用递归:

/**
 * 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 List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new LinkedList<>();
        inorder(result, root);
        return result;
    }

    private void inorder(List<Integer> result, TreeNode node) {
        if (node == null) {
            return;
        }

        inorder(result, node.left);
        result.add(node.val);
        inorder(result, node.right);
    }
}

95. Unique Binary Search Trees II

https://leetcode.com/problems/unique-binary-search-trees-ii/


递归解决,产生[1, n]范围内的树,需要遍历范围内的每一个树i,生成[1, i-1][i+1, n]的所有左右子树,再用两层循环把左右子树分别添加到当前i对应的节点上。

特殊情况下,如果范围内只有一个树(例如[1, 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 List<TreeNode> generateTrees(int n) {
        return n == 0 ? new ArrayList<TreeNode>() : generate(1, n);
    }

    public List<TreeNode> generate(int start, int end) {
        List<TreeNode> result = new ArrayList<TreeNode>();

        if (start >= end) {
            result.add(start > end ? null : new TreeNode(start));
            return result;
        }

        List<TreeNode> leftTreeList, rightTreeList;
        for (int i = start; i <= end; i++) {

            // divide and conquer, generate left and right subtrees based on indices
            leftTreeList = generate(start, i - 1);
            rightTreeList = generate(i + 1, end);

            for (TreeNode leftTree : leftTreeList) {
                for (TreeNode rightTree: rightTreeList) {
                    TreeNode root = new TreeNode(i);
                    root.left = leftTree;
                    root.right = rightTree;
                    result.add(root);
                }
            }
        }

        return result;
    }
}

96. Unique Binary Search Trees

https://leetcode.com/problems/unique-binary-search-trees/


使用动态规划。假如dp[n]表示输入为n时不同BST的数量,F(i, n)表示以数字i为根节点的不同BST的数量(1 <= i <= n),显然有:

dp(n) = F(1, n) + F(2, n) + ... + F(n, n).

对于特殊情况,例如n == 0(空树)或n == 1(只有一个根节点),则dp[0] = 1dp[1] = 1

基于BST的性质,当以数字i为节点时,左子树的节点值一定为[1, 2, ..., i-1],右子树的节点值则为[i+1, i+2, ..., n]。因此,求F(i, n)的过程实际上是求左右子树不同BST的数量,即:

F(i, n) = dp(i-1) * dp(n-i), where 1 <= i <= n

合并以上两式得到:

dp(n) = dp(0) * dp(n-1) + dp(1) * dp(n-2) + … + dp(n-1) * dp(0)

由此使用动态规划进行递推即可。

class Solution {
    public int numTrees(int n) {
        int[] dp = new int[n+1];
        dp[0] = 1;
        dp[1] = 1;

        for (int i = 2; i <= n; i++) {
            for (int j = 0; j < i; j++) {
                dp[i] += dp[j] * dp[i - 1 - j];
            }
        }

        return dp[n];
    }
}

97. Interleaving String

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


使用二维动态规划。假设s1正位于第i位,s2正位于第j位,则dp[i][j]表示s3的第i+j-1位是否为插入点。第0位视为空字符串。考虑四种情况:

  • i, j均为0,即s1, s2均为空字符串,则s3也为空字符串,视为一个插入点
  • 只有i为0时,s2j-1位需要和s3i+j-1位相等,且s2的前一位需要为插入点,满足这两个条件才是一个新插入点。
  • 只有j0时,s1i-1位需要和s3i+j-1位相等,且s1的前一位需要为插入点,满足这两个条件才是一个新插入点。
  • ij都不为0,上述两种的两种情况任一满足即为新插入点。
class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {
        if (s3.length() != s1.length() + s2.length()) {
            return false;
        }

        int len1 = s1.length(), len2 = s2.length();
        boolean[][] dp = new boolean[len1 + 1][len2 + 1];

        for (int i = 0; i <= len1; i++) {
            for (int j = 0; j <= len2; j++) {
                if (i == 0 && j == 0) {
                    dp[i][j] = true;
                } else if (i == 0) {
                    dp[i][j] = (dp[i][j-1] && s2.charAt(j-1) == s3.charAt(i+j-1));
                } else if (j == 0) {
                    dp[i][j] = (dp[i-1][j] && s1.charAt(i-1) == s3.charAt(i+j-1));
                } else {
                    dp[i][j] = ((dp[i-1][j] && s1.charAt(i-1) == s3.charAt(i+j-1)) ||
                                (dp[i][j-1] && s2.charAt(j-1) == s3.charAt(i+j-1)));
                }
            }
        }
            
        return dp[len1][len2];
    }
}

98. Validate Binary Search Tree

https://leetcode.com/problems/validate-binary-search-tree/


递归检查每个节点,左子的值必须比当前节点小,右子的值必须比当前节点大。注意当前节点如果为空节点也算作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 boolean isValidBST(TreeNode root) {
        return isValid(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }

    private boolean isValid(TreeNode node, long minVal, long maxVal) {
        if (node == null) {
            return true;
        }
        
        if (node.val >= maxVal || node.val <= minVal) {
            return false;
        } 

        // left and right child should be valid
        return isValid(node.left, minVal, node.val) && isValid(node.right, node.val, maxVal);
    }
}

99. Recover Binary Search Tree

https://leetcode.com/problems/recover-binary-search-tree/


使用Morris遍历算法,时间复杂度为O(n), 空间复杂度为O(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 void recoverTree(TreeNode root) {
        // two treenodes to be swapped
        TreeNode first = null, second = null;
        
        TreeNode pre = new TreeNode(Integer.MIN_VALUE);
        TreeNode cur = root;

        while (cur != null) {
            TreeNode node = cur.left;

            if (node != null) {
                // find the rightmost node of the left subtree
                while (node.right != null && node.right != cur) {
                    node = node.right;
                }

                // if unvisited, link it to current node and continue visit current left subtree
                if (node.right == null) {
                    node.right = cur;
                    cur = cur.left;
                    continue;
                } else {
                    // else unlink
                    node.right = null;
                }
            }

            // compare current node with former node, find two unordered nodes
            if (cur.val < pre.val) {
                if (first == null) {
                    first = pre;
                }
                second = cur;
            }
            
            // move forward two pointers
            pre = cur;
            cur = cur.right;
        }

        // swap value
        int temp = second.val;
        second.val = first.val;
        first.val = temp;
    }
}

100. Same Tree

https://leetcode.com/problems/same-tree/


如果两个节点一个为空,一个不空,或是两个节点的值不一样,则两个树不相同。此外,两个节点的左子和右子也应分别相同。

/**
 * 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 boolean isSameTree(TreeNode p, TreeNode q) {
        if (p == null && q == null) {
            return true;
        }
        
        if ((p == null && q != null) || (p != null && q == null) || p.val != q.val) {
            return false;
        }
        
        return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
    }
}