LeetCode 解题报告

张天宇 on 2021-04-07

LeetCode 解题报告与每日打卡记录,顺便记录不太顺利或者有其他巧妙思路的题目。题目较多,渲染可能比较缓慢。

2 Add Two Numbers

题目大意

多位整数每位按照倒序排列放入链表,使用链表求和并返回。

解题思路

设立一个表示进位的变量temp,建立一个新链表,把输入的两个链表从头往后同时处理,每两个相加,将结果加上temp%10后的进位作为一个新节点到新链表后面。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
if (l1 == null)
return l1;
if (l2 == null)
return l2;
ListNode resu = new ListNode(0);
ListNode node = resu;
int temp = 0;
while (l1 != null || l2 != null || temp != 0) {
if (l1 != null) {
temp += l1.val;
l1 = l1.next;
}
if (l2 != null) {
temp += l2.val;
l2 = l2.next;
}
node.next = new ListNode(temp % 10);
node = node.next;
temp /= 10;
}
return resu.next;
}
}

3 Longest Substring Without Repeating Characters

题目大意

寻找最长的无重复的子串。

解题思路

寻找当前字符串出现的上一次位置,长度就是中间的长度,因为中间的字符串也是这么判断过来的。

这里的 start 和 map 数组设计的很巧妙。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int lengthOfLongestSubstring(String s) {
if (s == null || s.length() == 0) return 0;
int[] count = new int[256];
// 因为0也是位置,所以全部-1防止误判断
Arrays.fill(count, -1);
int start = -1, result = 0;
for (int i = 0; i < s.length(); i++){
// 更新重复的字符最后一次出现的位置
start = Math.max(count[s.charAt(i)], start);
// 修改当前字符出现的位置
count[s.charAt(i)] = i;
// 取最大值
result = Math.max(result, i - start);
}
return result;
}
}

5 Longest Palindromic Substring

题目大意

寻找字符串中最长的回文子串。

解题思路

动态规划,设置dp[i][j]为字符串上i到j的字符串是否为子串。它将由它的内部决定,即如果它的两头相等的话且去掉两头后仍为回文字符串。即:
$$
dp[i][j] = dp[i+1][j-1], if(s[i]==s[j])
$$
发现回文字符串后,更新最大值。同时,显然有些必要条件要考虑,如i一定是<=j的。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public static String longestPalindrome(String s) {
boolean[][] dp = new boolean[1005][1005];
int left = 0;
int right = 0;
int maxl = 1;
int n = s.length();
for (int j = 0; j < n; j++) {
// 单个字符肯定是回文字符串
dp[j][j] = true;
for (int i = 0; i < j; i++) {
if (s.charAt(i) == s.charAt(j) && (j - i < 2 || dp[i + 1][j - 1])) {
dp[i][j] = true;
}
if (dp[i][j] && maxl < j - i + 1) {
maxl = j - i + 1;
left = i;
right = j;
}
}
}
return (s == null || s.length() <= 1) ? s : s.substring(left, right + 1);
}
}

6 ZigZag Conversion

题目大意

将字符串改为N字后逐行输出,例如 “PAYPALISHIRING” 输出为 “PAHNAPLSIIGYIR” 。

解题思路

使用StringBuffer作为储存每一行的工具,然后分别处理上下两种状态。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public String convert(String s, int nRows) {
char[] c = s.toCharArray();
int len = c.length;
StringBuffer[] sb = new StringBuffer[nRows];
for (int i = 0; i < sb.length; i++)
sb[i] = new StringBuffer();
int i = 0;
while (i < len) {
for (int idx = 0; idx < nRows && i < len; idx++) // 竖着的
sb[idx].append(c[i++]);
for (int idx = nRows - 2; idx >= 1 && i < len; idx--) // 斜向上的
sb[idx].append(c[i++]);
}
for (int idx = 1; idx < sb.length; idx++)
sb[0].append(sb[idx]);
return sb[0].toString();
}
}

8 String to Integer (atoi)

题目大意

将输入的字符串转化为整数。有以下几点要求:

  1. 第一个非空字符必须是可选的±或者数字,然后是一些数字。
  2. 字符串后面可以包含额外字符,将被忽略。
  3. 如果非法,将不发生转换。
  4. 没有发生转换返回0,如果超出了正值就返回整数最大值,或者最小值。
1
2
3
4
5
6
7
8
9
10
11
Input: "   -42"
Output: -42
Explanation: The first non-whitespace character is '-', which is the minus sign.
Then take as many numerical digits as possible, which gets 42.
Input: "4193 with words"
Output: 4193
Explanation: Conversion stops at digit '3' as the next character is not a numerical digit.
Input: "words and 987"
Output: 0
Explanation: The first non-whitespace character is 'w', which is not a numerical
digit or a +/- sign. Therefore no valid conversion could be performed.

解题思路

按照题目要求处理即可,注意最大值最小值的判断。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public int myAtoi(String str) {
if (str == null || str.length() == 0)
return 0;
str = str.trim();
if (str.length() == 0)
return 0;
int start = 0, sign = 1, base = 0;
if (str.charAt(start) == '-' || str.charAt(start) == '+') {
sign = (str.charAt(start) == '-' ? -1 : 1);
start++;
}
while (start < str.length() && str.charAt(start) >= '0' && str.charAt(start) <= '9') {
if (base > Integer.MAX_VALUE / 10
|| (base == Integer.MAX_VALUE / 10 && (str.charAt(start) - '0' > Integer.MAX_VALUE % 10))) {
return sign > 0 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
base = base * 10 + (str.charAt(start++) - '0');
}
return base * sign;
}
}

9 Palindrome Number

题目大意

判断一个整数是不是回文数。

解题思路

  1. 利用字符串的反转,回文串反转和原串相同。
  2. 判断每个位数。

代码

1
2
3
4
5
6
7
class Solution {
public boolean isPalindrome(int x) {
String str = String.valueOf(x);
StringBuilder sb = new StringBuilder(str);
return sb.reverse().toString().equals(str);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public static boolean isPalindrome(int x) {
if (x < 0) return false;
int[] a = new int[33];
int i = 0;
while (x != 0) {
a[i++] = x % 10;
x /= 10;
}
for (int j = 0; j < i; j++) {
if (a[j] != a[i - j - 1]) {
return false;
}
}
return true;
}

public static void main(String[] args) {
System.out.println(isPalindrome(1234564321));
}
}

10 Regular Expression Matching

题目大意

字符串的匹配,.表示任意字符,*表示一个字符重复n次或者0次。

解题思路与代码

这道题开始理解为了*为代表任意个数的任意字符,提交怎么也不对,后来看了别人的解析恍然大悟,题目会错意了,已经不是第一次了。下面是总结了一下简书上一个很棒的解析。

一、递归
  1. 先假设没有通配符*,只需要一个字符一个字符的匹配即可。

    设函数为isMatch(String text, String pattern),text和pattern匹配,等价于text和pattern的第一个字符匹配并且剩下的也匹配。去判断剩下的是否匹配时候,就可以继续调用isMatch函数。

    1
    boolean first_match = (pattern.charAt(0) == text.charAt(0) || pattern.charAt(0) == '.')&&isMatch(text.substring(1), pattern.substring(1));

    递归出口:随着规模的减小,当pattern为空的时候,如果text也为空,就返回true,否则就返回false。

    1
    if (pattern.isEmpty()) return text.isEmpty();

    综上所述,针对1的代码如下,

    1
    2
    3
    4
    5
    6
    public boolean isMatch(String text, String pattern) {
    if (pattern.isEmpty()) return text.isEmpty();
    //判断 text 是否为空,防止越界,如果 text 为空, 表达式直接判为 false, text.charAt(0)就不会执行了
    boolean first_match = (!text.isEmpty() && (pattern.charAt(0) == text.charAt(0) || pattern.charAt(0) == '.'));
    return first_match && isMatch(text.substring(1), pattern.substring(1));
    }
  2. 当考虑了通配符,代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public boolean isMatch(String text, String pattern) {
    if (pattern.isEmpty()) return text.isEmpty();
    boolean first_match = (!text.isEmpty() && (pattern.charAt(0) == text.charAt(0) || pattern.charAt(0) == '.'));
    //只有长度大于 2 的时候,才考虑 *
    if (pattern.length() >= 2 && pattern.charAt(1) == '*'){
    //两种情况
    //pattern 直接跳过两个字符。表示 * 前边的字符出现 0 次
    //pattern 不变,例如 text = aa ,pattern = a*,第一个 a 匹配,然后 text 的第二个 a 接着和 pattern 的第一个 a 进行匹配。表示 * 用前一个字符替代。
    return (isMatch(text, pattern.substring(2)) || (first_match && isMatch(text.substring(1), pattern)));
    } else {
    return first_match && isMatch(text.substring(1), pattern.substring(1));
    }
    }
二、动态规划

设定二维数组 dp[i][j] 表示 s[0…i-1] 可以和 p[0…j-1] 完全匹配,那么会存在以下几种情况:

1
2
3
4
5
6
7
8
9
1. if s[i-1] == p[j-1] || p[j-1] == '.' : dp[i][j] = dp[i-1][j-1] // i-1和j-1相同,或者可以跳过
2. if p[j-1] == '*':
// j-1是*了,看j-2,如果j-2不是*而且不等于i-1,就不要这个字符了
if p[j-2] != '*' && p[j-2] != s[i-1]: dp[i][j] = dp[i][j-2] // 0a
// 不然的话,分情况讨论
else:
dp[i][j] = dp[i][j-2] // 0a
dp[i][j] = dp[i-1][j-2] // 1a
dp[i][j] = dp[i-1][j] // 1+a

对于初始化,dp[0][0] 为 true,dp[i][0] 为 false,而 dp[0][j] 的取值当 p 为#*#*#* 或者 (#*)* 时为 true。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public boolean isMatch(String s, String p) {
int l1 = s.length(), l2 = p.length();
boolean[][] dp = new boolean[l1 + 1][l2 + 1];
dp[0][0] = true;
for (int i = 2; i < l2 + 1; i += 2) {
if (p.charAt(i - 1) == '*' && dp[0][i - 2]) {
dp[0][i] = true;
}
}
for (int i = 1; i < l1 + 1; i++) {
for (int j = 1; j < l2 + 1; j++) {
if (s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '.') {
dp[i][j] = dp[i - 1][j - 1];
} else if (p.charAt(j - 1) == '*') {
if (p.charAt(j - 2) != s.charAt(i - 1) && p.charAt(j - 2) != '.') {
dp[i][j] = dp[i][j - 2];
} else {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - 2] || dp[i][j - 2];
}
}
}
}
return dp[l1][l2];
}
}

15 3Sum

题目大意

在数组中寻找三个和为0的数的组合。

解题思路

排序后,固定一个值,另外两个夹逼寻找。如果和大于零,说明正数太大,右边的需要缩小往左移。如果和小于零,说明负数太小,需要左边的需要往右移。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
if (nums == null || nums.length == 0) return result;
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++){
if (nums[i] > 0){
break;
}
if (i > 0 && nums[i] == nums[i - 1])
continue;
int left = i + 1, right = nums.length - 1;
while (left < right){
int l = nums[left];
int r = nums[right];
int m = nums[i];
if (l + r + m == 0){
result.add(Arrays.asList(m, l, r));
while (left < right && nums[left] == l) left ++;
while (left < right && nums[right] == r) right --;
} else if (l + r + m < 0){
left ++;
} else {
right --;
}
}
}
return result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
if (nums.length == 0)
return result;
Arrays.sort(nums);
int left, right, temp;
for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0)
break;
if (i > 0 && nums[i] == nums[i - 1])
continue;
left = i + 1;
right = nums.length - 1;
while (left < right) {
temp = nums[left] + nums[right] + nums[i];
if (temp == 0) {
List<Integer> resu = new ArrayList<>();
int lll = nums[left];
int rrr = nums[right];
resu.add(nums[i]);
resu.add(nums[left]);
resu.add(nums[right]);
result.add(resu);
while (left < right && nums[left] == lll)
left++;
while (left < right && nums[right] == rrr)
right--;
} else if (temp > 0)
right--;
else
left++;
}
}
return result;
}
}

16 3Sum Closest

题目大意

找到乱序数列中和最接近目标值的三个数。

解题思路

和三数之和类似,双指针法,遇到绝对值更小的更新。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int resu = 0;
int sub = Integer.MAX_VALUE; // 这里要注意一下。
for (int i = 0; i < nums.length - 1; i++) {
int mid = i + 1;
int right = nums.length - 1;
while (mid < right) {
int sum = nums[i] + nums[right] + nums[mid];
if (Math.abs(sum - target) < sub) {
sub = Math.abs(sum - target);
resu = sum;
}
if (sum < target) {
mid++;
} else if (sum > target) {
right--;
} else {
return target;
}
}
}
return resu;
}
}

17 Letter Combinations of a Phone Number

题目大意

输出电话号码代表的字母全排列。

1
2
Input: "23"
Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

解题思路

将电话号码对应的字母存储到Map中,定义一个空vector,每处理一个字符串中的字符就将vector中的所有元素拿出来追加这个电话号码的全排列。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
public:
vector<string> letterCombinations(string digits) {
if (digits == "") return vector<string>();
vector<string> resu(1, "");
map<int, vector<string>> key;
//存储键值对
vector<string> t = {"a", "b", "c"};
key.insert(make_pair(2, t));
t = {"d", "e", "f"};
key.insert(make_pair(3, t));
t = {"g", "h", "i"};
key.insert(make_pair(4, t));
t = {"j", "k", "l"};
key.insert(make_pair(5, t));
t = {"m", "n", "o"};
key.insert(make_pair(6, t));
t = {"p", "q", "r", "s"};
key.insert(make_pair(7, t));
t = {"t", "u", "v"};
key.insert(make_pair(8, t));
t = {"w", "x", "y", "z"};
key.insert(make_pair(9, t));
//对每个电话号码处理
for (auto d : digits) {
vector<string> temp;
//将上个电话号码处理后的结果拿出来
for (auto r : resu) {
//追加当前号码代表的字母
for (auto k : key[d - '0']) {
temp.push_back(r + k);
}
}
resu = temp;
}
return resu;
}
};

20 Valid Parentheses

题目大意

判断括号是不是符合开闭规则。

解题思路

主要是特殊情况的考虑。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public boolean isValid(String s) {
HashMap<Character, Character> map = new HashMap<>();
map.put('{','}');
map.put('(',')');
map.put('[',']');
Stack<Character> stack = new Stack<>();
for (int i = 0; i< s.length(); i++){
char sa = s.charAt(i);
// 判断是不是左括号,左括号压入
if (map.containsKey(sa)){
stack.push(sa);
continue;
}
if (map.containsValue(sa)){
if (stack.isEmpty()) return false;
char tmp = stack.peek();
if (map.get(tmp) == sa){
stack.pop();
} else {
return false;
}
}
}
return stack.isEmpty();
}
}

22 Generate Parentheses

题目大意

生成n对括号的合法全排列。

解题思路

  1. 常规,每次都往上一次的结果的缝隙中插入成对的括号。
  2. 回溯,考虑左括号和右括号的情况,如果发现左小于n,就填左;如果右小于左,就填右。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public List<String> generateParenthesis(int n) {
Set<String> set = new HashSet<>();
if (n == 0) {
return new ArrayList<>(set);
}
if (n == 1) {
set.add("()");
return new ArrayList<>(set);
}
List<String> tmp = generateParenthesis(n - 1);
for (String s : tmp) {
for (int i = 0; i < s.length(); i++) {
set.add(s.substring(0, i) + "()" + s.substring(i +));
}
}
return new ArrayList<>(set);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Solution {
public List<String> generateParenthesis(int n) {
List<String> list = new ArrayList<>();
help(list, n, 0, 0, "");
return list;
}
public void help(List<String> list, int n, int l, int r, String s) {
if (s.length() == 2 * n) {
list.add(s);
}
if (l < n) {
help(list, n, l + 1, r, s + '(');
}
if (r < l) {
help(list, n, l, r + 1, s + ')');
}
}
}

23 Merge k Sorted Lists

题目大意

合并 k 个有序链表。

解题思路

将所有节点存放进 PriorityQueue,重写排序方法。这里先将每个节点放入队列中,因为分列表已经有序,取出来的时候,将下一个节点扔进去,自动排序后,再取头。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
PriorityQueue<ListNode> queue = new PriorityQueue<>(new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val - o2.val;
}
});
ListNode result = new ListNode(0);
ListNode tmp = result;
for (ListNode l : lists) {
tmp = l;
if (tmp != null) {
queue.add(tmp);
}
}
ListNode iter = result;
while (!queue.isEmpty()) {
tmp = queue.poll();
iter.next = tmp;
iter = iter.next;
if (iter.next != null) {
queue.add(iter.next);
}
}
return result.next;
}
}

24 Swap Nodes in Pairs

题目大意

给定一个链表,交换临近的两个链表结点。

解题思路

  1. 递归:判断是否符合条件,不符合停止。如果符合条件,交换前两个,并接上去掉前两个的调换结果。
  2. 建立头节点的前节点,按照步骤理清。解析

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//递归
class Solution {
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null)
return head;
ListNode origin = head.next;
head.next = swapPairs(head.next.next);
origin.next = head;
return origin;
}
}
//接头
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode point = dummy;
while (point.next != null && point.next.next != null) {
ListNode swap1 = point.next;
ListNode swap2 = point.next.next;
point.next = swap2;
swap1.next = swap2.next;
swap2.next = swap1;
point = swap1;
}
return dummy.next;
}

25 Reverse Nodes in k-Group

题目大意

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

