LeetCode 1-100

25103 字
126 分钟
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);
}
}
Profile Image of the Author
MikeYan01
Hello world!
站点统计
文章
23
分类
7
标签
12
总字数
215,225
运行时长
0
最后活动
0 天前
1
1. Two Sum
2
2. Add Two Numbers
3
3. Longest Substring Without Repeating Characters
4
4. Median of Two Sorted Arrays
5
5. Longest Palindromic Substring
6
6. Zigzag Conversion
7
7. Reverse Integer
8
8. String to Integer (atoi)
9
9. Palindrome Number
10
10. Regular Expression Matching
11
11. Container With Most Water
12
12. Integer to Roman
13
13. Roman to Integer
14
14. Longest Common Prefix
15
15. 3Sum
16
16. 3Sum Closest
17
17. Letter Combinations of a Phone Number
18
18. 4Sum
19
19. Remove Nth Node From End of List
20
20. Valid Parentheses
21
21. Merge Two Sorted Lists
22
22. Generate Parentheses
23
23. Merge k Sorted Lists
24
24. Swap Nodes in Pairs
25
25. Reverse Nodes in k-Group
26
26. Remove Duplicates from Sorted Array
27
27. Remove Element
28
28. Implement strStr()
29
29. Divide Two Integers
30
30. Substring with Concatenation of All Words
31
31. Next Permutation
32
32. Longest Valid Parentheses
33
33. Search in Rotated Sorted Array
34
34. Find First and Last Position of Element in Sorted Array
35
35. Search Insert Position
36
36. Valid Sudoku
37
37. Sudoku Solver
38
38. Count and Say
39
39. Combination Sum
40
40. Combination Sum II
41
41. First Missing Positive
42
42. Trapping Rain Water
43
43. Multiply Strings
44
44. Wildcard Matching
45
45. Jump Game II
46
46. Permutations
47
47. Permutations II
48
48. Rotate Image
49
49. Group Anagrams
50
50. Pow(x, n)
51
51. N-Queens
52
52. N-Queens II
53
53. Maximum Subarray
54
54. Spiral Matrix
55
55. Jump Game
56
56. Merge Intervals
57
57. Insert Interval
58
58. Length of Last Word
59
59. Spiral Matrix II
60
60. Permutation Sequence
61
61. Rotate List
62
62. Unique Paths
63
63. Unique Paths II
64
64. Minimum Path Sum
65
65. Valid Number
66
66. Plus One
67
67. Add Binary
68
68. Text Justification
69
69. Sqrt(x)
70
70. Climbing Stairs
71
71. Simplify Path
72
72. Edit Distance
73
73. Set Matrix Zeroes
74
74. Search a 2D Matrix
75
75. Sort Colors
76
76. Minimum Window Substring
77
77. Combinations
78
78. Subsets
79
79. Word Search
80
80. Remove Duplicates from Sorted Array II
81
81. Search in Rotated Sorted Array II
82
82. Remove Duplicates from Sorted List II
83
83. Remove Duplicates from Sorted List
84
84. Largest Rectangle in Histogram
85
85. Maximal Rectangle
86
86. Partition List
87
87. Scramble String
88
88. Merge Sorted Array
89
89. Gray Code
90
90. Subsets II
91
91. Decode Ways
92
92. Reverse Linked List II
93
93. Restore IP Addresses
94
94. Binary Tree Inorder Traversal
95
95. Unique Binary Search Trees II
96
96. Unique Binary Search Trees
97
97. Interleaving String
98
98. Validate Binary Search Tree
99
99. Recover Binary Search Tree
100
100. Same Tree
文章目录
1
1. Two Sum
2
2. Add Two Numbers
3
3. Longest Substring Without Repeating Characters
4
4. Median of Two Sorted Arrays
5
5. Longest Palindromic Substring
6
6. Zigzag Conversion
7
7. Reverse Integer
8
8. String to Integer (atoi)
9
9. Palindrome Number
10
10. Regular Expression Matching
11
11. Container With Most Water
12
12. Integer to Roman
13
13. Roman to Integer
14
14. Longest Common Prefix
15
15. 3Sum
16
16. 3Sum Closest
17
17. Letter Combinations of a Phone Number
18
18. 4Sum
19
19. Remove Nth Node From End of List
20
20. Valid Parentheses
21
21. Merge Two Sorted Lists
22
22. Generate Parentheses
23
23. Merge k Sorted Lists
24
24. Swap Nodes in Pairs
25
25. Reverse Nodes in k-Group
26
26. Remove Duplicates from Sorted Array
27
27. Remove Element
28
28. Implement strStr()
29
29. Divide Two Integers
30
30. Substring with Concatenation of All Words
31
31. Next Permutation
32
32. Longest Valid Parentheses
33
33. Search in Rotated Sorted Array
34
34. Find First and Last Position of Element in Sorted Array
35
35. Search Insert Position
36
36. Valid Sudoku
37
37. Sudoku Solver
38
38. Count and Say
39
39. Combination Sum
40
40. Combination Sum II
41
41. First Missing Positive
42
42. Trapping Rain Water
43
43. Multiply Strings
44
44. Wildcard Matching
45
45. Jump Game II
46
46. Permutations
47
47. Permutations II
48
48. Rotate Image
49
49. Group Anagrams
50
50. Pow(x, n)
51
51. N-Queens
52
52. N-Queens II
53
53. Maximum Subarray
54
54. Spiral Matrix
55
55. Jump Game
56
56. Merge Intervals
57
57. Insert Interval
58
58. Length of Last Word
59
59. Spiral Matrix II
60
60. Permutation Sequence
61
61. Rotate List
62
62. Unique Paths
63
63. Unique Paths II
64
64. Minimum Path Sum
65
65. Valid Number
66
66. Plus One
67
67. Add Binary
68
68. Text Justification
69
69. Sqrt(x)
70
70. Climbing Stairs
71
71. Simplify Path
72
72. Edit Distance
73
73. Set Matrix Zeroes
74
74. Search a 2D Matrix
75
75. Sort Colors
76
76. Minimum Window Substring
77
77. Combinations
78
78. Subsets
79
79. Word Search
80
80. Remove Duplicates from Sorted Array II
81
81. Search in Rotated Sorted Array II
82
82. Remove Duplicates from Sorted List II
83
83. Remove Duplicates from Sorted List
84
84. Largest Rectangle in Histogram
85
85. Maximal Rectangle
86
86. Partition List
87
87. Scramble String
88
88. Merge Sorted Array
89
89. Gray Code
90
90. Subsets II
91
91. Decode Ways
92
92. Reverse Linked List II
93
93. Restore IP Addresses
94
94. Binary Tree Inorder Traversal
95
95. Unique Binary Search Trees II
96
96. Unique Binary Search Trees
97
97. Interleaving String
98
98. Validate Binary Search Tree
99
99. Recover Binary Search Tree
100
100. Same Tree