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
因此我们创建一个新链表,按照上述条件,对l1和l2逐位计算相加,得到结果后创建一个新节点插入到链表中。
/**
* 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,提高运行效率。
定义两个指针begin和end,begin指向最长不重复子串起点,尾指针用于遍历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个切割点,下标设定为
C1和C2 - 切割的左侧和右侧总共都有
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/
流程分为三步:
- 去除字符串开头的空格
- 判断是否存在正负号
- 判断是否为数字。如果是数字,更新运算结果,并检查当前结果是否超出整数范围;如果不是数字,则返回当前结果
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/
此题需要使用二维动态规划算法。对于s和p,我们需要从头开始推导出子字符串s[i]和p[j]的匹配情况,进而推出整个s和p是否匹配。
解决时,需要对题设条件进行分类讨论:
- 最标准的匹配情况,即
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, third。first负责从数组头遍历至数组尾,在每一次循环中固定first,移动头尾指针two和third,对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/
第一种方法,比较l1和l2上的指针当前指向的元素,取较小元素之后的子链表和另一个链表继续进行递归合并。这样能保证每一次比较都优先提取出两个剩余链表的最小值,其余部分的结果也会接在这个最小值之后。
/**
* 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;
}
}
}
第二种方法,也是比较l1和l2上的指针当前指向的元素,但不使用递归,而是直接根据大小关系决定排列顺序。
/**
* 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/
使用递归策略,优先构造左括号,当左括号个数大于右括号时再构造右括号。
具体代码实现时,可以使用两个变量left,right跟踪当前左、右括号数量,在递归中将它们与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/
此题要求实现除法,并且不能使用乘、除、取模三个符号。大致的思路流程是:
- 专门处理被除数为
Integer.MIN_VALUE,divisor为-1时的情况,这种时候int形式的结果会发生溢出 - 将
dividend和divisor都取绝对值 - 使用指数倍增思想,快速寻找到
dividend中至少包含几个divisor。 - 决定最终结果的实际符号
为了避免溢出,在第一步处理完特例后,我们最好将dividend和divisor都转为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则根据较低的池壁高度不断更新。指针移动过程中,新增的水量为currentLevel与lowerBound的差值。
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-1或m+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()];
}
}
解法二,使用指针:
使用两个指针sIdx和pIdx分别跟踪s和p,分类讨论所有情况:
- 严格的单字对应,或
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/
实现幂函数的构造需要运用递归。然而在开始递归前,需要先排除一些输入的特殊情况。n和x各有若干特殊情况:
- 输入的
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/
此题判断的是能否到达数组终点。仍然使用贪心算法,选取能达到最远位置的落点进行跳跃。
然而,落点可能永远无法落到终点或终点之后,此时下一次备选的落点区域边界start和end都将无法更新,因此需要额外判断这两个落点的关系。
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/
- 添加肯定位于
newInterval之前的区间 - 针对所有和
newInterval有重叠的区间,计算出最小的起点和最大的终点,直接合并 - 添加合并后的大区间
- 添加肯定位于
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/
- 获取链表的总长度,并将最后一个元素指向头元素,该过程遍历一次链表可完成
- 计算出指针实际移动的距离。尽管
k的值可能大于链表长度,但是取模后即可解决这个问题 - 返回新的
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中每个字符出现的次数
解题步骤:
- 统计
t中每个字符出现的次数到tmap中,并同时统计t中总共有多少个不同的字符 - 双指针法遍历
s,每当遇到一个在t中出现过的字符,就相应更新smap中的字符出现次数 - 如果某个字符在
smap中的出现次数与tmap相等,说明目前的子字符串s[left, right]中已经包含了足够多的该字符。如果全部字符都满足了这个条件,就说明当前的子字符串是一个符合题意的sliding window,看这个window的长度是否是已知最小的 - 在保证sliding window仍旧合法的前提下,尝试从头开始缩减长度,即:如果当前的字符
s[left]在smap中的次数比tmap中多,就删掉一个s[left],并且向后移动left指针。一直重复这个过程,直到某次完成删除后,sliding window不再合法 - 完成遍历后,根据此前统计的最小值信息截取子字符串,得到结果;而如果发现最短长度并没有更新过,说明不存在这样的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);
}
}
}
79. Word Search
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,保存数组每一列的最大高度left、right,保存矩形高为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/
使用两个子链表sublist1和sublist2,再用node1和node2表示两个子链表的起点。
遍历原始链表,将每个元素按照与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/
首先,排除一些特殊情况。s1和s2如果完全相同,肯定成立;但如果二者长度不相同,或是组成字符串的每个字母的数量不同,肯定不成立。
之后进行分析,s1和s2互为scrambled string,需要满足以下2种情况之一:
s1的前i个字符与s2的前i个字符互为scrambled string,且s1其余字符与s2的其余字符也互为scrambled strings1的前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;
}
}
在s1和s2特别长的情况下,由于涉及的中间字符串较多,我们可以用一个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+1和i+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/
三层循环,i、j、k表示三个分界点'.'的位置。判断三部分是否都符合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] = 1,dp[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时,s2第j-1位需要和s3第i+j-1位相等,且s2的前一位需要为插入点,满足这两个条件才是一个新插入点。 - 只有
j为0时,s1第i-1位需要和s3第i+j-1位相等,且s1的前一位需要为插入点,满足这两个条件才是一个新插入点。 i,j都不为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);
}
}