给定这个链表:1->2->3->4->5

当 k = 2 时,应当返回: 2->1->4->3->5

当 k = 3 时,应当返回: 3->2->1->4->5

你的算法只能使用常数的额外空间。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

解题思路

你可以想象把一个很长的链表分成很多个小链表,每一份的长度都是 k (最后一份的长度如果小于 k 则不需要反转),然后对每个小链表进行反转,最后将所有反转后的小链表按之前的顺序拼接在一起。

  • 第一,在反转子链表的时候,上一个子链表的尾必须知道
  • 第二,下一个子链表的头也必须知道
  • 第三,当前反转的链表的头尾都必须知道

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
if (head == null || head.next == null || k <= 1) {
return head;
}
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pointer = dummy;
while (pointer != null) {
// 记录上一个子链表的尾
ListNode lastGroup = pointer;
int i = 0;
for (; i < k; ++i) {
pointer = pointer.next;
if (pointer == null) {
break;
}
}
// 如果当前子链表的节点数满足 k, 就进行反转
// 反之,说明程序到尾了,节点数不够,不用反转
if (i == k) {
// 记录下一个子链表的头
ListNode nextGroup = pointer.next;
// 反转当前子链表,reverse 函数返回反转后子链表的头
ListNode reversedList = reverse(lastGroup.next, nextGroup);
// lastGroup 是上一个子链表的尾,其 next 指向当前反转子链表的头
// 但是因为当前链表已经被反转,所以它指向的是反转后的链表的尾
pointer = lastGroup.next;
// 将上一个链表的尾连向反转后链表的头
lastGroup.next = reversedList;
// 当前反转后的链表的尾连向下一个子链表的头
pointer.next = nextGroup;
}
}
return dummy.next;
}
private ListNode reverse(ListNode head, ListNode tail) {
if (head == null || head.next == null) {
return head;
}
ListNode prev = null, temp = null;
while ((head != null) && (head != tail)) {
temp = head.next;
head.next = prev;
prev = head;
head = temp;
}
return prev;
}
}

26 Remove Duplicates from Sorted Array

题目大意

数组去重并返回去重后的长度。

解题思路

本题主要记录Vector函数的使用。

代码

1
2
3
4
5
6
7
8
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
auto it =unique(nums.begin(),nums.end());
nums.resize(distance(nums.begin(),it));
return nums.size();
}
};

28 Implement strStr()

题目大意

找子串出现的第一次位置,如果没出现,就返回-1。

解题思路

纯暴力。KMP 实在太久远了,没去看。此外这道题在 LeetCode 是 Easy。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int strStr(String haystack, String needle) {
if (needle.equals("")) return 0;
if (haystack.equals("") || haystack.length() < needle.length()) return -1;
int index = 0;
for (int i = 0; i < haystack.length(); i++) {
int ni = 0;
int hi = i;
while (ni < needle.length()&&hi < haystack.length()) {
if (haystack.charAt(hi) == needle.charAt(ni)) {
if (ni == needle.length() - 1) {
return i;
}
ni++;
hi++;
} else {
break;
}
}
}
return -1;
}
}

30 Substring with Concatenation of All Words

题目大意

找出串联所有单词的字串,且每个单词只能出现一次(样例2因为good出现了不止一次)。单词的长度一致。返回索引。

1
2
3
4
5
6
7
8
9
10
Input:
s = "barfoothefoobarman",
words = ["foo","bar"]
Output: [0,9]
Explanation: Substrings starting at index 0 and 9 are "barfoo" and "foobar" respectively.
The output order does not matter, returning [9,0] is fine too.
Input:
s = "wordgoodgoodgoodbestword",
words = ["word","good","best","word"]
Output: []

解题思路

本题引入了HashMap,使得我们不需要纠结单词的排列顺序。这里引用一下别人的解答:

  1. 常规解法:先建立word中单词到次数的映射,因为某些单词可能不止出现一次。再使用新的HashMap建立一个映射用来记录当前的寻找过程中每个单词出现的次数。如果次数超过了words中的次数,直接退出,下一次寻找;如果单词不对,也退出。

  2. 解法一中,每次移动一个字符然后判断指定单词的数量是否合法。现在每次移动一个单词。

    以从0开始移动为例,看一下优化情况:

    ① 当子串完全匹配,移动到下一个子串的时候:

    完全匹配

    在1中,对于i = 3的子串,我们肯定是从第一个foo开始判断。但其实前两个foo都不用判断了 ,因为在判断上一个i=0的子串的时候我们已经判断过了。所以解法一中的HashMap2每次并不需要清空从0开始,而是可以只移除之前i=0子串的第一个单词bar即可,然后直接从箭头所指的foo开始就可以了。 、

    ② 当判断过程中,出现不符合的单词:

    不符合

    在判断i=0的时候出现了the,所以i=36都不用判断了,直接判断i=9。

    ③ 判断过程中,出现的是符合的单词,但是次数超了:

    对于i=0,虽然bar符合要求,但是bar出现了两次。往后移动窗口虽然删除了foo但是bar仍然两个,因此需要继续移动。直到bar被删除一个,i=6时候,就可以判断了。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//1
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<Integer>();
int wordNum = words.length;
if (wordNum == 0) {
return res;
}
int wordLen = words[0].length();
//HashMap1 存所有单词
HashMap<String, Integer> allWords = new HashMap<String, Integer>();
for (String w : words) {
int value = allWords.getOrDefault(w, 0);
allWords.put(w, value + 1);
}
//遍历所有子串
for (int i = 0; i < s.length() - wordNum * wordLen + 1; i++) {
//HashMap2 存当前扫描的字符串含有的单词
HashMap<String, Integer> hasWords = new HashMap<String, Integer>();
int num = 0;
//判断该子串是否符合
while (num < wordNum) {
String word = s.substring(i + num * wordLen, i + (num + 1) * wordLen);
//判断该单词在 HashMap1 中
if (allWords.containsKey(word)) {
int value = hasWords.getOrDefault(word, 0);
hasWords.put(word, value + 1);
//判断当前单词的 value 和 HashMap1 中该单词的 value
if (hasWords.get(word) > allWords.get(word)) {
break;
}
} else {
break;
}
num++;
}
//判断是不是所有的单词都符合条件
if (num == wordNum) {
res.add(i);
}
}
return res;
}
//2
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<Integer>();
int wordNum = words.length;
if (wordNum == 0) {
return res;
}
int wordLen = words[0].length();
HashMap<String, Integer> allWords = new HashMap<String, Integer>();
for (String w : words) {
int value = allWords.getOrDefault(w, 0);
allWords.put(w, value + 1);
}
// 将所有移动分成 wordLen 类情况
for (int j = 0; j < wordLen; j++) {
HashMap<String, Integer> hasWords = new HashMap<String, Integer>();
int num = 0; // 记录当前 HashMap2(这里的 hasWords 变量)中有多少个单词
// 每次移动一个单词长度
for (int i = j; i < s.length() - wordNum * wordLen + 1; i = i + wordLen) {
boolean hasRemoved = false; // 防止情况三移除后,情况一继续移除
while (num < wordNum) {
String word = s.substring(i + num * wordLen, i + (num + 1) * wordLen);
if (allWords.containsKey(word)) {
int value = hasWords.getOrDefault(word, 0);
hasWords.put(word, value + 1);
// 出现情况三,遇到了符合的单词,但是次数超了
if (hasWords.get(word) > allWords.get(word)) {
// hasWords.put(word, value);
hasRemoved = true;
int removeNum = 0;
// 一直移除单词,直到次数符合了
while (hasWords.get(word) > allWords.get(word)) {
String firstWord = s.substring(i + removeNum * wordLen, i + (removeNum + 1) * wordLen);
int v = hasWords.get(firstWord);
hasWords.put(firstWord, v - 1);
removeNum++;
}
num = num - removeNum + 1; // 加 1 是因为我们把当前单词加入到了 HashMap 2 中
i = i + (removeNum - 1) * wordLen; // 这里依旧是考虑到了最外层的 for 循环,看情况二的解释
break;
}
// 出现情况二,遇到了不匹配的单词,直接将 i 移动到该单词的后边(但其实这里
// 只是移动到了出现问题单词的地方,因为最外层有 for 循环, i 还会移动一个单词
// 然后刚好就移动到了单词后边)
} else {
hasWords.clear();
i = i + num * wordLen;
num = 0;
break;
}
num++;
}
if (num == wordNum) {
res.add(i);

}
// 出现情况一,子串完全匹配,我们将上一个子串的第一个单词从 HashMap2 中移除
if (num > 0 && !hasRemoved) {
String firstWord = s.substring(i, i + wordLen);
int v = hasWords.get(firstWord);
hasWords.put(firstWord, v - 1);
num = num - 1;
}

}

}
return res;
}
}

31 Next Permutation

题目大意

对于一个数组进行全排列后按照字典序排列,求某一种排列后的下一个排列顺序。例如,127341的下一个排列为131247。他是127341的下一个最小的排列。

解题思路

这道题主要是全排列规律的应用,对于一个全排列,他的字典序下一个排列有这么一个规律。

通过观察原数组可以发现,如果从末尾往前看,数字逐渐变大,到了2时才减小的,然后再从后往前找第一个比2大的数字,是3,那么我们交换2和3,再把此时3后面的所有数字转置一下即可,步骤如下:

1  2  7  4  3  1

1  2  7  4  3  1

1  3  7  4  2  1

1  3  1  2  4  7

代码

先看一种挂逼解法:

1
2
3
4
5
6
class Solution {
public:
void nextPermutation(vector<int>& nums) {
next_permutation(nums.begin(), nums.end());
}
};

按照上面解题思路的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
public void nextPermutation(int[] nums) {
// 先从后往前找到第一次出现升序的两个数,i指向前一个数
int n = nums.length;
int i = n - 2;
while (i >= 0 && nums[i] >= nums[i + 1])
i--;
// 然后从后向前找到第一个大于nums[i]的数
if (i >= 0) {
int j = nums.length - 1;
while (nums[j] <= nums[i])
j--;
// 交换2个下标对应的元素
swap(nums, i, j);
}
// 逆置i之后的所有元素
reverse(nums, i + 1);
}

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

public void reverse(int[] nums, int start) {
int i = start;
int j = nums.length - 1;
while (i < j) {
swap(nums, i, j);
i++;
j--;
}
}
}

32 Longest Valid Parentheses

题目描述

给定一个只包含左括号和右括号的字符串,找出最长的包含有效括号的子串的长度。

解题思路

例如,“(()”,先遍历一遍字符串 S,判断遍历到的字符 ch 是不是左括号,如果是的话,将此时 ch 的位置加入到栈中。

继续判断,发现又碰到了左括号,同样的,将它的位置添加到栈中。

如果遇到的元素是右括号,且栈不为空栈顶元素是左括号,就出栈。

如果当前栈为空,不管遇到什么元素,都入栈。

这样扫描完一轮,栈里存放的就是没有匹配到的位置。

最后考虑栈空的情况,当前的最长匹配就是当前指针位置+1,如果不为空,那么最长匹配就是当前指针位置减去栈顶元素位置。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.Stack;
class Solution {
public static int longestValidParentheses(String s) {
int res = 0;
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < s.length(); i++) {
if (!stack.isEmpty()) {
if (s.charAt(stack.peek()) == '(' && s.charAt(i) == ')') {
stack.pop();
} else {
stack.push(i);
}
} else {
stack.push(i);
}
if (!stack.isEmpty()) {
res = Math.max(res, i - stack.peek());
} else {
res = Math.max(res, i + 1);
}
}
return res;
}
}

33 Search in Rotated Sorted Array

题目描述

在旋转数组中查找目标,不存在返回-1。

1
2
Input: nums = [4,5,6,7,0,1,2], target = 0
Output: 4

解题思路

基本有序,使用二分法查找,判断下一次要搜索左半段还是右半段,根据中间值和左右的关系,判断哪边有序哪边无序,在不同区域使用不同的判断方法。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
if (nums[mid] == target) return mid;
if (nums[mid] < nums[right]){
if (nums[mid] < target && nums[right] >= target) left = mid + 1;
else right = mid - 1;
} else {
if (nums[left] <= target && nums[mid] >= target) right = mid;
else left = mid + 1;
}
}
return -1;
}
}

37 Sudoku Solver

题目描述

给定一个指定格式的数独棋盘,输出他的一个解。

解题思路

采用回溯法,对于没有填写的空,填写一个合法数字,如果无法填写合法数字,退回到上一层,填写合法数字。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Solution {
public void solveSudoku(char[][] board) {
solver(board);
}

private boolean solver(char[][] board) {
for (int i = 0; i < 9; i++)
for (int j = 0; j < 9; j++)
if (board[i][j] == '.') {
char count = '1';
while (count <= '9') {
if (isValid(i, j, board, count)) {
board[i][j] = count;
if (solver(board))
return true;
else
board[i][j] = '.';
}
count++;
}
return false;
}
return true;
}

private boolean isValid(int row, int col, char[][] board, char c) {
for (int i = 0; i < 9; i++)
if (board[row][i] == c)
return false;
for (int i = 0; i < 9; i++)
if (board[i][col] == c)
return false;
int start_row = row / 3 * 3;
int start_col = col / 3 * 3;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
if (board[start_row + i][start_col + j] == c)
return false;
return true;
}
}

38 Count and Say

题目大意

按照题述规律打印指定项。

1
2
3
4
5
6
7
8
1.     1
2. 11
3. 21
4. 1211
5. 111221
1 is read off as "one 1" or 11.
11 is read off as "two 1s" or 21.
21 is read off as "one 2, then one 1" or 1211.

解题思路

显然每一项都是上一项“数”出来的,因此写出相邻两项的转换规则之后递归解决就可以了。相邻两项就是数数。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
string count(string input) {
int i, j;
string resu = "";
for (i = 0; i < input.size(); i++) {
for (j = i; j < input.size(); j++) {
if (input[j] != input[i]) break;
}
resu += j - i + '0';
resu += input[i];
i = j - 1;
}
return resu;
}
string countAndSay(int n) {
if (n == 1) return "1";
string str = "1";
for (int i = 1; i < n; i++) {
str = count(str);
}
return str;
}
};

39 Conbination Sum

题目大意

给定数字集合,一个目标值,输出所有和等于目标值的组合。

解题思路

依旧是回溯法,找到一个解或者找不到可行解时候就回溯。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> list = new ArrayList<>();
backtrack(list, new ArrayList<>(), candidates, target, 0);
return list;
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int[] nums, int remain, int start) {
if (remain < 0)
return;
else if (remain == 0)
list.add(new ArrayList<>(tempList));
else {
for (int i = start; i < nums.length; i++) {
tempList.add(nums[i]);
backtrack(list, tempList, nums, remain - nums[i], i);
tempList.remove(tempList.size() - 1);
}
}
}
}

40 Combination Sum Ⅱ

题目大意

给定数字集合,一个目标值,输出所有和等于目标值的组合。数组中有重复数字,每个数字只能用一次。

解题思路

和上题类似,依旧回溯,这里为了避免元组会重复出现,先排序。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> list = new ArrayList<>();
Arrays.sort(candidates);
backtrack(list, new ArrayList<>(), candidates, target, 0);
return list;
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int[] nums, int remain, int start) {
if (remain < 0)
return;
else if (remain == 0) {
list.add(new ArrayList<>(tempList));
} else {
for (int i = start; i < nums.length; i++) {
// 避免多余计算,剪枝
if (i > start && nums[i] == nums[i - 1])
continue;
tempList.add(nums[i]);
// 每个数只能用一次,从自己的下一个计算
backtrack(list, tempList, nums, remain - nums[i], i + 1);
tempList.remove(tempList.size() - 1);
}
}
}
}

41 FIrst Missing Positive

题目大意

给定一个无序的整数数组,找到最小的缺失的正整数。要求 O(n) 级别的时间复杂度和常数级别的空间复杂度。

思路

这个题一开始没理解透,以为是找数列中缺少的那个数。实际上是指从1到 n 缺的最小的那个。

如果 nums 不缺任何数字的话,那么 nums 排序后,数字应该和索引是对应的。例如,[3, 4, -1, 1] 首先遇到 3,便将 3 放到 nums[2] 即第三个位置上,交换两个位置上的数。重复此操作,直到全部换完。即,[1, -1, 3, 4]。显然 -1 上的位置不对,返回 index+1。如果都在位置上,谁也不缺,返回下一个元素。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public int firstMissingPositive(int[] nums) {
int len = nums.length;
for (int i = 0; i < len; i++) {
while (nums[i] > 0 && nums[i] <= len && nums[nums[i] - 1] != nums[i]) {
swap(nums, i, nums[i] - 1);
}
}
for (int i = 0; i < len; i++) {
if (nums[i] != i + 1) {
return i + 1;
}
}
return len + 1;
}
public void swap(int[] nums, int left, int right) {
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
}
}

42 Trapping Rain Water

题目大意

求蓝色区域面积

1
2
Input: [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6

填雨水

解题思路

木桶理论中,桶能装的水高度取决于最短一块木板的高度。

在这题,当前槽能装多少水,取决于它左右最高的那块挡板的高度。因此先为每个槽寻找左右挡板最高高度,这个槽的储水量就是两个挡板最低的那个减掉槽高度。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public int trap(int[] height) {
if (height == null || height.length <= 2) return 0;
int len = height.length, temp = 0, res = 0;
int[] left = new int[len];
int[] right = new int[len];
for (int i = 0; i < len; i++) {
temp = Math.max(temp, height[i]);
left[i] = temp;
}
temp = 0;
for (int i = len - 1; i >= 0; i--) {
temp = Math.max(temp, height[i]);
right[i] = temp;
}
for (int i = 0; i < len; i++) {
res += Math.min(left[i], right[i]) - height[i];
}
return res;
}
}

43 Multiply Strings

题目大意

大数乘法,不能用大数类。

解题思路

这应该算是第一次规规矩矩写了大数乘法,都是因为师兄一句面试时候让我撕了。

写了两个函数,两个字符串相加,还有字符串乘字符。因为123*456就是123*6+123*5*10。所以最后拼接时候追加0。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class Solution {
public String multiply(String num1, String num2) {
// 特殊情况的处理
if (num1.equals("0") || num2.equals("0")) return "0";
if (num1.equals("1") || num2.equals("1")) return num1.equals("1") ? num2 : num1;
// 逐个和字符相乘 然后后面追加0相加
String resu = "0";
for (int i = num2.length() - 1; i >= 0; i--) {
String ts = mul(num1, num2.charAt(i));
for (int j = num2.length() - 1; j > i; j--) {
ts += "0";
}
System.out.println(ts);
resu = add(ts, resu);
}
return resu;
}
// 两个数相乘得到得字符串
public String mul(String num1, char c) {
// 从0开始算,算出来的数
String resu = "";
int flag = 0;
for (int i = num1.length() - 1; i >= 0; i--) {
int tmp = (num1.charAt(i) - '0') * (c - '0') + flag;
flag = tmp / 10;
tmp %= 10;
resu = tmp + resu;
}
if (flag != 0) resu = flag + resu;
return resu;
}
// 两个字符串相加
public String add(String num1, String num2) {
String resu = "";
int flag = 0;
int i = num1.length() - 1;
int j = num2.length() - 1;
while (i >= 0 && j >= 0) {
int tmp = num1.charAt(i) - '0' + num2.charAt(j) - '0' + flag;
flag = tmp / 10;
tmp %= 10;
resu = tmp + resu;
i--;
j--;
}
while (i >= 0) {
int tmp = num1.charAt(i) - '0' + flag;
flag = tmp / 10;
tmp %= 10;
resu = tmp + resu;
i--;
}
while (j >= 0) {
int tmp = num2.charAt(j) - '0' + flag;
flag = tmp / 10;
tmp %= 10;
resu = tmp + resu;
j--;
}
if (flag == 1) resu = '1' + resu;
return resu;
}
}

44 Wildcard Matching

题目大意

给定一个字符串 s 和 p,支持 “?” 和 “*” 的通配符匹配。

1
2
'?' 可以匹配任何单个字符
'*' 可以匹配任意字符串
  • s 可能为空,且只包含从 a-z 的小写字母。
  • p 可能为空,且只包含从 a-z 的小写字母,以及字符 ?*。

解题思路

建立数组用来记录,dp[i][j] 记录 s 的前 i 个字符是否可以编辑成 p 的前 j 个字符。

当遇见问号直接跳过了。要不然就三种情况。

1
2
3
dp[i-1][j-1]=true,且s.charAt(i-1)==p.charAt(j-1);
dp[i][j-1]=true,且p.charAt(j-1)=='*',这时表示‘*’匹配了0 个字符;
dp[i-1][j]=true,且p.charAt(j-1)=='*',这时表示‘*’匹配了s的第i个字符;

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length();
int n = p.length();
boolean[][] dp = new boolean[m + 1][n + 1];
dp[0][0] = true;
for (int j = 1; j <= n; j++) {
dp[0][j] = dp[0][j - 1] && p.charAt(j - 1) == '*';
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (p.charAt(j - 1) == '?') {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = (dp[i - 1][j - 1] && s.charAt(i - 1) == p.charAt(j - 1)) // 完全匹配了
|| (dp[i - 1][j] && p.charAt(j - 1) == '*') // * 匹配了0个字符
|| (dp[i][j - 1] && p.charAt(j - 1) == '*');// * 匹配了s的第i个字符
}
}
}
return dp[m][n];
}
}

46 Permutations

题目大意

给定一个集合,生成全排列。

解题思路

每次都将当前位置和后面位置交换,换到不能换了添加到结果中去。

深度搜索

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> resu = new ArrayList<>();
backtrack(nums, 0, resu);
return resu;
}

public void backtrack(int[] nums, int start, List<List<Integer>> resu) {
if (start >= nums.length) {
// 因为asList只能接受包装类型,因此需要自动装箱至 Integer
List<Integer> list = Arrays.stream(nums).boxed().collect(Collectors.toList());
resu.add(list);
}
// 把当前元素和后面每个元素交换,并在此基础上继续交换
for (int i = start; i < nums.length; i++) {
swap(nums, start, i);
backtrack(nums, start + 1, resu);
swap(nums, start, i);
}
}

public void swap(int[] nums, int left, int right) {
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
}
}

47 Permutations Ⅱ

题目大意

给定集合,生成全排列。与46不同,这些数字可能会重复。

解题思路

在 46 的基础上换用 set,遇到重复的不再添加。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.*;
import java.util.stream.Collectors;

class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
Set<List<Integer>> resu = new HashSet<>();
backtrack(nums, 0, resu);
return new ArrayList<>(resu);
}

public void backtrack(int[] nums, int start, Set<List<Integer>> resu) {
if (start >= nums.length) {
List<Integer> list = Arrays.stream(nums).boxed().collect(Collectors.toList());
resu.add(list);
}
for (int i = start; i < nums.length; i++) {
if (i != start && nums[i] == nums[start]) continue;
swap(nums, start, i);
backtrack(nums, start + 1, resu);
swap(nums, start, i);
}
}

public void swap(int[] nums, int left, int right) {
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
}
}

48 Rotate Image

题目大意

将矩阵顺时针旋转90°。

解题思路

将矩阵对角线对称,然后左右对称,即可。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
//先转置,再水平对称
void rotate(vector<vector<int>>& matrix) {
int l = matrix.size();
for (int i = 0; i < l; i++) {
for (int j = i+1; j < l; j++) {
swap(matrix[i][j], matrix[j][i]);
}
}
for (int i = 0; i < l; i++) {
for (int j = 0; j < l / 2 ; j++) {
swap(matrix[i][j], matrix[i][l - j - 1]);
}
}
}
};

49 Group Anagrams

题目大意

将属于一个全排列的字符串粗放到一起。

1
2
3
4
5
6
7
Input: ["eat", "tea", "tan", "ate", "nat", "bat"],
Output:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]

解题思路

事实上,对全排列的字符串排序的结果应该相同,以结果作为索引储存到 HashMap 中。

比如,“ate” “aet” “tae”排序后都是“aet”。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
HashMap<String, List<String>> map = new HashMap<>();
for (String str : strs) {
char[] astr = str.toCharArray();
Arrays.sort(astr);
// 这里一定要用String的方法转,直接toString是把对象转了
String key = String.valueOf(astr);
if (!map.containsKey(key)) {
map.put(key, new ArrayList<>());
}
map.get(key).add(str);
}
return new ArrayList<List<String>>(map.values());
}
}

53 Maximum Subarray

题目大意

子数组最大和

解题思路

贪心,累加和,如果算到当前数字总和小于0了,说明这一步加的是非常小的一个复数,直接恢复0,因为负数会对后面的数产生影响。

代码

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public int maxSubArray(int[] nums) {
int max = nums[0];
int sum = 0;
for (int num: nums){
sum += num;
max = Math.max(max, sum);
sum = sum <= 0? 0: sum;
}
return max;
}
}

54 Spiral Matrix

题目大意

矩阵螺旋遍历。

1
2
3
4
5
6
7
Input:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
Output: [1,2,3,6,9,8,7,4,5]

解题思路

用变量left, right, top, bottom记录左,右,顶,底。然后按照左到右,顶到底,右到左,底到顶的顺序循环,把遍历的元素加入到结果。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//1
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>> &matrix) {
vector<int> res;
if (matrix.empty() || matrix[0].empty()) return res;
int m = matrix.size(), n = matrix[0].size();
int c = m > n ? (n + 1) / 2 : (m + 1) / 2;
int p = m, q = n;
for (int i = 0; i < c; ++i, p -= 2, q -= 2) {
for (int col = i; col < i + q; ++col) res.push_back(matrix[i][col]);
for (int row = i + 1; row < i + p; ++row)
res.push_back(matrix[row][i + q - 1]);
if (p == 1 || q == 1) break;
for (int col = i + q - 2; col >= i; --col)
res.push_back(matrix[i + p - 1][col]);
for (int row = i + p - 2; row > i; --row)
res.push_back(matrix[row][i]);
}
return res;
}
};
//2
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> ans;
if (matrix.size() == 0) {
return ans;
}
//上下左右四个边界
int up = 0, down = matrix.size() - 1, left = 0, right = matrix[0].size() - 1;
while (up <= down && left <= right) {
//从左到右
if (up < matrix.size()) {
for (int i = left; i <= right; ++i) {
ans.push_back(matrix[up][i]);
}
up++;
}
//从上到下
if (right >= 0) {
for (int i = up; i <= down; ++i) {
ans.push_back(matrix[i][right]);
}
right--;
}
//从右往左
if (down >= up) {
for (int i = right; i >= left; --i) {
ans.push_back(matrix[down][i]);
}
down--;
}
//从下到上
if (left <= right) {
for (int i = down; i >= up; --i) {
ans.push_back(matrix[i][left]);
}
left++;
}
}
return ans;
}
};

56 Merge Intervals

题目大意

合并区间,输出最后的区间结果。

解题思路

贪心,将元素按照两个位排序之后,贪心建立区间,具体原理可以看代码中的注释。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.Arrays;
import java.util.Comparator;

class Solution {
public int[][] merge(int[][] intervals) {
// 对二维数组首元素按照升序排列
Comparator<int[]> comparator = new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
if (o1[0] == o2[0]) {
return o1[1] - o2[1];
} else {
return o1[0] - o2[0];
}
}
};
Arrays.sort(intervals, comparator);
// 创建结果数组
int[][] resu = new int[intervals.length][2];
// 结果集的索引
int idx = -1;
for (int[] interval : intervals) {
// 如果结果集为空,新键一个区间,或者当前区间的元素比结果集中最后一个区间的末尾大,说明非重叠区间
if (idx == -1 || interval[0] > resu[idx][1]) {
resu[++idx] = interval;
} else if (interval[0] <= resu[idx][1]) {
// 重叠了,选择最大的元素作为区间末尾
resu[idx][1] = Math.max(resu[idx][1], interval[1]);
}
}
return Arrays.copyOf(resu, idx + 1);
}
}

62 Unique Paths

题目大意

机器人从右上角走向右下角,每次只能往下或者往右走,问有多少种走法。

解题思路

动态规划,如果机器人在终点所在的行列出发,毫无疑问,只有一种方法可达。如果从别的地方出发,它的方案个数等于下方的个数+右边的个数。即每个结果都依赖于右下角的数。将矩阵旋转180就可以转换为从依赖于左上角,方便计算。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public int uniquePaths(int m, int n) {
if (m == 1 || n==1) return 1;
int[][] dp = new int[n+1][m+1];
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
if (i == 1 || j == 1) dp[i][j] = 1;
else {
dp[i][j] = dp[i][j-1] + dp[i-1][j];
}
}
}
return dp[n][m];
}
}

63 Unique Paths Ⅱ

题目大意

机器人从右上角走向右下角,每次只能往下或者往右走,矩阵位置为1的地方不能走,问有多少种走法。

解题思路

和62差不多的思路,但是这道题加了限制路径不走过值为1的地方,因此如果某个位置为1那么经过他的路径为0。这样的话,边界条件也要改了,虽然机器人和终点在同一行或列但是中间有1也是白给。因此边界条件要单独拿出来判断。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public int uniquePathsWithObstacles(int[][] o) {
int m = o.length, n = o[0].length;
if (m == 0 || n == 0) return 1;
int[][] dp = new int[m+1][n+1];
dp[0][1] = dp[1][0] = 1;
for (int i = 1; i <= m; i++){
for (int j = 1; j <= n; j++){
if (i == 1){
dp[i][j] = o[i-1][j-1] == 0 ? dp[i][j-1]: 0;
} else if (j == 1){
dp[i][j] = o[i-1][j-1] == 0 ? dp[i-1][j]: 0;
} else {
dp[i][j] = o[i-1][j-1] == 1 ? 0: (dp[i-1][j] + dp[i][j-1]);
}
}
}
return dp[m][n];
}
}

64 最小路径和

题目大意

给出一个二维矩阵,找出一条从左上角到右下角的路径, 使得路径上的数字总和最小。

解题思路

左上角到右下角的路径最小,就是从右下角往左上角寻找短路径和,非常简单的动态规划。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int minPathSum(int[][] grid) {
for (int i = grid.length - 1; i >= 0; i--) {
for (int j = grid[i].length - 1; j >= 0; j--) {
if (i == grid.length - 1 && j == grid[i].length - 1) {
// 啥也不做
} else if (i == grid.length - 1) {
grid[i][j] +=grid[i][j + 1];
} else if (j == grid[i].length - 1) {
grid[i][j] += grid[i + 1][j];
} else {
grid[i][j] += Math.min(grid[i + 1][j], grid[i][j + 1]);
}
}
}
return grid[0][0];
}
}

69 Sqrt(x)

题目大意

一个数 x 的开方 sqrt 一定在 0 ~ x 之间,并且满足 sqrt == x / sqrt。可以利用二分查找在 0 ~ x 之间查找 sqrt。

解题思路

对于 x = 8,它的开方是 2.82842…,最后应该返回 2 而不是 3。在循环条件为 l <= h 并且循环退出时,h 总是比 l 小 1,也就是说 h = 2,l = 3,因此最后的返回值应该为 h 而不是 l。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int mySqrt(int x) {
if (x <= 1) return x;
int left = 1, right = x, sqrt = 0;
while (left <= right) {
int mid = left + (right - left) / 2;
sqrt = x / mid;
if (sqrt == mid){
return mid;
} else if (sqrt < mid) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return right;
}
}

70 Climbing Stairs

题目大意

数字拆解,1和2的组合。

1
2
3
4
5
6
7
8
9
10
11
12
Input: 2
Output: 2
Explanation: There are two ways to climb to the top.
1. 1 step + 1 step
2. 2 steps

Input: 3
Output: 3
Explanation: There are three ways to climb to the top.
1. 1 step + 1 step + 1 step
2. 1 step + 2 steps
3. 2 steps + 1 step

解题思路

dp[i] = dp[i-1] + dp[i-2] ,因为每次走一步或者两步, 所以dp[i]的方法就是它一步前和两步前方法加和 。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//递归在数量增长时候会超时
class Solution {
public:
int climbStairs(int n) {
// cout << n << endl;
if (n <= 1) return 1;
return (climbStairs(n - 1)) + (climbStairs(n - 2));
}
};
//DP解法
class Solution {
public:
int climbStairs(int n) {
if (n <= 1) return 1;
vector<int> dp(n);
dp[0] = 1; dp[1] = 2;
for (int i = 2; i < n; ++i) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp.back();
}
};

71 Simplify Path

题目大意

简化文件路径,比如"…“代表返回上一级,”."表示当前目录,目的是为了将给定的目录翻译为简单目录。

1
2
Input: "/a/./b/../../c/"
Output: "/c"

解题思路

将字符串切割后,按照不同规则压入栈,特别注意,根目录下遇到返回上一级就不再做处理了。同样的,如果解析到最后,栈里啥都没有,要返回根目录。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
public String simplifyPath(String path) {
// 先针对//进行分割,然后根据每个的状态进行出栈和入栈操作
String[] paths = path.split("/");
Stack<String> strings = new Stack<>();
for (int i = 0; i < paths.length; i++) {
switch (paths[i]) {
case "..":
if (!strings.isEmpty()) {
strings.pop();
}
break;
case ".":
break;
case "":
break;
default:
strings.push(paths[i]);
}
}
List<String> list = new ArrayList<>();
while (!strings.isEmpty()) {
list.add(strings.pop());
}
StringBuilder stringBuilder = new StringBuilder();
for (int i = list.size() - 1; i >= 0; i--) {
stringBuilder.append("/" + list.get(i));
}
if (list.size() == 0) return "/";
return stringBuilder.toString();
}
}

72 Edit Distance

题目大意

给定两个单词,计算将单词1转换为单词2所使用的最少操作数。

可以对一个单词进行如下操作,插入一个字符,删除一个字符,替换一个字符。

解题思路

通过动态规划解决问题,通过两个指针 ij 分别指向 word1 和 word2。定义一个数组 dp[i][j] 表示 word1[:i] 转换到 word2[:j] 需要的最少步骤。

需要先比较 word1[i-1] 和 word2[j-1] 是不是相同,如果相同的话,不用做任何操作,所以此时 dp[i][j] = dp[i-1][j-1],即看前面的串了。

对于不相同的时候,情况比较复杂,有三种处理手段,分别是插入、替换和删除。先看插入操作,插入之后,也就是 word1 中的元素不变,j的位置往前挪一个,相当于匹配了这个,dp[i][j] = dp[i][j-1]+1。

考虑替换操作,相当于都忽略掉了当前位置的元素,因为会替换成需要的,dp[i][j] = dp[i][j-1]+1。

最后考虑删除操作,在单词1中删除即可,dp[i][j] = dp[i-1][j]+1。

最后取最小值即可。

初始条件即有一个单词为空的情况下,要不然插入到相同,要不然删除到相同。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int minDistance(String word1, String word2) {
int len1 = word1.length();
int len2 = word2.length();
int[][] dp = new int[len1 + 1][len2 + 1];
for (int i = 0; i < len1 + 1; i++) {
dp[i][0] = i;
}
for (int i = 0; i < len2 + 1; i++) {
dp[0][i] = i;
}
for (int i = 1; i < len1 + 1; i++) {
for (int j = 1; j < len2 + 1; j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(dp[i - 1][j], Math.min(dp[i][j - 1], dp[i - 1][j - 1])) + 1;
}
}
}
return dp[len1][len2];
}
}

75 Sort Colors

题目大意

一串0 1 2构成的数字,排序,要求尽可能只遍历一次。

1
2
Input: [2,0,2,1,1,0]
Output: [0,0,1,1,2,2]

解题思路

  1. 统计个数,然后安排。
  2. 根据题目要求,我们只能遍历array一次,可以用到two pointers来实现。设一个指针red 在开头,blue 在最后。想法就是,遇到红色0,就交换,把0放到最左边去;遇到蓝色2就交换,把2都放到最右边去,这样1就会被保留在最中间。需要注意的是,当把蓝色2交换完毕之后,需要i–, 停留 i 在原地一次,因为还需要继续检查 被2交换回来的数字。那当遇到红色0,交换完毕不需要停留i 的原因是, 交换回来的只可能是1,对于1,我们不需要做任何处理,直接过就可以。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//思路1
class Solution {
public:
void sortColors(vector<int>& nums) {
int a[3] = {0};
for (int i = 0; i < nums.size(); i++) a[nums[i]]++;
for (int i = 0; i < nums.size(); i++) {
if (a[0] > 0) {
nums[i] = 0;
a[0]--;
} else if (a[1] > 0) {
nums[i] = 1;
a[1]--;
} else if (a[2] > 0) {
nums[i] = 2;
a[2]--;
}
}
}
};
//思路2
class Solution {
public:
void sortColors(vector<int> nums) {
int red = 0;
int blue = nums.size() - 1;
for (int i = 0; i <= blue; i++) {
if (nums[i] == 0) // if find 0, swap with red pointer
{
int temp = nums[i];
nums[i] = nums[red];
nums[red] = temp;
red++;
} else if (nums[i] == 2) // if find 2, swap with blue pointer
{
int temp = nums[i];
nums[i] = nums[blue];
nums[blue] = temp;
i--;
blue--;
}
}
}
};

78 子集

题目大意

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

解题思路

每个元素都有取或者不取两种选择,从空集开始逐个增加。也可以参考重复元素的子集Ⅱ题目代码。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public List<List<Integer>> subsets(int[] array) {
List<List<Integer>> resu = new ArrayList<>();
resu.add(new ArrayList<>());
for (int i = 0; i < array.length; i++) {
int size = resu.size();
for (int j = 0; j < size; j++) {
// 注意 Java 传值问题
List<Integer> tmp = new ArrayList<>(resu.get(j));
tmp.add(array[i]);
resu.add(new ArrayList<>(tmp));
}
}
return resu;
}
}

80 删除有序数组中的重复项 Ⅱ

题目大意

给定有序数组,原地删除重复出现元素,使得每个元素最多出现两次,返回删除后数组的长度。需要原地修改数组并使用O(1)额外空间的条件下完成。

解题思路

快慢指针,快指针表示遍历到的元素,慢指针表示当前要放的位置,使用index-2检查重复了几项。与之类似的还有26题。

代码

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public int removeDuplicates(int[] nums) {
int index = 0;
for (int num : nums) {
if (index < 2 || nums[index - 2] < num) {
nums[index] = num;
index++;
}
}
return index;
}
}

82 Remove Duplicates from Sorted List II

题目大意

删除所有重复的节点,使得重复出现的节点一个不剩。

解题思路

先把重复的删了之后,再把剩下的那个跳过。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode preHead = new ListNode(-1);
preHead.next = head;
ListNode pre = preHead;
while (pre.next != null){
ListNode cur = pre.next;
while (cur.next != null && cur.val == cur.next.val){
cur = cur.next;
}
if (cur != pre.next){
pre.next = cur.next;
} else {
pre = pre.next;
}
}
return preHead.next;
}
}

83 Remove Duplicates from Sorted Lsit

题目大意

给出一个排好序的链表,删除所有连续出现的节点,使得每个几点只出现一次。

解题思路

  1. 递归:如果头节点重复了,删除头节点;否则的话,递归头节点的next。
  2. 迭代:找到一个删除一个。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/// 1 递归
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null)
return head;
if (head.val == head.next.val)
return deleteDuplicates(head.next);
else {
head.next = deleteDuplicates(head.next);
return head;
}
}
}
// 2 迭代
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode cur = head;
while (cur != null && cur.next != null) {
// 元素相同,删除
if (cur.val == cur.next.val)
cur.next = cur.next.next;
// 不相同,当前节点后移
else
cur = cur.next;
}
return head;
}
}

86 PartitionList

题目大意

给定一个链表,将小于X的结点放到所有大于等于X的结点的前面,不要修改节点之间的顺序。

解题思路

  1. 类似快排的思路,建立哨兵结点,找到第一个大于等于X的结点的位置,之后遍历结点,将所有小于值X的结点提到这个位置。
  2. 两个链表,小于分区点一个大于的一个。根据值的情况改变指针即可。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 解法1
public class Solution {
public ListNode partition(ListNode head, int x) {
ListNode preHead = new ListNode(0);
preHead.next = head;
ListNode pre = preHead;
ListNode cur = head;
ListNode insPos = null;
while (cur != null) {
if (cur.val >= x && insPos == null) {
insPos = pre;
}
if (cur.val < x && insPos != null) {
pre.next = pre.next.next;
cur.next = insPos.next;
insPos.next = cur;
insPos = insPos.next;
cur = pre.next;
continue;
}
pre = pre.next;
cur = cur.next;
}
return preHead.next;
}
}
// 解法2
public ListNode partition(ListNode head, int x) {
//小于分区点的链表
ListNode min_head = new ListNode(0);
ListNode min = min_head;
//大于等于分区点的链表
ListNode max_head = new ListNode(0);
ListNode max = max_head;

//遍历整个链表
while (head != null) {
if (head.val < x) {
min.next = head;
min = min.next;
} else {
max.next = head;
max = max.next;
}

head = head.next;
}
max.next = null; //这步不要忘记,不然链表就出现环了
//两个链表接起来
min.next = max_head.next;
return min_head.next;
}

88 Merge Sorted Array

题目大意

合并两个有序vector。

解题思路

  1. 将nums2复制进nums1然后排序。
  2. 高级一点,因为nums1预留了空间,所以可以从后往前存储。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int i = m - 1, j = n - 1, k = m + n - 1;
while (i >= 0 && j >= 0) {
if (nums1[i] > nums2[j])
nums1[k--] = nums1[i--];
else
nums1[k--] = nums2[j--];
}
while (j >= 0) nums1[k--] = nums2[j--];
}
};

89 Gray Code

题目大意

给一个编码总位数n,打印格雷码序列,格雷码必须以0开头。

2——>0,1,3,2/0,2,3,1

解题思路

解法一

参考链接 将样例写出,找规律。

找规律

当n=2时候,它的结果包括n=1时候结果左边补0,以及逆序遍历n=1时候的结果左边补1。根据规律可知,当n=k+1的情况下,对n=k的结果左边补0,然后逆序对n=k的结果左边补1即可。

解法二

格雷码的数学计算方法,由对应的十进制求出。grayCode=i ^ (i >>1),就是自己与自己右移一位进行异或。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//解法一
class Solution {
public List<Integer> grayCode(int n) {
List<Integer> rs = new ArrayList<Integer>();
rs.add(0);
for (int i = 0; i < n; i++) {
int size = rs.size();
for (int k = size - 1; k >= 0; k--)
rs.add(rs.get(k) | 1 << i);
}
return rs;
}
}
//解法二 公式法
class Solution {
public List<Integer> grayCode(int n) {
List<Integer> rs = new ArrayList<Integer>();
for (int i = 0; i < 1 << n; i++)
rs.add(i ^ i >> 1);
return rs;
}
}

90 子集 Ⅱ

题目大意

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

解题思路

按照子集中元素个数逐个生成。每个元素都可以有或者没有。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
private List<List<Integer>> res = new ArrayList<>();

public List<List<Integer>> subsetsWithDup(int[] array) {
// 排序方便去重
Arrays.sort(array);
List<Integer> path = new ArrayList<>();
// i 表示子集长度,长度为n的集合的子集最长为n即全子集
for (int i = 0; i <= array.length; i++) {
dfs(i, 0, array, path);
}
return res;
}

private void dfs(int size, int start, int[] array, List<Integer> path) {
// 跳出条件,空集或者已经找到的集合长度刚好是要找的长度
if (size == 0 || path.size() == size) {
res.add(new ArrayList<>(path));
return;
}
// 从起点元素开始找
for (int i = start; i < array.length; i++) {
// 去重
if (i > start && array[i] == array[i - 1]) {
continue;
}
// 包含当前节点
path.add(array[i]);
dfs(size, i + 1, array, path);
// 不包含
path.remove(path.size() - 1);
}
}
}

92 Reverse Linked List II

题目大意

反转链表中的第 m 个到第 n 个。使用一次遍历。

解题思路

看代码吧,不好讲。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public ListNode reverseBetween(ListNode head, int m, int n) {
if (head == null || head.next == null || m > n || m < 0 || n < 0) {
return head;
}
ListNode p = new ListNode(-1);
p.next = head;
ListNode pre = p;
for (int i = 1; i < m; i++) {
pre = pre.next;
}
ListNode cur = pre.next;
for (int i = m; i < n; i++) {
ListNode tmp = cur.next;
cur.next = tmp.next;
tmp.next = pre.next;
pre.next = tmp;
}
return p.next;
}
}

93 Restore IP Addresses

题目大意

给一个没有分割的字符串,将其还原为 IP 地址。

解题思路

代码中有注释。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import java.util.ArrayList;
import java.util.List;

class Solution {
/**
* s 字符串,n 还剩几个ip没分割,index 当前切到哪里了,ip 已经生成的ip, result 结果集
*/
public static void backtrack(String s, int n, int index, String ip, List<String> result) {
// 生成完毕
if (n == 0) {
if (index == s.length()) {
result.add(ip);
}
return;
}
// 切割验证,可以就继续回溯
for (int i = index + 1; i < s.length() + 1; i++) {
if (isNum(s.substring(index, i))) {
backtrack(s, n - 1, i, ip.equals("") ? s.substring(index, i) : ip + "." + s.substring(index, i), result);
} else {
break;
}
}
}

/**
* 验证数据是否合法,同时防止001这种情况的出现
*/
public static boolean isNum(String num) {
int n = Integer.parseInt(num);
if (n >= 0 && n <= 255 && String.valueOf(n).equals(num)) {
return true;
}
return false;
}

public static List<String> restoreIpAddresses(String s) {
List<String> list = new ArrayList<>();
if (s == null || s.length() < 4 || s.length() > 12) {
return list;
}
backtrack(s, 4, 0, "", list);
return list;
}

public static void main(String[] args) {
System.out.println(restoreIpAddresses("25525511135"));
}
}

96 Unique Binary Search Tree

题目大意

给一个n,求1到n这些数可以构成多少个二叉搜索树。

解题思路

  1. 递归

  2. 动态规划

思路基本一致,都是分开左右子树算,不同的是动态规划储存了数组。还有一个要注意的是,左右子树的个数最终要相乘而不是想加。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 1 递归
class Solution {
private int getAns(int n) {
if (n == 1 || n == 0)
return 1;
int sum = 0;
// 左子树从0到n-1,因为还得给根节点一个
for (int i = 0; i < n; i++) {
int left = getAns(i);
int right = getAns(n - i - 1);
sum += left * right;
}
return sum;
}
public int numTrees(int n) {
if (n == 0)
return 0;
return getAns(n);
}
}
// 动态规划
public int numTrees(int n) {
int[] dp = new int[n + 1];
dp[0] = 1;
if (n == 0) {
return 0;
}
// 长度为 1 到 n
for (int len = 1; len <= n; len++) {
// 将不同的数字作为根节点
for (int root = 1; root <= len; root++) {
int left = root - 1; // 左子树的长度
int right = len - root; // 右子树的长度
dp[len] += dp[left] * dp[right];
}
}
return dp[n];
}

110 Balanced Binary Tree

题目大意

平衡二叉树

解题思路

老掉牙的题,之前在一篇博客上看过一种自底向上的算法,大大减少了计算次数。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 底往上
class Solution {
public boolean isBalanced(TreeNode root) {
return ban(root) != -1;
}
public int ban(TreeNode cur){
if (cur == null) return 0;
int left = ban(cur.left);
if (left == -1) return -1;
int right = ban(cur.right);
if (right == -1) return -1;
return Math.abs(left - right) <= 1? Math.max(left, right) + 1: -1;
}
}
// 递归算法
class Solution {
public boolean isBalanced(TreeNode root) {
if (root == null) return true;
return Math.abs(maxHigh(root.left) - maxHigh(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}
public int maxHigh(TreeNode cur){
if (cur == null) return 0;
return Math.max(maxHigh(cur.left), maxHigh(cur.right)) + 1;
}
}

121 Best Time to Buy and Sell Stock

题目大意

决定股票买卖最佳时机。

1
2
3
Input: [7,1,5,3,6,4]
Output: 5
1 - 6

解题思路

先确定一个买入点,以后假设第 i 天卖,正数就更新最大值,负数说明现在底谷期,不抛后面收益肯定小于等于现在,抄底冲。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) return 0;
int max = 0;
int start = prices[0];
for (int i = 1; i < prices.length; i++){
if (prices[i] - start > max){
max = prices[i] - start;
} else if (prices[i] - start < 0) {
start = prices[i];
}
}
return max;
}
}

123 Best Time to Buy and Sell Stock III

题目大意

买卖股票问题,只允许最多两次买卖。

解题思路

  • buy1=max(buy1,−prices[i])
  • sell1=max(sell1,buy1+prices[i])sell1 = max(sell1, buy1 + prices[i])sell1=max(sell1,buy1+prices[i])
  • buy2=max(buy2,sell1−prices[i])buy2 = max(buy2, sell1 - prices[i])buy2=max(buy2,sell1−prices[i])
  • sell2=max(sell2,buy2+prices[i])sell2 = max(sell2, buy2 + prices[i])sell2=max(sell2,buy2+prices[i])

然后就是考虑边界问题,很显然buy1[0]=-prices[0],而sell1=0(相当于买入后再卖出)、buy2-prices[0](相当于买入后再卖出再买入)、sell2=0(相当于买入后再卖出再买入再卖出)。

注意这里 buy 是复数。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public int maxProfit(int[] prices) {
int buy1 = -prices[0];
int sell1 = 0;
int buy2 = -prices[0];
int sell2 = 0;
for (int i = 1; i < prices.length; i++) {
buy1 = Math.max(buy1, -prices[i]);
sell1 = Math.max(sell1, buy1 + prices[i]);
buy2 = Math.max(buy2, sell1 - prices[i]);
sell2 = Math.max(sell2, buy2 + prices[i]);
}
return sell2;
}
}

124 Binary Tree Maximum Path Sum

题目大意

给定非空二叉树,找到最大路径总和,可能不过根节点。

1
2
3
4
5
6
7
8
9
Input: [-10,9,20,null,null,15,7]

-10
/ \
9 20
/ \
15 7

Output: 42

解题思路

一棵树的路径,从一个开始节点出发,向上走 0 或者 k 步, 到达某一个根节点,然后再往下走 0 或者 j 步。一旦它往下走。一旦它往下走,就不会再上升。因此,每个路径都有一个最高节点,也是这条路径上其他节点的最低公共祖先。

采用递归策略解决,对于某一个节点,我们会递归地计算它的左子树和右子树的最大的路径之和,然后判断当前节点作为根节点时,路径的值是否是当前所遍历到的所有节点的最大值。然后如果当它不是根节点,返回他作为一串链上的值。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
int maxL = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
dfs(root);
return maxL;
}
public int dfs(TreeNode cur){
if (cur == null) return 0;
int left = dfs(cur.left);
left = left > 0? left : 0;
int right = dfs(cur.right);
right = right > 0? right : 0;
// 作为根节点,直接更新
maxL = Math.max(maxL, cur.val + left + right);
// 返回往下的链的深度,参与它的父节点的运算
return cur.val + Math.max(left, right);
}
}

125 Valid Palindrome

题目大意

给定一个字符串,判断是不是回文字符串,只考虑数字和字母且不考虑大小写。

解题思路

讲字符串清洗后,判断反转后是否相同即可。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public boolean isPalindrome(String s) {
if (s == null || s.length() <= 1) return true;
// 处理空格
String tmp = "";
for (int i = 0; i < s.length(); i++) {
if ((s.charAt(i) >= 'A' && s.charAt(i) <= 'Z') || (s.charAt(i) >= '0' && s.charAt(i) <= '9')) {
tmp += s.charAt(i);
} else if (s.charAt(i) >= 'a' && s.charAt(i) <= 'z') {
tmp += (char) (s.charAt(i) - 32);
}
}
StringBuilder sb = new StringBuilder(tmp);
System.out.println(sb.toString());
return sb.toString().equals(sb.reverse().toString());
}
}

128 Longest Consecutive Sequence

题目大意

给定一个未排序的数组,找出其中最长的连续子序列的长度,元素不必在一起。要求算法复杂度在O(n)。

1
2
3
Input: [100, 4, 200, 1, 3, 2]
Output: 4
Explanation: The longest consecutive elements sequence is [1, 2, 3, 4]. Therefore its length is 4.

解题思路

这道题是舍友发给我的,初上手没有思路,觉得排序之后寻找即可,排序算法nlogn显然不符合要求。

换做了 HashSet 虽然保证了有序和复杂度的问题,但是因为还要将数组转换为Set,相当于在logn外面又套了一个n。

最后看到了题解使用的 HashSet,而且在查找时候也有很强的优化。最差也是2n。下面摘自题解

仔细分析这个过程,我们会发现其中执行了很多不必要的枚举,如果已知有一个 x, x+1, x+2, \cdots, x+yx,x+1,x+2,⋯,x+y 的连续序列,而我们却重新从 x+1x+1,x+2x+2 或者是 x+yx+y 处开始尝试匹配,那么得到的结果肯定不会优于枚举 xx 为起点的答案,因此我们在外层循环的时候碰到这种情况跳过即可。

那么怎么判断是否跳过呢?由于我们要枚举的数 xx 一定是在数组中不存在前驱数 x-1x−1 的,不然按照上面的分析我们会从 x-1x−1 开始尝试匹配,因此我们每次在哈希表中检查是否存在 x-1x−1 即能判断是否需要跳过了。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> set = new HashSet<>();
for (int num : nums) {
set.add(num);
}
int longlen = 0;
for (int num : nums) {
if (!set.contains(num - 1)) {
int currentNum = num;
int len = 1;
while (set.contains(currentNum + 1)) {
currentNum++;
len++;
}
longlen = Math.max(longlen, len);
}
}
return longlen;
}
}

135 Candy

题目大意

老师给孩子们分糖果,有 N 个孩子。

  • 每个孩子至少分配一个糖果。
  • 相邻的孩子中评分最高的必须获得更高的水果。

问老师需要准备多少糖果。

1
2
3
4
5
6
输入:[1,0,2]
输出:5
发2、1、2
输入:[1,2,2]
输出:4
发1、2、1

解题思路

  • 正序遍历,如果后一位比前一位高分,就给比前一位多1的糖果,否则给1。
  • 再倒序遍历,如果前一位比后一位高分并且得到的糖果小于等于后一位,就给前一位孩子比后一位多一个糖果。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public int candy(int[] ratings) {
if (ratings == null || ratings.length == 0) {
return 0;
}
int[] tmp = new int[ratings.length];
for (int i = 0; i < tmp.length; i++) {
tmp[i] = 1;
}
int resu = 0;
for (int i = 1; i < ratings.length; i++) {
if (ratings[i] > ratings[i - 1]) {
tmp[i] = tmp[i - 1] + 1;
}
}
for (int i = ratings.length - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1] && tmp[i] <= tmp[i + 1]) {
tmp[i] = tmp[i + 1] + 1;
}
}
for (int t : tmp) {
resu += t;
}
return resu;
}
}

139 Word Break

题目大意

判断字符串是否可以由字典里的字符串组成,可以重复。

解题思路

一开始想用递归写,但是发现好像结果的或不太好写。在别人的博客里看来了一种动态规划,看来 leetcode 上的题目,别问,解不出来就动态规划。

设置标记数组,dp[i] 表示第 i 个位置的结果。这里注意第 i 个应该是索引 + 1。对于每个以 i 结尾字串 A,判断是不是在字典里或者他是由一个已经由字典拼接的字符串和字典里的字符串拼接而成。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
if (s == null || s.length() == 0) return true;
boolean[] result = new boolean[s.length() + 1];
result[0] = true;
for (int i = 0; i < s.length(); i++){
StringBuilder sb = new StringBuilder(s.substring(0, i + 1));
// 遍历字串,看看是不是由一个已经拼出来的和字典里的组合而成
for (int j = 0; j <= i; j++){
if (result[j] && wordDict.contains(sb.toString())){
// 因为数组的索引从 0 开始,而结果数组的 i 表示次序,需要 + 1
result[i + 1] = true;
break;
}
sb.deleteCharAt(0);
}
}
return result[s.length()];
}
}

146 LRU Cache

题目大意

根据给出的代码完善一个 LRUCache。

解题思路

通过使用 LinkedHashMap 实现顺序存放键值对,之后再实现 LRU 的特性。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class LRUCache {
private int capacity;
private LRULinkedHashMap<Integer, Integer> linkedHashMap = new LRULinkedHashMap<>();

public LRUCache(int capacity) {
this.capacity = capacity;
}

// 这里实现一个新的类,用于重写一个方法,通过覆盖该方法加入一定的条件,满足条件返回 true, 当 put 新的值返回 true,就移除该 map 中最老的键值对。
private class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
if (size() > capacity) {
return true;
} else {
return false;
}
}
}

public int get(int key) {
Integer value = linkedHashMap.get(key);
if (value == null){
return -1;
}
linkedHashMap.remove(key);
linkedHashMap.put(key, value);
return value;
}

public void put(int key, int value) {
if (linkedHashMap.containsKey(key)){
linkedHashMap.remove(key);
}
linkedHashMap.put(key, value);
}
}

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

152 Maximum Product Subarray

题目大意

寻找连续数组乘积的最大值。

解题思路

看起来和最大子序列和一致,但是又有不同。因为前面算出的负数一时不是最大值,但很有可能因为遇上一个负数,摇身一变就变成了一个正数。

因此使用数组记录下最大最小值,用来避免这种情况。这里使用 dp[i][] 来表示 i 位置的最大最小值。根据当前位置的正负来计算数组的最大最小值。当前位置为正数,与前一位的最大最小值的乘积显然还保持了最大最小值的乘积。但是如果当前的数是负数,最大值就变成了最小值。不仅如此,之前位置的最值得正负也有影响,因此需要最值函数获取一下。

因为数组当中存得是以 index 为结尾的数组最值,因此还需要挨个循环寻找最大值。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public int maxProduct(int[] nums) {
int[][] dp = new int[nums.length + 1][2];
dp[0][0] = nums[0];
dp[0][1] = nums[0];
for (int i = 1; i < nums.length; i++) {
int tmp = nums[i];
if (tmp >= 0) {
dp[i][0] = Math.max(tmp, dp[i - 1][0] * tmp);
dp[i][1] = Math.min(tmp, dp[i - 1][1] * tmp);
} else {
dp[i][1] = Math.min(tmp, dp[i - 1][0] * tmp);
dp[i][0] = Math.max(tmp, dp[i - 1][1] * tmp);
}
}
int resu = Integer.MIN_VALUE;
for (int i = 0; i < nums.length; i++) {
resu = Math.max(resu, dp[i][0]);
}
return resu;
}
}

153 Find Minimum in Rotated Sorted Array

题目大意

找出旋转数组的最小数字。

解题思路

二分查找,如果 mid <= right,那一定在中间或者左边,5 6 7 0 1 2 3或者7 0 1 2 3 5 6。

左边连续的话,一定是在右边了。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public int findMin(int[] nums) {
int left = 0, right = nums.length - 1;
while (left < right){
int mid = left + (right - left) / 2;
if (nums[mid] <= nums[right]){
right = mid;
} else {
left = mid + 1;
}
}
return nums[left];
}
}

172 Factorial Trailing Zeroes

题目大意

数阶乘后的数字末尾有多少个零。如3!=6,0个;5!=120,1个。

解题思路

事实上,你在使用暴力破解法的过程中就能发现规律:这 9 个数字中只有 2(它的倍数) 与 5 (它的倍数)相乘才有 0 出现

所以,现在问题就变成了这个阶乘数中能配 多少对 2 与 5

举个复杂点的例子:

1
10!= 【 2 *( 2 * 2 )* 5 *( 2 * 3 )*( 2 * 2 * 2 )*( 2 * 5)】

在 10!这个阶乘数中可以匹配两对 2 * 5 ,所以10!末尾有 2 个 0。

可以发现,一个数字进行拆分后 2 的个数肯定是大于 5 的个数的,所以能匹配多少对取决于 5 的个数。(好比现在男女比例悬殊,最多能有多少对异性情侣取决于女生的多少)。

那么问题又变成了 统计阶乘数里有多少个 5 这个因子

需要注意的是,像 25,125 这样的不只含有一个 5 的数字的情况需要考虑进去。

比如 n = 15。那么在 15! 中 有 35 (来自其中的5, 10, 15), 所以计算 n/5 就可以 。

但是比如 n=25,依旧计算 n/5 ,可以得到 55,分别来自其中的5, 10, 15, 20, 25,但是在 25 中其实是包含 25 的,这一点需要注意。

所以除了计算 n/5 , 还要计算 n/5/5 , n/5/5/5 , n/5/5/5/5 , ..., n/5/5/5,,,/5直到商为0,然后求和即可。

代码

1
2
3
4
5
public class Solution {
public int trailingZeroes(int n) {
return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5);
}
}

179 Largest Number

题目大意

非负整数全排列组成的数的最大值。

1
2
Input: [3,30,34,5,9]
Output: "9534330"

解题思路

写一个比较器,硬比。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.Arrays;
import java.util.Comparator;

class Solution {
public String largestNumber(int[] nums) {
String[] strings = new String[nums.length];
for (int i = 0; i < nums.length; i++) {
strings[i] = String.valueOf(nums[i]);
}
Arrays.sort(strings, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return (o2 + o1).compareTo(o1 + o2);
}
});
if (strings[0].equals("0")) return "0";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < strings.length; i++) {
sb.append(strings[i]);
}
return sb.toString();
}
}

188 Best Time to Buy and Sell Stock IV

题目大意

买卖股票问题,最多 k 笔交易,买之前必须要先卖掉手里股票。

解题思路

和123差不多的思路,因为一笔交易要买卖两次,所以当k大到一定程度时候,可以直接采用贪心,因为此时限制的就是天数了。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.Arrays;

class Solution {
public int maxProfit(int k, int[] prices) {
if (k >= prices.length / 2) {
int res = 0;
for (int i = 1; i < prices.length; i++) {
if (prices[i] > prices[i - 1]) {
res += prices[i] - prices[i - 1];
}
}
return res;
}
int[] buy = new int[k + 1];
Arrays.fill(buy, -prices[0]);
int[] sell = new int[k + 1];
Arrays.fill(sell, 0);
for (int i = 0; i < prices.length; i++) {
for (int j = 1; j <= k; j++) {
buy[j] = Math.max(buy[j], sell[j - 1] - prices[i]);
sell[j] = Math.max(sell[j], buy[j] + prices[i]);
}
}
Arrays.sort(sell);
return sell[sell.length - 1];
}
}

189 旋转数组

题目大意

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。

解题思路

将数组从中间切割,分别转置,然后从头到尾转置。这里要注意当 k 大于数组长度时候的处理。否则会发生数组越界,k 的大小大于数组长度时候,前边的一部分是会移动回原地的,所以进行取模。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public void rotate(int[] nums, int k) {
k %= nums.length;
swap(nums, 0, nums.length - k - 1);
swap(nums, nums.length - k, nums.length -1);
swap(nums, 0, nums.length-1);
}
public void swap(int[] nums, int left, int right) {
while (left < right) {
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
left++;
right--;
}
}
}

190 颠倒二进制位

题目大意

颠倒给定32位无符号整数的二进制位。

解题思路

将 n 视作一个长为32的二进制串,从低位往高位枚举 n 的每一位,倒序添加到翻转结果中。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Solution {
public int reverseBits(int n) {
// 储存结果
int res = 0;
// 逐位翻转
for (int i = 0; i < 32 && n != 0; i++) {
// 取最低位翻转放置
res |= (n & 1) << (31 - i);
// 往右
n >>>= 1;
}
return res;
}
}

204 Count Primes

题目大意

计算小于 n 的素数的个数。

解题思路

倍数法标记所有素数打表。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.Arrays;

class Solution {
public static int countPrimes(int n) {
if (n < 2) return 0;
boolean[] count = new boolean[n];
Arrays.fill(count, true);
count[0] = false;
count[1] = false;
for (int i = 2; i < n; i++) {
if (count[i]) {
for (int j = i + i; j < n; j += i) {
count[j] = false;
}
}
}
int result = 0;
for (int i = 2; i < n; i++) {
if (count[i]) {
result += 1;
}
}
return result;
}
}

206 Reverse Linked List

题目大意

反转链表。

解题思路

递归,如果链表为空或者单节点不用翻转,直接跳出。

否则就将当前节点放到翻转后的子链后面。这里主要是各个节点的关系有点乱。如代码中:

1
2
head.next 是子链表的头节点,它翻转之后应该位于子链表的尾部,也就是head前
所以在处理的时候,应该让这个节点后面接上head,然后head作为尾节点,也就是node定义完后的那几句话

代码

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null){
return head;
}
ListNode node = reverseList(head.next);
head.next.next = head;
head.next = null;
return node;
}
}

215 Kth Largest Element in an Array

题目大意

找到一个未排序数组中第k大的数。

解题思路

  1. 最小堆,每当长度大于k了,就把最后一个弹出去。
  2. 使用快排,利用每次标记位归位的机会判断是不是在第k个位置上,注意重复情况的处理。

代码

1
2
3
4
5
6
7
8
9
10
class Solution {
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> queue = new PriorityQueue<>();
for (int num : nums){
queue.add(num);
if (queue.size() > k) queue.poll();
}
return queue.peek();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
public int findKthLargest(int[] nums, int k) {
QuickSort(nums, 0, nums.length - 1, nums.length - k);
return nums[nums.length - k];
}

public void QuickSort(int[] nums, int start, int end, int k) {
int benchmark = nums[start];
int left = start;
int right = end;
while (left < right) {
while (nums[right] >= benchmark && left < right) {
right--;
}
while (nums[left] <= benchmark && left < right) {
left++;
}
if (left < right) {
swap(nums, left, right);
}
}
nums[start] = nums[left];
nums[left] = benchmark;
if (left == k) {
return;
} else if (left < k) {
QuickSort(nums, left + 1, end, k);
} else {
QuickSort(nums, start, left - 1, k);
}
}

public void swap(int[] nums, int left, int right) {
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
}
}

230 Kth Smallest Element in a BST

题目大意

二叉搜索树中找第 k 个最小的元素。

解题思路

想复杂了,上来就写了个PriorityQueue,中序遍历,加入到数组里,取 k - 1 即可。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
List<Integer> arrayList = new ArrayList<>();
public int kthSmallest(TreeNode root, int k) {
if (root == null || k <= 0) return 0;
MidOrder(root);
return arrayList.get(k - 1);
}
public void MidOrder(TreeNode root){
if (root == null) return;
MidOrder(root.left);
arrayList.add(root.val);
MidOrder(root.right);
}
}

231 Power of Two

题目大意

判断一个数是不是2的幂。

解题思路

如果一个数是 2 的次方数的话,那么它的二进数必然是最高位为 1,其它都为 0 ,那么如果此时我们减 1 的话,则最高位会降一位,其余为 0 的位现在都为变为 1,那么我们把两数相与,就会得到 0。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Solution {
public boolean isPowerOfTwo(int n) {
return n > 0 && ((n & (n - 1)) == 0);
}
}
// 方法二
class Solution {
public boolean isPowerOfTwo(int n) {
int flag = 0;
while (n > 0){
if ((n & 1) == 1){
flag ++;
}
n=n >> 1;
}
return flag == 1;
}
}

236 Lowest Common Ancestor of a Binary Tree

题目大意

寻找两个节点的最近的公共祖先。

解题思路

递归查找,如果根节点为空或者是其中一个节点是根节点,那这个根节点就是结果。

否则的话,左右子树找一下,如果左右子树分别找到了,节点分布两边,说明这个节点就是祖先节点。如果都在一边,那找到的那个节点就是祖先节点了。

代码

1
2
3
4
5
6
7
8
9
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || p == root || q == root) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (left != null && right != null) return root;
return left != null? left: right;
}
}

242 Valid Anagram

题目大意

判断一个字符串是不是另一个字符串的全排列的一种,判断两个字符串的组成元素是不是一样的。

解题思路

  1. 按照字符排序,判断是否相同。
  2. 统计不同元素出现的次数是不是一致。

代码

268 Missing Number

题目大意

给定一个数组,包含了从0到n的不同数字,其中缺少一位,找到那个缺少的位置。

解题思路

  1. 因为这道题都是从0开始的,可以直接用一模一样的不缺失的数异或,得到缺失的数。
  2. 一模一样的不缺失的数组求和,减掉原来数组的和。

代码

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public int missingNumber(int[] nums) {
int len = nums.length;
int resu = 0;
for (int i = 0; i < len; i++){
resu ^= nums[i];
resu ^= (i+1);
}
return resu;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public int missingNumber(int[] nums) {
int len = nums.length;
int sum = 0;
int resu = 0;
for (int i = 0; i < len; i++){
sum += nums[i];
resu += (i+1);
}
return resu -sum;
}
}

278 First Bad Version

题目大意

给定一个元素 n 代表有 [1, 2, …, n] 版本,在第 x 位置开始出现错误版本,导致后面的版本都错误。可以调用 isBadVersion(int x) 知道某个版本是否错误,要求找到第一个错误的版本。

解题思路

简单难度,主要在题意的理解上,还出现了调用已有函数这一形式。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Solution extends VersionControl {
public int firstBadVersion(int n) {
int left = 1, right = n;
while (left <= right){
int mid = left + (right - left) / 2;
if (isBadVersion(mid)){
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
}
}

279 Perfect Squares

题目大意

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

12 = 4 + 4 + 4

13 = 4 + 9

解题思路

使用广度优先搜索方法,将 n 依次减去比 n 小的所有平方数,直至 n = 0 ,此时的层数即为最后的结果。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public int numSquares(int n) {
Queue<Integer> queue = new LinkedList<>();
ArrayList<Integer> visited = new ArrayList<>();
int level = 0;
queue.offer(n);
while (!queue.isEmpty()) {
int len = queue.size();
for (int i = 0; i < len; i++) {
n = queue.poll();
if (n == 0)
return level;
for (int j = 1; j < Math.sqrt(n) + 1; j++) {
int val = n - j * j;
if (visited.contains(val))
continue;
queue.offer(val);
visited.add(val);
}
}
level += 1;
}
return level;
}
}

292 Nim Game

题目大意

你和你的朋友在玩下面的Nim游戏:桌子上有一堆石头,每次你们中的一个人轮流移走1到3块石头。谁把最后一块石头移走,谁就赢。你将在第一个拐弯处移走石头。

你们俩都很聪明,在游戏中都有最佳的策略。写一个函数来确定你是否可以赢得游戏给定的石头堆的数量。

例如,4,无论如何你都会输掉,因为最后一块石头都会被朋友拿走。

解题思路

如果我能赢,那么最后轮到我取石子的时候必须要剩下 1~3 颗石子,这样我才能一把拿完。

如何营造这样的一个局面呢?显然,如果对手拿的时候只剩 4 颗石子,那么无论他怎么拿,总会剩下 1~3 颗石子,我就能赢。

如何逼迫对手面对 4 颗石子呢?要想办法,让我选择的时候还有 5~7 颗石子,这样的话我就有把握让对方不得不面对 4 颗石子。

如何营造 5~7 颗石子的局面呢?让对手面对 8 颗石子,无论他怎么拿,都会给我剩下 5~7 颗,我就能赢。

这样一直循环下去,我们发现只要踩到 4 的倍数,就落入了圈套,永远逃不出 4 的倍数,而且一定会输。

代码

1
2
3
4
5
6
7
public class Solution {
bool canWinNim(int n) {
// 如果上来就踩到 4 的倍数,那就认输吧
// 否则,可以把对方控制在 4 的倍数,必胜
return n % 4 != 0;
}
}

300 最长递增子序列

题目大意

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

解题思路

注意这里数组可能不连续。

动态规划,以当前元素为结尾的数组长度等于前面最长的长度+1,或者当前元素不取,具体看代码内注释。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums.length <= 1) {
return nums.length;
}
int result = 1;
// dpi表示以i为结尾的最长长度
int[] dp = new int[nums.length];
dp[0] = 1;
for (int i = 1; i < nums.length; i++) {
// 当前位置初始化
dp[i] = 1;
// 过往长度 + 1,从低往高算
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
// 更新每一位结果
result = Math.max(result, dp[i]);
}
return result;
}
}

319 Bulb Switcher

题目大意

一开始有n个灯泡是关着的。首先打开所有的灯泡。第二轮,每隔一秒就关掉一个灯泡。在第三轮游戏中,每隔3个灯泡就切换一次(如果灯泡是关的,就打开;如果灯泡是开的,就关闭)。第i轮,你按下了所有的i灯泡。对于第n轮,您只切换最后一个灯泡。找出n轮后有多少灯泡是亮着的。

解题思路

首先,因为电灯一开始都是关闭的,所以某一盏灯最后如果是点亮的,必然要被按奇数次开关。

我们假设只有 6 盏灯,而且我们只看第 6 盏灯。需要进行 6 轮操作对吧,请问对于第 6 盏灯,会被按下几次开关呢?这不难得出,第 1 轮会被按,第 2 轮,第 3 轮,第 6 轮都会被按。

为什么第 1、2、3、6 轮会被按呢?因为 6 = 1×6 = 2×3。一般情况下,因子都是成对出现的,也就是说开关被按的次数一般是偶数次。但是有特殊情况,比如说总共有 16 盏灯,那么第 16 盏灯会被按几次?

1
16 = 1 × 16 = 2 × 8 = 4 × 4

其中因子 4 重复出现,所以第 16 盏灯会被按 5 次,奇数次。现在你应该理解这个问题为什么和平方根有关了吧?

不过,我们不是要算最后有几盏灯亮着吗,这样直接平方根一下是啥意思呢?稍微思考一下就能理解了。

就假设现在总共有 16 盏灯,我们求 16 的平方根,等于 4,这就说明最后会有 4 盏灯亮着,它们分别是第 1 × 1 = 1 盏、第 2 × 2=4 盏、第 3 × 3 = 9 盏和第 4 × 4 = 16盏。

我们不是想求有多少个可开方的数吗,4 是最大的平方根,那么小于 4 的正整数的平方都是在 1~16 内的,是会被按奇数次开关,最终亮着的灯。

就算有的 n 平方根结果是小数,强转成 int 型,也相当于一个最大整数上界,比这个上界小的所有整数,平方后的索引都是最后亮着的灯的索引。所以说我们直接把平方根转成整数,就是这个问题的答案。

代码

1
2
3
4
5
class Solution {
public int bulbSwitch(int n) {
return (int)Math.sqrt(n);
}
}

322 CoinChange

题目大意

给定不同面额的硬币 coins 和金额 amount,计算凑成总金额所需的最少的硬币个数。如果没有任何一种方案能组成该金额,返回 -1。每种硬币的数量是无限的。

解题思路

将子问题定义为dp[k]即凑出k的最少的硬币数。显然dp[0]=0。对于k,我们可以尝试所有面额的硬币,例如,如果尝试了面额c的硬币,问题就变成了凑出金额k-c的问题。因此可以写出关系式:
$$
dp[k] = min_{c \in C}{1+dp(k-c)}
$$
可以看到,每一个数的结果都是由左边的问题计算出来的。

下面处理DP中的无效数组,例如,当我们只有2、5元硬币的时候,是凑不出3来的,所以这时候dp[3]为无效元素。因为要求最小值,我们可以将无效值设置为正无穷方便参与计算。在编程时候我们发现,k最多也就是由面额最小的全部兑换,例如k个1元,所以只需要大于k就可以看作无效因素。这里取k+1。

参考文章

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public int coinChange(int[] coins, int amount) {
int dp[] = new int[amount+1];
Arrays.fill(dp, amount+1);
dp[0] = 0;
for (int i = 1; i <= amount; i++){
for (int coin : coins){
if (i >= coin){
dp[i] =Math.min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
}

326 Power of Three

题目大意

给一个整数,在不使用递归或者循环,判断是不是3的幂。

解题思路

正常的思路是不停地去除以 3,看最后的迭代商是否为 1。这种思路的代码使用到了循环,逼格不够高。

这里取巧的方法 用到了数论的知识:3 的幂次的质因子只有 3

题目要求输入的是 int 类型,正数范围是 0 - 231,在此范围中允许的最大的 3 的次方数为 319 = 1162261467 ,那么只要看这个数能否被 n 整除即可。

代码

1
2
3
4
5
class Solution {
public boolean isPowerOfThree(int n) {
return n > 0 && 1162261467 % n == 0;
}
}

329 Longest Increasing Path in a Matrix

题目大意

寻找矩阵中最长的递增序列。

解题思路

设置标志位,深度搜索并更新最大值。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Solution {
int[][] state = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

public int longestIncreasingPath(int[][] matrix) {
int r = matrix.length;
if (r == 0) {
return 0;
}
int c = matrix[0].length;
int[][] dp = new int[r][c];
int max = 0;
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
// 从i j出发
max = Math.max(max, dfs(dp, matrix, i, j));
}
}
return max;
}

private int dfs(int[][] dp, int[][] matrix, int i, int j) {
if (dp[i][j] != 0) {
return dp[i][j];
}
// 该位置访问,至少长度为1了
dp[i][j] = 1;
// 四个方向遍历
for (int[] s : state) {
// 新坐标
int x = i + s[0];
int y = j + s[1];
// 坐标符合要求
if (x >= 0 && x < matrix.length && y >= 0 && y < matrix[x].length && matrix[i][j] < matrix[x][y]) {
// 要么当前值,要么就是往下走,长度需要加1
dp[i][j] = Math.max(dp[i][j], dfs(dp, matrix, x, y) + 1);
}
}
return dp[i][j];
}
}

330 Patching Array

题目大意

给定一个已排序的正整数数组nums和一个整数n,在数组中添加补丁元素,使数组中某些元素的和可以构成包含在[1,n]范围内的任意数。返回所需的最小补丁数。

1
2
3
4
5
Input: nums = [1,3], n = 6
Output: 1, 补2
Input: nums = [1,5,10], n = 20
Output: 2
Explanation: The two patches can be [2, 4].补24

解题思路

这是贪心算法的一个应用。举个例子,对于数组 [1, 2, 3, 8] :

  1. 用一个miss来表示当前缺失的数,初始时为1,num[0] = 1,它的覆盖范围为 [1, 1] ,可以补足miss = 1

  2. 那么哪个数是num[0]达不到的呢?答案是:miss + nums[0] = 2。那么向数组申请一个新的数nums[1],它们的覆盖范围为 [1, 3] 发现可以补足miss = 2。

  3. 那么哪个数是它们两达不到的呢?答案是:miss + nums[1] = 4。那么向数组申请一个新的数nums[2],它们的覆盖范围为 [1, 6] 发现3可以补足miss = 4

  4. 那么哪个数是它们三达不到的呢?答案是:miss + nums[2] = 7。那么向数组申请一个新的数nums[3],发现由于8大于7,因此miss要自己申请一个7,此时由于7的加入,覆盖范围变为了 [1, 13]

  5. 那么下一个 [1, 2, 3, 7]达不到的呢?答案是:miss + miss(补丁7) = 14。由于8 < 14因此可以覆盖到14,且它们的覆盖范围变为[1, 21]

概括来说,就是从1开始按照248的顺序开始追加,这是由数字的二进制规律得出的。如果遇到了数组有的数比当前要补充的小,那就先把这个数补充上。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public int minPatches(int[] nums, int n) {
int count = 0;
long miss = 1;
int i = 0;
while (miss <= n){
if (i < nums.length && nums[i] <= miss){
miss += nums[i++];
} else {
miss += miss;
count++;
}
}
return count;
}
}

347 Top K Frequent Elements

题目大意

出现频率最高的 k 个元素

题解思路

设置若干个桶,每个桶存储出现频率相同的数。桶的下标表示数出现的频率,即第 i 个桶中存储的数出现的频率为 i。

把数都放到桶之后,从后向前遍历桶,最先得到的 k 个数就是出现频率最多的的 k 个数。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
for (int num : nums){
map.put(num, map.getOrDefault(num, 0) + 1);
}
List<Integer>[] bukets = new ArrayList[nums.length + 1];
for (int key : map.keySet()){
int fre = map.get(key);
if (bukets[fre] == null){
bukets[fre] = new ArrayList<>();
}
bukets[fre].add(key);
}
List<Integer> result = new ArrayList<>();
for (int i = bukets.length - 1; i >= 0 && result.size() < k; i--){
if (bukets[i] == null) continue;
if (bukets[i].size() <= k - result.size()){
result.addAll(bukets[i]);
} else {
result.addAll(bukets[i].subList(0, k - result.size()));
}
}
int[] r = new int[result.size()];
for (int i = 0; i < result.size(); i++){
r[i] = result.get(i);
}
return r;
}
}

377 Combination Sum IV

题目大意

给定一个由正整数组成且不存在重复数字的数组 nums,找出和为给定目标正整数 target 的组合的个数。顺序不同的序列视作不同的组合

解题思路

这题和322题硬币类似,只不过这题是求方案了。以凑硬币为例来看,对于f(k)是凑出金额k的方案数,考虑第一个硬币选哪个,如果硬币有1、3、5,那么放每个都是不同的方案。如果第一个硬币放的1,剩下k-1方案就是f(k-1)。可以得出递推关系式:
$$
f(k) = \sum_{c \in C}{f(k-c)}
$$
问题的base case:f(0) = 1。这个数组不存在什么无效元素,因此凑不出的金额直接0就可以了。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public int combinationSum4(int[] nums, int target) {
int res[] = new int[target + 1];
res[0] = 1;
for (int i = 1; i <= target; i++){
for (int k : nums){
if (i >= k){
res[i] += res[i - k];
}
}
}
return res[target];
}
}

387 字符串中的第一个唯一字符

题目大意

给定一个都是小写字母的字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。

示例:

1
2
3
4
5
s = "leetcode"
返回 0

s = "loveleetcode"
返回 2

解题思路

采用哈希记录方法,记录每个字母出现的次数,然后遍历整个字符串找到第一次出现的次数为1的字母。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public int firstUniqChar(String s) {
int[] map = new int[26];
for (int i = 0; i < s.length(); i++) {
map[s.charAt(i) - 'a']++;
}
for (int i = 0; i < s.length(); i++) {
if (map[s.charAt(i) - 'a'] == 1) {
return i;
}
}
return -1;
}
}

394 Decode String

题目大意

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k次。注意 k 保证为正整数。

1
2
3
4
Input: s = "3[a]2[bc]"
Output: "aaabcbc"
Input: s = "3[a2[c]]" // 注意这种嵌套的写法
Output: "accaccacc"

解题思路

分情况讨论,放了两个栈,一个存储数字,一个存储字符串。这里要注意,数字可能不止一位数。

当数字的时候,追加到前面的数字上。

遇到了左括号说明数字结束了,放到数字栈中,顺便归位,字符串也要归位准备放新串。

遇到了右括号说明当前字符串结束了,取出当前括号前的数字开始追加指定次数,然后放到保存字符串str中待保存。

注意嵌套行为,使用栈每次存放最里面括号得内容。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.util.Stack;

class Solution {
public String decodeString(String s) {
Stack<Integer> nums = new Stack<>();
Stack<String> strs = new Stack<>();
// 当前数字
int num = 0;
// 当前字符串
String str = "";
for (int i = 0; i < s.length(); i++) {
// 如果是数字,计算,很有可能是n位数连续
if (s.charAt(i) <= '9' && s.charAt(i) >= '0') {
num = num * 10 + (s.charAt(i) - '0');
} else if (s.charAt(i) == '[') {
// 遇到左括号把当前算得数字和上一轮得字符串加进去,然后归位
nums.push(num);
strs.push(str);
num = 0;
str = "";
} else if (s.charAt(i) == ']') {
// 遇到右括号说明当前字符串准备完了,把数字取出来,追加n遍保存到str
int n = nums.pop();
String tmp = strs.pop();
while (n-- > 0) {
tmp += str;
}
str = tmp;
} else {
// 准备字符串
str += s.charAt(i);
}
}
return str;
}
}

406 Queue Reconstruction by Height

题目大意

一个学生用两个分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 个学生的身高比他高或者和他一样高。重建这个数组。

解题思路

为了使插入操作不影响后续的操作,身高较高的学生应该先做插入操作,否则身高较小的学生原先正确插入的第 k 个位置可能会变成第 k+1 个位置。

身高 h 降序、个数 k 值升序,然后将某个学生插入队列的第 k 个位置中。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public int[][] reconstructQueue(int[][] people) {
if (people == null || people.length == 0 || people[0].length == 0){
return new int[0][0];
}
Arrays.sort(people, new Comparator<int[]>(){
@Override
public int compare(int[] o1, int[] o2){
if (o1[0] == o2[0])
return o1[1] - o2[1];
return o2[0] - o1[0];
}
});
List<int[]> queue = new ArrayList<>();
for (int[] p : people) {
queue.add(p[1], p);
}
return queue.toArray(new int[queue.size()][]);
}
}

435 Non-overlapping Intervals

题目大意

计算让一组区间不重叠所需要移除的区间个数。

解题思路

先计算最多能组成的不重叠区间个数,然后用区间总个数减去不重叠区间的个数。

在每次选择中,区间的结尾最为重要,选择的区间结尾越小,留给后面的区间的空间越大,那么后面能够选择的区间个数也就越大。

按区间的结尾进行排序,每次选择结尾最小,并且和前一个区间不重叠的区间。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
if (intervals.length == 0) return 0;
Arrays.sort(intervals, new Comparator<int[]>(){
@Override
public int compare(int[] o1, int[] o2){
return o1[1] - o2[1];
}
});
int count = 1;
int end = intervals[0][1];
for (int i = 0; i < intervals.length; i++){
if (intervals[i][0] >= end){
end = intervals[i][1];
count ++;
}
}
return intervals.length - count;
}
}

438 Find All Anagrams in a String

题目大意

给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。

解题思路

见异位词专题篇。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> list = new ArrayList<>();
if (p.length() > s.length()) return list;
int[] dic = new int[26];
for (char ch : p.toCharArray()) {
dic[ch - 'a']++;
}
// 子串长度
int len = p.length();
int[] cur = new int[26];
// 固定滑动窗口为 len,每次进一个出一个,保证滑动窗口的长度不变
int index = 0;
for (int i = 0; i < len; i++) {
cur[s.charAt(i) - 'a']++;
}
// i 表示末尾进来的元素
for (int i = len; i < s.length(); i++) {
if (isSame(dic, cur)) list.add(i - len);
cur[s.charAt(i - len) - 'a']--;
cur[s.charAt(i) - 'a']++;
}
// 因为每次处理的都是i前面len个的,所以最后i+1时候才是最后一个,但是上边的循环会越界
if (isSame(dic, cur)) list.add(s.length() - len);
return list;
}
// 判断拥有的元素数量都一致,就是异位词了
public boolean isSame(int[] a, int[] b) {
for (int i = 0; i < 26; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
}

442 Find All Duplicates in an Array

题目大意

寻找数组中出现了两次的数。

解题思路

排序找,大佬用了标记法,有点像之前一道题,+len 然后膜一下的算法。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public List<Integer> findDuplicates(int[] nums) {
Arrays.sort(nums);
List<Integer> result = new ArrayList<>();
for (int i = 1; i < nums.length; i++) {
if (nums[i] == nums[i - 1]) {
result.add(nums[i]);
}
}
return result;
}
}
class Solution {
public List<Integer> findDuplicates(int[] nums) {
List<Integer> resu = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
int num = Math.abs(nums[i]);
int num2 = nums[num - 1];
if (num2 <= 0) {
resu.add(num);
} else {
nums[num - 1] *= -1;
}
}
return resu;
}
}

451 Sort Characters By Frequency

题目大意

按照字符出现次数从小到大对字符串进行排序。

解题思路

按照频率放进桶,从后往前取。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public String frequencySort(String s) {
Map<Character, Integer> map = new HashMap<>();
char[] a= s.toCharArray();
for (char as : a){
map.put(as, map.getOrDefault(as, 0) + 1);
}
List<Character>[] buckets = new ArrayList[s.length() + 1];
for (char key : map.keySet()){
int fre = map.get(key);
if (buckets[fre] == null){
buckets[fre] = new ArrayList<>();
}
buckets[fre].add(key);
}
StringBuilder result = new StringBuilder();
for (int i = buckets.length - 1; i >= 0; i--){
if (buckets[i] != null){
for (char b : buckets[i]){
// 这里要注意,每个位置存储的是出现i次的b要进行次数的还原,如果使用String这里会超内存
for (int j = 0; j < i; j++){
result.append(b);
}
}
}
}
return result.toString();
}
}

452 Minimum Number of Arrows to Burst Balloons

题目大意

气球在一个水平数轴上摆放,可以重叠,飞镖垂直投向坐标轴,使得路径上的气球都被刺破。求解最小的投飞镖次数使所有气球都被刺破。

解题思路

计算不重叠的区间个数,[1, 2] 和 [2, 3] 在本题中算是重叠区间。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public int findMinArrowShots(int[][] points) {
if (points == null || points.length == 0){
return 0;
}
Arrays.sort(points, new Comparator<int[]>(){
@Override
public int compare(int[] o1, int[] o2){
return o1[1] - o2[1];
}
});
int count = 1, end = points[0][1];
for (int i = 0; i < points.length; i++){
if (points[i][0] > end){
end = points[i][1];
count ++;
}
}
return count;
}
}

454 4 Sum Ⅱ

题目大意

给定四个数组,包含很多元组,计算每个数组挑一个数后和为 0 的个数。

解题思路

把前两个算出来之后,放到map里然后,计算后两个的和寻找前面相反数的出现次数。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.HashMap;

class Solution {
public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
int result = 0;
for (int i = 0; i < A.length; i++) {
for (int j = 0; j < B.length; j++) {
map.put(A[i] + B[j], map.getOrDefault(A[i] + B[j], 0) + 1);
}
}
for (int i = 0; i < C.length; i++) {
for (int j = 0; j < D.length; j++) {
result += map.getOrDefault(0 - C[i] - D[j], 0);
}
}
return result;
}
}

455 Assign Cookies

题目大意

每个孩子都有一个满足度 grid,每个饼干都有一个大小 size,只有饼干的大小大于等于一个孩子的满足度,该孩子才会获得满足。求解最多可以获得满足的孩子数量。

解题思路

  1. 给一个孩子的饼干应当尽量小并且又能满足该孩子,这样大饼干才能拿来给满足度比较大的孩子。
  2. 因为满足度最小的孩子最容易得到满足,所以先满足满足度最小的孩子。

代码

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public static void main(String[] args) {
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Collections.reverseOrder());
for (int i = 0; i < 10; i++) {
priorityQueue.offer(i);
}
while (!priorityQueue.isEmpty()) {
System.out.println(priorityQueue.poll());
}
}
}

456 123 模式

题目大意

判断数组中是否存在 1 3 2 这中大小关系的格式。即 nums[i]、nums[j]、nums[k] 中存在 nums[i] < nums[k] < nums[j]。

解题思路

代码

494 Target Sum

题目大意

通过 ± 组合数组中的数为目标数,求多少种组法。

解题思路

本来想强行排列组合,发现有人证明了一波。

1
2
3
4
5
6
7
sum(P) 前面符号为+的集合;sum(N) 前面符号为减号的集合
所以题目可以转化为
sum(P) - sum(N) = target
=> sum(nums) + sum(P) - sum(N) = target + sum(nums)
=> 2 * sum(P) = target + sum(nums)
=> sum(P) = (target + sum(nums)) / 2
因此题目转化为01背包,也就是能组合成容量为sum(P)的方式有多少种

给定集合nums={1,2,3,4,5}, 求解子集,使子集中元素之和等于9 = new_target = sum§ = (target+sum(nums))/2

定义dp[10]数组, dp[10] = {1,0,0,0,0,0,0,0,0,0}

dp[i]表示子集合元素之和等于当前目标值的方案个数, 当前目标值等于9减去当前元素值

当前元素等于1时,dp[9] = dp[9] + dp[9-1]

dp[8] = dp[8] + dp[8-1]…

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public int findTargetSumWays(int[] nums, int S) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if (sum < S || (sum + S) % 2 == 1) {
return 0;
}
int w = (sum + S) / 2;
int[] dp = new int[w + 1];
dp[0] = 1;
for (int num : nums) {
for (int j = w; j >= num; j--) {
dp[j] += dp[j - num];
}
}
return dp[w];
}
}
1
2
3
4
5
6
7
8
9
10
11
class Solution {
public int findTargetSumWays(int[] nums, int S) {
return dfs(0, S, nums);
}
public int dfs(int index, int target, int[] nums) {
if (index == nums.length) {
return target == 0 ? 1 : 0;
}
return dfs(index + 1, target - nums[index], nums) + dfs(index + 1, target + nums[index], nums);
}
}

518 Coin Change 2

题目大意

依旧是兑换硬币,但是这次需要不重复的方案。

解题思路

这里我们考虑有顺序的方案,即从大到小排列,丢弃其他排列。因此为动态规划增加一个维度,用参数i表示可选前i个硬币进行兑换,即f(i, k)表示使用前i中硬币凑出k的方案数。

那么一开始i=m,可以选全部的硬币,如果当前一步选了面额第二大的硬币$C_m-1$,那么接下来i更新为m-1,限制接下来只能选择比$C_{m-1}$小的硬币。写出递推关系:

$$
f(i, k) = f(i, k - c_i) + f(i-1, k)
$$

这里第一项表示用最大面额凑硬币,因为第一个硬币可以拿多次,所以i不变,金额变小。第二项,不拿面额大的硬币,那后面只能拿倒数前i-1的硬币。

基础解,当k=0的时候,f(i, 0) = 1,即凑出金额0的方案数为1。当i=0,f(0, k) = 0,硬币用完之后,凑不出任何金额。

从左往右从上往下解问题。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public int change(int amount, int[] coins) {
int m = coins.length;
int[][] dp = new int[m+1][amount+1];

for (int i = 0; i <= m; i++) {
for (int k = 0; k <= amount; k++) {
if (k == 0) {
dp[i][k] = 1; // base case, 这里可以写在一起
} else if (i == 0) {
dp[i][k] = 0; // base case
} else {
dp[i][k] = dp[i-1][k];
if (k >= coins[i-1]) {
dp[i][k] += dp[i][k-coins[i-1]];
}
}
}
}
return dp[m][amount];
}
// 一维计算方法,要注意循环的先后顺序,比如121和112,两种回重复计算
class Solution {
public int change(int amount, int[] coins) {
int [] dp = new int[amount + 1];
dp[0] = 1;
for (int coin : coins){
for (int i = 1; i <= amount; i++){
if (i - coin >= 0){
dp[i] += dp[i - coin];
}
}
}
return dp[amount];
}
}

524 Longest Word in Dictionary through Deleting

题目大意

给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。

解题思路

通过删除字符串 s 中的一个字符能得到字符串 t,可以认为 t 是 s 的子序列,我们可以使用双指针来判断一个字符串是否为另一个字符串的子序列。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public String findLongestWord(String s, List<String> d) {
String result = "";
for (String sd : d){
int l1 = result.length(), l2 = sd.length();
if (l1 > l2 || (l1 == l2 && result.compareTo(sd) < 0)) continue;
if (isSubString(sd, s)){
result = sd;
}
}
return result;
}
public boolean isSubString(String sub, String str){
int i = 0, j = 0;
while (i < sub.length() && j < str.length()){
if (sub.charAt(i) == str.charAt(j)){
i++;
}
j++;
}
return i == sub.length();
}
}
// result.compareTo(sd) < 0 表示字典序升序,返回值是整型,它是先比较对应字符的大小(ASCII码顺序),如果第一个字符和参数的第一个字符不等,结束比较,返回他们之间的差值,如果第一个字符和参数的第一个字符相等,则以第二个字符和参数的第二个字符做比较,以此类推,直至比较的字符或被比较的字符有一方结束。

538 Convert BST to Greater Tree

题目大意

把二叉搜索树的每个节点的值重新设置为所有比它值大的节点的值的和。

解题思路

因为二叉搜索树,所以左节点都比右节点小,所以修改次序应该为右中左,然后记录大节点的和。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
private int sum = 0;

public TreeNode convertBST(TreeNode root) {
tree(root);
return root;
}

public void tree(TreeNode tree) {
if (tree == null) return;
tree(tree.right);
sum += tree.val;
tree.val = sum;
tree(tree.left);
}
}

540 Single Element in a Sorted Array

题目大意

一个有序数组只有一个数不出现两次,找出这个数。

解题思路

  1. 异或之后就剩单个的数了,直接起飞,算法复杂度为O(n)。
  2. 采用二分查找为O(logn)。因为有数是单个,因为01、23、34这样检查,如果没出现单个应该都是一样的,一旦出现了不一样说明这个数已经在左边出现过了。为了保证验证的每次都是偶数位置,写个 if 过滤一下。很巧妙的办法。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 法 1
class Solution {
public int singleNonDuplicate(int[] nums) {
int left = 0, right = nums.length - 1;
while (left < right){
int mid = left + (right - left) / 2;
if (mid % 2 == 1) {
mid --; // 保证 l/h/m 都在偶数位,使得查找区间大小一直都是奇数
}
if (nums[mid] == nums[mid + 1]){
left = mid + 2;
} else {
right = mid;
}
}
return nums[left];
}
}
// 法 2
class Solution {
public int singleNonDuplicate(int[] nums) {
int result = 0;
for (int num : nums){
result ^= num;
}
return result;
}
}

547 省份数量

题目大意

有一些城市,使用二维矩阵表示这些省份的相连关系,1 表示相连。相连的城市组成在一起构成省份。求给定的二维矩阵中,有多少省份。

解题思路

找到一个没被访问过的节点,即看作一个省份,使用深度搜索将与之相连的城市全部找出并置访问标记。

这里要注意的是,虽然题目中给定了二维矩阵,但是城市的访问标记数组应当是一维的。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public int findCircleNum(int[][] isConnected) {
if (isConnected == null || isConnected.length == 0 || isConnected[0].length == 0) {
return 0;
}
int resu = 0;
boolean[] visited = new boolean[isConnected.length];
for (int i = 0; i < isConnected.length; i++) {
if (!visited[i]) {
resu++;
visited[i] = true;
dfs(isConnected, visited, i);
}
}
return resu;
}

public void dfs(int[][] isConnected, boolean[] visited, int cur) {
for (int i = 0; i < isConnected.length; i++) {
if (cur != i && !visited[i] && isConnected[i][cur] == 1) {
visited[i] = true;
dfs(isConnected, visited, i);
}
}
}
}

560 Subarray Sum Equals K

题目大意

给定一个整数数组 nums 和一个整数k,返回该数组中「和为k的连续子数组」的个数。

示例,输入: nums = [1,1,1], k = 2; 输出: 2; 解释: [1,1] 与 [1,1] 为两种不同的情况。

解题思路

我们可以首先求出所有的前缀和,然后根据前缀和求出所有可能的子数组之和。

内层循环实际上是在求「有多少个 满足 presum[i] 的值为 presum[j] - k」。而我们可以通过用哈希表存储每一个 presum[i] 的值,直接找到满足条件的 presum[i] 的个数,而不需要写一个循环。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public int subarraySum(int[] nums, int k) {
int N = nums.length;
// 计算前缀和数组
// presum[k] 表示元素 nums[0..k) 之和
int[] presum = new int[N+1];
int sum = 0;
for (int i = 0; i < N; i++) {
presum[i] = sum;
sum += nums[i];
}
presum[N] = sum;
// sum of nums[i..j) = sum of nums[0..j) - sum of nums[0..i)
int count = 0;
for (int i = 0; i <= N; i++) {
for (int j = i+1; j <= N; j++) {
// 前缀和相减求子数组之和
if (presum[j] - presum[i] == k) {
count++;
}
}
}
return count;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1);
int sum = 0;
int resu = 0;
for (int num : nums) {
sum += num;
if (map.containsKey(sum - k)) {
resu += map.get(sum - k);
}
map.put(sum, map.getOrDefault(sum, 0) + 1);
}
return resu;
}
}

567 Permutation in String

题目大意

给定两个字符串 s1s2,写一个函数来判断 s2 是否包含 s1 的排列。换句话说,第一个字符串的排列之一是第二个字符串的子串。

1
2
3
输入: s1 = "ab" s2 = "eidbaooo"
输出: True
解释: s2 包含 s1 的排列之一 ("ba").

解题思路

滑动窗口,比较元素个数是不是相同。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.Arrays;
class Solution {
public static boolean checkInclusion(String s1, String s2) {
int l1 = s1.length();
int l2 = s2.length();
int[] c1 = new int[26];
int[] c2 = new int[26];
for (char c : s1.toCharArray()) {
c1[c - 'a']++;
}
for (int i = 0; i < l2; i++) {
if (i >= l1) {
// 滑出的
c2[s2.charAt(i - l1) - 'a']--;
}
c2[s2.charAt(i) - 'a']++;
if (Arrays.equals(c1, c2)) {
return true;
}
}
return false;
}
}

605 Can Place Flowers

题目大意

flowerbed 数组中 1 表示已经种下了花朵。花朵之间至少需要一个单位的间隔,求解是否能种下 n 朵花。

解题思路

  1. 贪心,计算出能放花的个数,如果>=n,OK。以中间位为基准,设置前后位来检查。

  2. 贪心,题目要求是否能在不打破规则的情况下插入n朵花,与直接计算不同,采用“跳格子”的解法只需遍历不到一遍数组,处理以下两种不同的情况即可:

    【1】当遍历到index遇到1时,说明这个位置有花,那必然从index+2的位置才有可能种花,因此当碰到1时直接跳过下一格。
    【2】当遍历到index遇到0时,由于每次碰到1都是跳两格,因此前一格必定是0,此时只需要判断下一格是不是1即可得出index这一格能不能种花,如果能种则令n减一,然后这个位置就按照遇到1时处理,即跳两格;如果index的后一格是1,说明这个位置不能种花且之后两格也不可能种花(参照【1】),直接跳过3格。

    当n减为0时,说明可以种入n朵花,则可以直接退出遍历返回true;如果遍历结束n没有减到0,说明最多种入的花的数量小于n,则返回false。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public boolean canPlaceFlowers(int[] flowerbed, int n) {
int count = 0;
for (int i = 0; i < flowerbed.length; i++){
if (flowerbed[i] == 1) continue;
int pre = i == 0 ? 0 : flowerbed[i - 1];
int next = flowerbed[i + 1];
if (pre == 0 && next == 0){
count ++;
flowerbed[i] = 1;
}
}
return count >= n;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public boolean canPlaceFlowers(int[] flowerbed, int n) {
int index = 0;
while (index < flowerbed.length) {
if (flowerbed[index] == 0) {
// 防溢出,也是将flowerbed.length看作没有花
if (index == flowerbed.length - 1 || flowerbed[index + 1] == 0) {
n--;
index += 2;
} else {
index += 3;
}
} else {
index += 2;
}
}
return n <= 0;
}
}

647 Palindromic Substrings

题目大意

给定义一个字符串,计算有多少个回文子串。

1
2
3
Input: "aaa"
Output: 6
Explanation: Six palindromic strings: "a", "a", "a", "aa", "aa", "aaa".

解题思路

中间外扩法,分奇数和偶数两种情况外扩。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
int result = 0;

public int countSubstrings(String s) {
for (int i = 0; i < s.length(); i++) {
count(s, i, i);
count(s, i, i + 1);
}
return result;
}

public void count(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
left--;
right++;
result++;
}
}
}

665 Non-decreasing Array

题目大意

判断一个数组是否能只修改一个数就成为非递减数组。

解题思路

在出现 nums[i] < nums[i - 1] 时,需要考虑的是应该修改数组的哪个数,使得本次修改能使 i 之前的数组成为非递减数组,并且 不影响后续的操作 。优先考虑令 nums[i - 1] = nums[i],因为如果修改 nums[i] = nums[i - 1] 的话,那么 nums[i] 这个数会变大,就有可能比 nums[i + 1] 大,从而影响了后续操作。还有一个比较特别的情况就是 nums[i] < nums[i - 2],修改 nums[i - 1] = nums[i] 不能使数组成为非递减数组,只能修改 nums[i] = nums[i - 1]。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public boolean checkPossibility(int[] nums) {
int count = 0;
for (int i = 1; i < nums.length && count < 2; i++){
if (nums[i] >= nums[i-1]){
continue;
}
count ++;
if (i - 2 >= 0 && nums[i - 2] > nums[i]){
nums[i] = nums[i - 1];
} else {
nums[i - 1] = nums[i];
}
}
return count <= 1;
}
}

674 Longest Continuous Increasing Subsequence

题目大意

最长连续递增子序列。

1
2
Input: [1,3,5,4,7]
Output: 3

解题思路

循环记录,断了就重新来。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public static int findLengthOfLCIS(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int res = 0;
int tmp = 1;
for (int i = 1; i < nums.length; i++) {
if (nums[i] > nums[i - 1]) {
tmp++;
} else {
res = Math.max(tmp, res);
tmp = 1;
}
}
return Math.max(res, tmp);
}

public static void main(String[] args) {
int[] nums = {1, 3, 5, 7};
System.out.println(findLengthOfLCIS(nums));
}
}

680 Valid Palindrome II

题目大意

可以删除一个字符,判断是否能构成回文字符串。

解题思路

在判断是否为回文字符串时,左右剥皮判断,因为左指针左边和右指针右边的字符之前已经判断过具有对称性质,所以只需要判断中间的子字符串即可。在试着删除字符时,我们既可以删除左指针指向的字符,也可以删除右指针指向的字符。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public boolean validPalindrome(String s) {
if (s == null || s.length() == 0){
return false;
}
for (int i = 0, j = s.length()-1; i<j; i++,j--){
if (s.charAt(i) != s.charAt(j)){
return isPalindrome(s, i, j - 1) || isPalindrome(s, i + 1, j);
}
}
return true;
}
public boolean isPalindrome(String s, int left, int right){
while (left < right){
if (s.charAt(left++) != s.charAt(right--)){
return false;
}
}
return true;
}
}

695 Max Area of Island

题目大意

给定一个包含了一些 01 的非空二维数组 grid

一个岛屿是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。

找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0。)

解题思路

这道题的主要思路是深度优先搜索。每次走到一个是 1 的格子,就搜索整个岛屿,并计算当前岛屿的面积。最后返回岛屿面积的最大值。

网格可以看成是一个无向图的结构,每个格子和它上下左右的四个格子相邻。如果四个相邻的格子坐标合法,且是陆地,就可以继续搜索。

在深度优先搜索的时候要注意避免重复遍历。我们可以把已经遍历过的陆地改成 2,这样遇到 2 我们就知道已经遍历过这个格子了,不进行重复遍历。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Solution {
public int maxAreaOfIsland(int[][] grid) {
int res = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
if (grid[i][j] == 1) {
int a = getArea(grid, i, j);
res = Math.max(res, a);
}
}
}
return res;
}
int getArea(int[][] grid, int r, int c) {
if (!isInArea(grid, r, c))
return 0;
if (grid[r][c] != 1)
return 0;
grid[r][c] = 2;
return 1 + getArea(grid, r - 1, c) + getArea(grid, r + 1, c) + getArea(grid, r, c - 1)
+ getArea(grid, r, c + 1);
}
boolean isInArea(int[][] grid, int r, int c) {
if (r >= 0 && r < grid.length && c >= 0 && c < grid[0].length)
return true;
return false;
}
}

739 Daily Temperatures

题目大意

输入数组为温度序列,寻找对于每一天,后面较温暖的一天在哪。

T = [73, 74, 75, 71, 69, 72, 76, 73], your output should be [1, 1, 4, 2, 1, 1, 0, 0].

解题思路

建立栈,用于存储那些还没有找到较温暖的天的索引。那么栈顶就是最近的一次没找到最近温度的索引。

遍历数组,如果当前位置温度还没有找到比他更暖和的,入栈,准备寻找下一个。每遍历一个新的温度,都去检查一下它和栈顶的关系,如果大于,说明他是离栈顶最近的一个暖和天气,往前寻找,因为可能也大于前面的天气。

这个栈在这里的作用就是建立了一个降序序列,每当遇到比降序序列最后一个元素大的元素的时候就向前不断地寻找所有较小地元素出栈。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public int[] dailyTemperatures(int[] T) {
Stack<Integer> stack = new Stack<>();
int[] resu = new int[T.length];
for (int i = 0; i < T.length; i++) {
while (!stack.isEmpty() && T[i] > T[stack.peek()]) {
resu[stack.peek()] = i - stack.peek();
stack.pop();
}
stack.push(i);
}
return resu;
}
}

744 Find Smallest Letter Greater Than Target

题目大意

给定一个有序的字符数组 letters 和一个字符 target,要求找出 letters 中大于 target 的最小字符,如果找不到就返回第 1 个字符。

如,cfj 中找 c 返回 f,找 g 返回 j,找 z 返回 c。

解题思路

因为有序,想到二分查找。在查找过程中,因为 left 判断 <= 的标准后 ++,所以最终结果应该在 left。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public char nextGreatestLetter(char[] letters, char target) {
int left = 0, right = letters.length - 1;
while (left <= right){
int mid = left + (right - left) / 2;
if (letters[mid] <= target){
left = mid + 1;
} else {
right = mid - 1;
}
}
return left < letters.length ? letters[left] : letters[0];
}
}

763 Partition Labels

题目大意

给你一个串, 要求分割尽量多份,使得每份中的字母只在该被分割部分出现。

解题思路

扫一遍串,用一个 map 存每个字母的最大 index 值

扫一遍串,lock 住 start 指针,更新即将被分割子串最大的last值,当 last == i, 则找到一个被分割子串。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public List<Integer> partitionLabels(String s) {
List<Integer> result = new ArrayList<>();
if (s == null || s.length() == 0) return result;
Map<Character, Integer> map = new HashMap<>();
for (int i = 0; i < s.length(); i++){
map.put(s.charAt(i), i);
}
int start = 0, last = 0;
for (int i = 0; i < s.length(); i++){
last = Math.max(last, map.get(s.charAt(i)));
if (last == i){
result.add(last - start + 1);
start = i + 1;
}
}
return result;
}
}

830 较大分组的位置

题目大意

将字符串中所有包含大于等于三个连续字符的分组的位置坐标按照起始位置坐标递增排序后返回结果。

1
2
3
输入:s = "abbxxxxzzy"
输出:[[3,6]]
解释:"xxxx" 是一个起始于 3 且终止于 6 的较大分组。

解题思路

我们可以遍历该序列,并记录当前分组的长度。如果下一个字符与当前字符不同,或者已经枚举到字符串尾部,就说明当前字符为当前分组的尾部。每次找到当前分组的尾部时,如果该分组长度达到 33,我们就将其加入答案。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public List<List<Integer>> largeGroupPositions(String s) {
int head = 0, tail = 1;
List<List<Integer>> resu = new ArrayList<>();
while (tail < s.length()) {
while (tail < s.length() && s.charAt(tail) == s.charAt(head)) {
tail++;
}
if (tail - head >= 3) {
List<Integer> list = new ArrayList<>();
list.add(head);
list.add(tail - 1);
resu.add(list);
}
head = tail++;
}
return resu;
}
}

877 Stone Game

题目大意

亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 piles[i] 。

游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。

亚历克斯和李轮流进行,亚历克斯先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。

假设亚历克斯和李都发挥出最佳水平,当亚历克斯赢得比赛时返回 true ,当李赢得比赛时返回 false 。

输入:[5,3,4,5]
输出:true
解释:
亚历克斯先开始,只能拿前 5 颗或后 5 颗石子 。
假设他取了前 5 颗,这一行就变成了 [3,4,5] 。
如果李拿走前 3 颗,那么剩下的是 [4,5],亚历克斯拿走后 5 颗赢得 10 分。
如果李拿走后 5 颗,那么剩下的是 [3,4],亚历克斯拿走后 4 颗赢得 9 分。
这表明,取前 5 颗石子对亚历克斯来说是一个胜利的举动,所以我们返回 true 。

解题思路

这道题直接返回了 true 竟然就通过了。。。。

代码

1
2
3
4
5
class Solution {
public boolean stoneGame(int[] piles) {
return true;
}
}

1006 笨阶乘

题目大意

我们以一个固定顺序的操作符序列来依次替换原有的乘法操作符:乘法(*),除法(/),加法(+)和减法(-)。

例如,clumsy(10) = 10 * 9 / 8 + 7 - 6 * 5 / 4 + 3 - 2 * 1。

解题思路

将多项式转化为多项和,即((10*9/8)+7+(-6*5/4),显然可以通过栈解决。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public int clumsy(int N) {
Stack<Integer> stack = new Stack<>();
stack.push(N);
N--;
int index = 0;
while (N > 0) {
if (index % 4 == 0) {
stack.push(stack.pop() * N);
} else if (index % 4 == 1) {
stack.push(stack.pop() / N);
} else if (index % 4 == 2) {
stack.push(N);
} else {
stack.push(-N);
}
index++;
N--;
}
int resu = 0;
while (!stack.isEmpty()) {
resu += stack.pop();
}
return resu;
}
}

1025 Divisor Game

题目大意

有数字N,对于两个游戏玩家轮流做以下动作:

  • 挑选一个X,使得X满足0<X<N并且N%x==0
  • 将N替换为N-X

如果有一个玩家不能继续玩了,游戏结束。爱丽丝先手,如果爱丽丝赢了返回True,假设两个玩家选择的都是最优。

解题思路

几个例子:

  • 假设 N = 1,爱丽丝没得选择,直接失败,即 鲍勃获胜
  • 假设 N = 2,爱丽丝有选择,她可以选择 x = 1,鲍勃面对的就是 N = 2 - 1 = 1,无法操作,爱丽丝获胜
  • 假设 N = 3,爱丽丝只能选择 x = 1,因为选 x = 2 不满足 3 % 2 = 0,鲍勃面对的就是 N = 3 - 1 = 2,参考上面 N = 2 的情形,此时鲍勃为 N = 2 的先手,鲍勃获胜
  • 假设 N = 4,爱丽丝可以选择 x = 1 来使鲍勃遇到 N = 3 的情况,爱丽丝获胜

似乎有个规律,N为偶数时候,爱丽丝获胜。

事实上,无论 N 为多大,最终都是在 N = 2 这个临界点结束的。谁最后面对的是 N = 2 的情形,谁就能获胜。

接下来,我们得知道一个数学小知识:奇数的因子(约数)只能是奇数,偶数的因子(约数)可以是奇数或偶数

千万不要忽略 1 也是因子!

爱丽丝是游戏开始时的先手。

  • 当她面对的 N 为偶数时,她 一定可以 选到一个 N 的奇数因子 x(比如 1 ),将 N - x 这个奇数传给鲍勃;用 N - x 替换黑板上的数字 N ,鲍勃面对的就是奇数 N,只能选择 N 的奇数因子 x,奇数 - 奇数 = 偶数,此时传给爱丽丝的又是偶数。这样轮换下去爱丽丝会遇到 N = 2 的情形,然后获胜;
  • 当爱丽丝遇到的 N 是奇数时,只能传给鲍勃偶数或无法操作 (N = 1) ,无法获胜。

代码

1
2
3
4
5
class Solution {
public boolean divisorGame(int N) {
return N % 2 == 0;
}
}

1046 最后一块石头的重量

题目大意

有一堆石头,每块石头的重量都是正整数。每一回合,从中选出两块最重的石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

  • 如果 x == y,那么两块石头都会被完全粉碎;

  • 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。

最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。

解题思路

  1. 递归,处理到仅剩两块石头时候结束递归。
  2. 使用大顶堆模拟操作。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public int lastStoneWeight(int[] stones) {
if (stones.length == 0) {
return 0;
}
if (stones.length == 1) {
return stones[0];
}
if (stones.length == 2) {
return Math.abs(stones[0] - stones[1]);
}
Arrays.sort(stones);
if (stones[stones.length - 3] == 0) {
return stones[stones.length - 1] - stones[stones.length - 2];
}
stones[stones.length - 2] = stones[stones.length - 1] - stones[stones.length - 2];
stones[stones.length - 1] = 0;
return lastStoneWeight(stones);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public int lastStoneWeight(int[] stones) {
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Collections.reverseOrder());
for (int s : stones) {
priorityQueue.offer(s);
}
while (priorityQueue.size() > 1) {
int top = priorityQueue.poll();
int sec = priorityQueue.poll();
if (top > sec) {
priorityQueue.offer(top - sec);
}
}
return priorityQueue.size() == 1 ? priorityQueue.peek() : 0;
}
}

1047 Remove All Adjacent Duplicates In String

题目大意

删除字符串中相邻重复的字符,包括删除后新组成的字符串中重复的。例如“abbaca”删除后“ca”。

解题思路

使用栈存放已经处理完的字符,入栈检查是不是和上一个入栈相同,是就弹出。否则就入栈。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public static String removeDuplicates(String S) {
String resu = "";
Stack<Character> stack = new Stack<>();
for (int i = 0; i < S.length(); i++) {
if (!stack.isEmpty() && S.charAt(i) == stack.peek()) {
stack.pop();
} else {
stack.push(S.charAt(i));
}
}
StringBuilder sb = new StringBuilder();
for (char c : stack) {
sb.append(c);
}
return sb.toString();
}
}

1095 Find in Mountain Array

题目大意

定义山峰数组,即中间往两边递减。求和目标值相等的最小索引。山峰数组定义如下。

1
2
3
4
interface MountainArray {
public int get(int index) {}
public int length() {}
}

解题思路

先二分查出山峰来,再二分往左查,查不到右边再二分。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
public int findInMountainArray(int target, MountainArray mountainArr) {
int l = 0;
int r = mountainArr.length() - 1;
while (l < r) {
int mid = l + (r - l) / 2;
if (mountainArr.get(mid) > mountainArr.get(mid + 1)) {
r = mid;
} else {
l = mid + 1;
}
}
int i = find(mountainArr, target, 0, l, true);
return i != -1 ? i : find(mountainArr, target, l + 1, mountainArr.length() - 1, false);
}

public int find(MountainArray mountainArray, int t, int l, int r, boolean asc) {
while (l < r) {
int mid = (l + r) >> 1;
if ((asc && mountainArray.get(mid) >= t) || (!asc && mountainArray.get(mid) <= t)) {
r = mid;
} else {
l = mid + 1;
}
}
if (mountainArray.get(l) == t) {
return l;
}
return -1;
}
}

1143 Longest Common Subsequence

题目大意

两个字符串的最长公共子序列,可以不连续。

解题思路

动态规划解决,设置 dp 二维数组,dp[i][j] 表示串1在 i 和 i 前的字串与串2的最长公共子序列长度。

这道题就可以转化为,当两个子串当前位置相等的时候,等于上一个位置的长度 + 1,否则的话就是矩阵中上边和左边前两种状态的最大值。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int len1 = text1.length(), len2 = text2.length();
if (text1 == null || text2 == null || len1 == 0 || len2 == 0)
return 0;
int [][] dp = new int[len1+1][len2+1];
dp[0][0] = 0;
for (int i = 1; i <=len1; i++){
for (int j = 1; j <= len2; j++){
if (text1.charAt(i-1) == text2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1] + 1;
} else {
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[len1][len2];
}
}

1371 Find the Longest Substring Containing Vowels in Even Counts

题目大意

求每个元音字母出现零次或者偶数次的子串的最长长度。

解题思路

  • 字符串的子串里出现的元音字母奇偶的个数设为状态码state可以简化为一个五位的二进制数,栗:'10000'代表 'a' 出现了奇数次,其他元音字母出现了偶数次,'11000'代表'a','e'出现了奇数次,其他偶数次,偶数次可以用异或来计算,即异或运算后,即使子串的值不同,他们的状态码可以是相同的。
  • 用一个哈希表或者数组设为countcount的下标代表状态的整数取值(最大为32),count代表字符串s在下标为count[state]之前的状态码为state,这个也有点像前缀和,只不过是异或计算的。由于奇数之间相减为偶数,偶数之间相减也为偶数,所以在计算过程中,如果出现了与以前某次相同的状态码,说明以当前下标结尾的子串与之前下标结尾的子串出现的元音字母次数奇偶一致,所以他们两个相减就能获得一个元音字母出现次数为偶数的子串
  • 在每次计算时都比较一下两者的大小,最后获得满足要求的最长子串。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public int findTheLongestSubstring(String s) {
int length = s.length();
char[] vos = {'a', 'e', 'i', 'o', 'u'};
Map<Integer, Integer> map = new HashMap<>();
map.put(0, -1);
int max = 0;
int state = 0;
for (int i = 0; i < length; i++) {
for (int j = 0; j < vos.length; j++) {
if (s.charAt(i) == vos[j]) {
state ^= 1 << j;
}
}
map.putIfAbsent(state, i);
max = Math.max(max, i - map.get(state));
}
return max;
}
}

1545 Find Kth Bit in Nth Binary String

题目大意

  • S1 = "0"
  • Si = Si-1 + "1" + reverse(invert(Si-1)) for i > 1

返回第 k 个位置的数。

  • S1 = "0"
  • S2 = "011"
  • S3 = "011**1**001"
  • S4 = "011100110110001"

解题思路

  • 暴力模拟
  • 找规律
    • 第一位肯定是0,中间一位肯定是1
    • 如果是中间左边的,那也就是上一个字符串的第k个。如果是右边的,右边是左边的对称,所以计算位置反转。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public char findKthBit(int n, int k) {
String s = "0";
for (int i = 1; i < n; i++) {
s = s + '1' + solve(s);
}
return s.charAt(k - 1);
}

private String solve(String s) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '1') {
sb.append('0');
} else {
sb.append('1');
}
}
return sb.reverse().toString();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public char findKthBit(int n, int k) {
if (k == 1) {
return '0';
}
int mid = (int) Math.pow(2, n - 1);
if (k == mid) {
return '1';
}
if (k < mid) {
return findKthBit(n - 1, k);
} else {
return f(findKthBit(n - 1, (int) (Math.pow(2, n) - k)));
}
}

public static char f(char a) {
if (a == '1') {
return '0';
} else {
return '1';
}
}
}