常用数据结构与算法,大厂笔试刷题记录。

一、数组

1、最大子序和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。

1
2
3
4
5
6
7
8
9
10
public int maxSubArray(int[] nums) {
int res = nums[0];
for (int i = 1; i < nums.length; i++) {
// 遍历数组,将元素相加,若>0加入结果集,否则重置。
nums[i] += Math.max(nums[i - 1], 0);
// 将最新和值赋值nums数组,减少空间消费。
res = Math.max(res, nums[i]);
}
return res;
}

2、两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

1
2
3
4
5
6
7
8
9
10
11
12
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> map=new HashMap<>();
for(int i=0;i<nums.length;i++){
if(map.containsKey(target-nums[i])){
//本来想着应该先遍历一次存储nums数组元素到map中,其实不必要,一次遍历即可,
//不存在再push, 否则判断另一元素是否也在,在遍历完一遍后,必然可以所有元素都存在map中。
return new int[]{i,map.get(target-nums[i])};
}
map.put(nums[i],i);
}
return new int[0];
}

3、合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1nums2,另有两个整数 mn ,分别表示 nums1nums2 中的元素数目。

请你 合并 nums2nums1 中,使合并后的数组同样按 非递减顺序 排列。

1
2
3
4
5
6
7
8
9
public void merge(int[] nums1, int m, int[] nums2, int n) {
int len = m + n - 1;
int len1 = m - 1, len2 = n - 1;
while (len1 >= 0 && len2 >= 0) {
nums1[len--] = nums1[len1] > nums2[len2] ? nums1[len1--] : nums2[len2--];
}
// 表示将nums2数组从下标0位置开始,拷贝到nums1数组中,从下标0位置开始,长度为len2+1
System.arraycopy(nums2, 0, nums1, 0, len2 + 1);
}

4、二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public int search(int[] nums, int target) {
int i = 0, j = nums.length - 1;
while (i <= j) {
int mid = i + (j - i) / 2;
if (nums[mid] < target) {
i = mid + 1;
} else if (nums[mid] > target) {
j = mid - 1;
} else {
return mid;
}
}
return -1;
}

5、移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

1
2
3
4
5
6
7
8
9
10
11
public void moveZeroes(int[] nums) {
int j = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
nums[j++] = nums[i];
}
}
while (j < nums.length) {
nums[j++] = 0;
}
}

6、删除排序数组中的重复项

给你一个 非严格递增排列 的数组 nums ,请你** 原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。

1
2
3
4
5
6
7
8
9
10
11
12
public int removeDuplicates(int[] nums) {
if (nums.length == 0) {
return 0;
}
int index = 0;
for (int i = 1; i < nums.length; i++) {
if (nums[i] != nums[index]) {
nums[++index] = nums[i];
}
}
return index + 1;
}

7、圆圈中最后剩下的数字

从 0 号成员起开始计数,排在第 target 位的成员离开圆桌,且成员离开后从下一个成员开始计数。请返回游戏结束时最后一位成员的编号。

1
2
3
4
5
6
7
8
9
10
11
12
13
public int lastRemaining(int n, int m) {
List<Integer> list = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
list.add(i);
}
int idx = 0;
while (n > 1) {
idx = (idx + m - 1) % n;
list.remove(idx);
n--;
}
return list.get(0);
}

8、调整数组使奇数位于偶数前面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int[] exchange(int[] nums) {
int i = 0, j = nums.length - 1;
while (i < j) {
while (i < j && nums[i] % 2 == 1) {
i++;
}
while (i < j && nums[j] % 2 == 0) {
j--;
}
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
return nums;
}

9、三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[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
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);

for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
res.add(Arrays.asList(nums[i], nums[left], nums[right]));
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return res;
}

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
26
27
28
29
30
31
32
33
34
// quickSort 方法是快速排序的入口。它接受一个整数数组 nums、一个起始索引 low 和一个结束索引 high。
// 如果 low 小于 high,则选择一个基准元素(通常选择最后一个元素),
// 将数组划分为两部分,使得左边的元素都小于基准元素,右边的元素都大于基准元素。然后递归地对左右两部分进行快速排序。
public void quickSort(int[] nums, int low, int high) {
if (low < high) {
int pivotIndex = partition(nums, low, high);
quickSort(nums, low, pivotIndex - 1);
quickSort(nums, pivotIndex + 1, high);
}
}

// partition 方法用于划分数组。它选择基准元素 pivot,并使用两个指针 i 和 j 遍历数组。
// 如果 nums[j] 小于 pivot,则将 nums[j] 与 nums[i] 交换,
// 并将 i 向后移动一位。最后,将基准元素 pivot 与 nums[i+1] 交换,
// 使得基准元素位于正确的位置,并返回基准元素的索引。
private int partition(int[] nums, int low, int high) {
int pivot = nums[high];
int i = low - 1;

for (int j = low; j < high; j++) {
if (nums[j] < pivot) {
i++;
swap(nums, i, j);
}
}
swap(nums, i + 1, high);
return i + 1;
}

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

11、合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public int[][] merge(int[][] intervals) {
if(intervals==null||intervals.length<=1){
return intervals;
}
Arrays.sort(intervals,(a,b)->Integer.compare(a[0],b[0]));
List<int[]> merged=new ArrayList<>();
int[] currentInterval=intervals[0];

for (int i = 1; i < intervals.length; i++) {
if(intervals[i][0]<=currentInterval[1]){
currentInterval[1]=Math.max(currentInterval[1],intervals[i][1]);
}else{
merged.add(currentInterval);
currentInterval=intervals[i];
}
}
merged.add(currentInterval);
return merged.toArray(new int[merged.size()][]);
}

12、在排序数组中查找元素的左右边界

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-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
45
46
47
48
49
50
51
52
53
54
public int[] searchRange(int[] nums, int target) {
int[] result = {-1, -1};

// 查找第一个位置
int first = findFirst(nums, target);
if (first == -1) {
return result; // 未找到目标元素,返回初始结果
}

// 查找最后一个位置
int last = findLast(nums, target);

result[0] = first;
result[1] = last;
return result;
}

private int findFirst(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int first = -1;

while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1;
if (nums[mid] == target) {
first = mid; // 更新第一个位置
}
} else {
left = mid + 1;
}
}
return first;
}

private int findLast(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int last = -1;

while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) {
left = mid + 1;
if (nums[mid] == target) {
last = mid; // 更新最后一个位置
}
} else {
right = mid - 1;
}
}
return last;
}

13、下一个排列

整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。

  • 例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3][1,3,2][3,1,2][2,3,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
45
46
1. 从右到左遍历给定的排列,找到第一个破坏升序的相邻元素对,记为i-1和i。即找到满足nums[i-1] < nums[i]的i值。 

2. 如果找不到这样的相邻元素对,说明给定排列已经是字典序最大的排列,将其翻转为最小排列即可。

3. 如果找到了相邻元素对,从右到左找到第一个大于nums[i-1]的元素,记为j。

4. 交换nums[i-1]和nums[j]。

5. 将从i开始到最右边的元素进行翻转,以得到下一个字典序更大的排列。

public void nextPermutation(int[] nums) {
int i = nums.length - 2;
// 从右到左找到第一个破坏升序的相邻元素对
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}

if (i >= 0) {
int j = nums.length - 1;
// 从右到左找到第一个大于nums[i]的元素
while (j >= 0 && nums[j] <= nums[i]) {
j--;
}
// 交换nums[i]和nums[j]
swap(nums, i, j);
}
// 翻转从i+1到最右边的元素
reverse(nums, i + 1);
}

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

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

14、乘积最大子数组

给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

子数组 是数组的连续子序列。

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 maxProduct(int[] nums) {
int maxProduct = nums[0];
//currentMax 和 currentMin 来分别记录以当前元素结尾的子数组的最大乘积和最小乘积
int currentMax = nums[0];
int currentMin = nums[0];

for (int i = 1; i < nums.length; i++) {
//如果遇到负数,我们交换 currentMax 和 currentMin 的值,以保证在乘积计算中能够得到最大的乘积
if (nums[i] < 0) {
int temp = currentMax;
currentMax = currentMin;
currentMin = temp;
}

currentMax = Math.max(nums[i], currentMax * nums[i]);
currentMin = Math.min(nums[i], currentMin * nums[i]);

maxProduct = Math.max(maxProduct, currentMax);
}

return maxProduct;
}

15、和为 K 的子数组

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的连续子数组的个数。

前缀和是一种常用于解决数组或序列问题的技巧,它通常用于快速计算某个区间内元素的和。前缀和的基本思想是将原数组的前缀和(或累积和)存储在一个辅助数组中,以便在常数时间内获取任意区间的和。

下面是前缀和的基本概念:

  1. 前缀和数组:前缀和数组是一个与原始数组具有相同长度的数组,其中每个元素存储了原始数组从开头到当前位置的所有元素的累积和。通常,前缀和数组的第一个元素为0,然后依次计算后续元素的值。

  2. 计算前缀和数组:计算前缀和数组的一种常见方法是使用迭代。从数组的第一个元素开始,依次计算前缀和数组中的每个元素。例如,如果原数组为nums,前缀和数组prefixSum可以如下计算:

    1
    2
    3
    4
    prefixSum[0] = 0;  // 第一个元素为0
    for (int i = 1; i < nums.length; i++) {
    prefixSum[i] = prefixSum[i - 1] + nums[i];
    }
  3. 计算区间和:一旦有了前缀和数组,你可以用它来快速计算任意区间 [i, j] 内元素的和,其中 ij 是数组的索引。区间 [i, j] 的和等于 prefixSum[j] - prefixSum[i - 1]。需要注意,如果 i 等于0,那么 prefixSum[i - 1] 通常被定义为0。

通过遍历数组并计算累积和,同时使用哈希表来记录每个前缀和出现的次数。当找到一个前缀和与当前和的差值等于k时,就说明存在一个子数组的和等于k,因此增加计数器count。最后返回count即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int subarraySum(int[] nums, int k) {
int count = 0;
int sum = 0;
HashMap<Integer, Integer> map = new HashMap<>();
map.put(0, 1); // 初始化前缀和为0的次数为1,用于处理数组中某个子数组的和正好等于k的情况
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
// 如果最新的前缀和-k的结果在map中,说明存在j-i这个连续区间和为k。
if (map.containsKey(sum - k)) {
count += map.get(sum - k);
}
map.put(sum, map.getOrDefault(sum, 0) + 1);
}
return count;
}

16、长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target

找出该数组中满足其总和大于等于 target 的长度最小的 连续子数,如果不存在,返回0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public int minSubArrayLen(int target, int[] nums) {
int left = 0, right = 0;
int sum = 0;
int count = Integer.MAX_VALUE;
while (right < nums.length) {
sum += nums[right];
// 此处还得是while
while (sum >= target) {
count = Math.min(count, right - left + 1);
sum -= nums[left];
left++;
}
right++;
}
return count == Integer.MAX_VALUE ? 0 : count;
}

17、三数排序

存在数组[1,0,0,0,2,0,1,2],重新排序,使得按0,1,2排序。同leetcode荷兰三色旗问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void sortColors(int[] nums) {
int low = 0, mid = 0, high = nums.length - 1;

while (mid <= high) {//必须用mid判断,方便mid=0时,置换到前面
if (nums[mid] == 0) {
swap(nums, low++, mid++);
} else if (nums[mid] == 2) {
swap(nums, mid, high--);
} else { // nums[mid] == 1
mid++;
}
}
}

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

18、旋转数组查找

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
public static int rotatedBinarySearch(int[] array, 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[left] <= nums[mid]) {
// 目标值在左半部分
if (target >= nums[left] && target < nums[mid]) {
right = mid - 1; // 在左半部分查找
} else {
left = mid + 1; // 在右半部分查找
}
}
// 右半部分有序
else {
// 目标值在右半部分
if (target > nums[mid] && target <= nums[right]) {
left = mid + 1; // 在右半部分查找
} else {
right = mid - 1; // 在左半部分查找
}
}
}

return -1; // 没有找到目标值
}
}

二、字符串

1、字符串相加

给定两个字符串形式的非负整数 num1num2 ,计算它们的和并同样以字符串形式返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public String addStrings(String num1, String num2) {
StringBuilder res = new StringBuilder();
int m = num1.length() - 1, n = num2.length() - 1;
int carry = 0;
while (m >= 0 || n >= 0) {
int n1 = m >= 0 ? num1.charAt(m) - '0' : 0;
int n2 = n >= 0 ? num2.charAt(n) - '0' : 0;
int ans = n1 + n2 + carry;
carry = ans / 10;
res.append(ans % 10);
m--;
n--;
}
if (carry == 1) {
res.append(carry);
}
return res.reverse().toString();
}

2、最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
}
String prefix = strs[0];
for (String s : strs) {
while (s.indexOf(prefix) != 0) {
prefix = prefix.substring(0, prefix.length() - 1);
if (prefix.isEmpty()) {
return "";
}
}
}
return prefix;
}

3、无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

解题记录:

1、移除重复元素时,可能存在多个连续的重复元素,需要使用while语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public int lengthOfLongestSubstring(String s) {
int maxLength = 0;
int left = 0, right = 0;
Set<Character> set = new HashSet<>();
while (right < s.length()) {
if (!set.contains(s.charAt(right))) {
set.add(s.charAt(right));
maxLength = Math.max(maxLength, right - left + 1);
right++;
} else {
set.remove(s.charAt(left));
left++;
}
}
return maxLength;
}
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
// 2、输出具体子串
public static String findLongestSubstring(String s) {
if (s == null || s.isEmpty()) {
return "";
}

int start = 0; // 子串起始位置
int maxLen = 0; // 最大长度
int maxStart = 0; // 最长子串的起始位置
HashMap<Character, Integer> charIndexMap = new HashMap<>(); // 记录字符及其最近一次出现的位置

for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
// 如果字符已经在窗口内,移动左边界
if (charIndexMap.containsKey(ch) && charIndexMap.get(ch) >= start) {
start = charIndexMap.get(ch) + 1;
}

// 更新字符的最新位置
charIndexMap.put(ch, i);

// 检查当前窗口是否是最长的
if (i - start + 1 > maxLen) {
maxLen = i - start + 1;
maxStart = start;
}
}

// 返回最长子串
return s.substring(maxStart, maxStart + maxLen);
}

4、最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public String getLongestPalinedromeString(String s) {
String res = "";
for (int i = 0; i < s.length(); i++) {
String s1 = palinedrome(s, i, i);
String s2 = palinedrome(s, i, i + 1);
res = res.length() > s1.length() ? res : s1;
res = res.length() > s2.length() ? res : s2;
}
return res;
}

/**
* 中心往两边扩展寻找回文串
*/
private String palinedrome(String s,int i,int j){
while (i >= 0 && j < s.length() && s.charAt(i) == s.charAt(j)) {
i--;
j++;
}
return s.substring(i+1,j);
//i + 1:i 指针在最后一次循环中被减到了回文串开始位置的前一个字符,所以 i + 1 恰好指向了回文串的开始位置。j:j 指针在最后一次循环中被增到了回文串结束位置的下一个字符,所以 j 实际上超出了回文串的范围。但是 substring 方法的结束索引是不包含在内的,所以 j 恰好指明了回文串的结束位置。
}

5、字符串相乘

给定两个以字符串形式表示的非负整数 num1num2,返回 num1num2 的乘积,它们的乘积也表示为字符串形式。

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
public String multiply(String num1, String num2) {
int m = num1.length(), n = num2.length();
int[] res = new int[m + n];

for (int i = m - 1; i >= 0; i--) {
for (int j = n - 1; j >= 0; j--) {
int mul = (num1.charAt(i) - '0') * (num2.charAt(j) - '0');
// num1[i] 和 num2[j] 的乘积对应的就是 res[i+j] 和 res[i+j+1] 这两个位置.
int p1 = i + j; // 进位
int p2 = i + j + 1; //当前位

int sum = mul + res[p2];
res[p1] += sum / 10;
res[p2] = sum % 10;
}
}

StringBuilder str = new StringBuilder();
for (int i = 0; i < res.length; i++) {
if (i == 0 && res[i] == 0) {
continue;
}
str.append(res[i]);
}
return str.toString();
}

6、回文子串

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public int countSubstrings(String s) {
int count = 0;

for (int i = 0; i < s.length(); i++) {
count += expandAroundCenter(s, i, i); // 以单个字符为中心扩展
count += expandAroundCenter(s, i, i + 1); // 以两个字符之间为中心扩展
}

return count;
}

private int expandAroundCenter(String s, int left, int right) {
int count = 0;

while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
count++;
left--;
right++;
}

return count;
}

三、栈

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
public boolean isValid(String s) {
//如果为奇数,肯定无效
if(s.length()%2==1){
return false;
}

Map<Character,Character> map=new HashMap<>();
map.put(')','(');
map.put(']','[');
map.put('}','{');

Deque<Character> stack=new LinkedList<>();
for (int i = 0; i < s.length(); i++) {
char c=s.charAt(i);
if(map.containsKey(c)){
if(stack.isEmpty()||stack.peek()!=map.get(c)){
return false;
}
stack.pop();
}else{
stack.push(c);
}
}
return stack.isEmpty();
}

2、用栈实现队列

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty

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 MyQueue {
Stack<Integer> stack1;
Stack<Integer> stack2;

public MyQueue() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}

public void push(int x) {
stack1.push(x);
}

public int pop() {
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}

public int peek() {
if(stack2.isEmpty()){
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.peek();
}

public boolean empty() {
return stack1.isEmpty() && stack2.isEmpty();
}
}

3、最小栈

设计一个支持 pushpoptop 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

  • MinStack() 初始化堆栈对象。
  • void push(int val) 将元素val推入堆栈。
  • void pop() 删除堆栈顶部的元素。
  • int top() 获取堆栈顶部的元素。
  • int getMin() 获取堆栈中的最小元素。
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 MinStack {
Stack<Integer> stack1;
Stack<Integer> stack2;
public MinStack() {
stack1=new Stack<>();
stack2=new Stack<>();
}

public void push(int val) {
stack1.push(val);
if (stack2.isEmpty() || stack2.peek() >= val) {
stack2.push(val);
}
}

public void pop() {
if(stack2.peek().equals(stack1.pop())){ //此处同时进行了stack1的pop操作
stack2.pop();
}
}

public int top() {
return stack1.peek();
}

public int getMin() {
return stack2.peek();
}
}

4、验证出栈序列合法性

给定 pushedpopped 两个序列,每个序列中的 值都不重复,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回 true;否则,返回 false

思路:(1)遍历入栈数组,并将其放入栈中;(2)栈不空,循环判断栈顶元素是否和出栈数组第一个元素相同,相同则出栈,并将指向出栈数组的索引+1;(3)最后判断栈是否为空,空则为合法出栈序列。

1
2
3
4
5
6
7
8
9
10
11
12
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer> stack=new Stack<>();
int i=0;
for(int c:pushed){
stack.push(c);
while(!stack.isEmpty()&&stack.peek()==popped[i]){
stack.pop();
i++;
}
}
return stack.isEmpty();
}

5、字符串解码

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

输入:s = “3[a2[c]]”
输出:”accaccacc”

输入:s = “2[abc]3[cd]ef”
输出:”abcabccdcdcdef”

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
public String decodeString(String s) {
Stack<Integer> countStack = new Stack<>();
Stack<String> strStack = new Stack<>();
StringBuilder res = new StringBuilder();

int index = 0;
while (index < s.length()) {
char c = s.charAt(index);
if (Character.isDigit(c)) {
int count = 0;
// 此处获取字符前的重复数,可能>10
while (Character.isDigit(s.charAt(index))) {
count = count * 10 + (s.charAt(index) - '0');
index++;
}
countStack.push(count);
} else if (c == '[') {
// 如果遇到左括号 [ ,则将当前解码的字符串压入 stringStack ,并重置 result 为一个新的空字符串
strStack.push(res.toString());
res.setLength(0);
index++;
} else if (c == ']') {
// 如果遇到右括号 ] ,则从 stringStack 中弹出一个字符串,并根据重复次数将 result 追加到弹出的字符串中
StringBuilder temp = new StringBuilder(strStack.pop());
// 此处由于定义的Stack<String>,因此直接弹出一次得到字符串。
int repeatTimes = countStack.pop();
for (int i = 0; i < repeatTimes; i++) {
temp.append(res);
}
res = temp;
index++;
} else {
// 如果遇到其他字符,则直接将其追加到 result 中。
res.append(c);
index++;
}
}
return res.toString();
}

6、每日温度

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

思路:辅助栈存储数组下标

(1)遍历温度数组,入栈,并循环判断“栈不空,且当前数组元素是否大于栈顶元素,得到栈中元素到下一个更大元素的下标步长,即i-stack.pop()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public int[] dailyTemperatures(int[] T) {
int[] result = new int[T.length];
Stack<Integer> stack = new Stack<>();

for (int i = 0; i < T.length; i++) {
while (!stack.isEmpty() && T[i] > T[stack.peek()]) {
int index = stack.pop();
result[index] = i - index;
}
stack.push(i);
}

return result;
}

7、接雨水

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 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
public int trap(int[] height){
if(height==null){
return 0;
}
Stack<Integer> stack=new Stack<>();
int ans=0;
for(int i=0;i<height.length;i++){
//当栈不空,判断当前指向height[i]是否大于上一个已入栈元素的高
while(!stack.isEmpty()&&height[stack.peek()]<height[i]){
int curIdx=stack.pop();
//如果栈顶元素一直相等,那么一直pop出去,只留下第一个。
while(!stack.isEmpty()&&height[stack.peek()]==height[curIdx]){
stack.pop();
}
if(!stack.isEmpty()){
int stackTop=stack.peek();
// stackTop此时指向的是此次接住的雨水的左边界的位置。右边界是当前的柱体,即i。
// Math.min(height[stackTop], height[i]) 是左右柱子高度的min,减去height[curIdx]就是雨水的高度。
// i - stackTop - 1 是雨水的宽度。
ans += (Math.min(height[stackTop], height[i]) - height[curIdx]) * (i - stackTop - 1);

}
}
stack.add(i);//单调栈添加元素在此
}
return ans;
}

四、链表

1、反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

1
2
3
4
5
6
7
8
9
10
11
public ListNode reverseList(ListNode head) {
ListNode pre=null;
ListNode cur=head;
while (cur!=null){
ListNode temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
}

2、合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode mergeList = new ListNode(-1);
ListNode cur=mergeList;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
cur.next = list1;
list1 = list1.next;
} else {
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}

cur.next = list1 != null ? list1 : list2;
return mergeList.next;
}

3、环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

1
2
3
4
5
6
7
8
9
10
11
12
public boolean hasCycle(ListNode head) {
ListNode fast = head, slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;

if (fast == slow) {
return true;
}
}
return false;
}

4、相交链表

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null

1
2
3
4
5
6
7
8
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode first = headA, second = headB;
while (first != second) {
first = first != null ? first.next : headB;
second = second != null ? second.next : headA;
}
return first;
}

5、链表中倒数第k个结点

获取链表中倒数第k个结点。

1
2
3
4
5
6
7
8
9
10
11
public ListNode kthReverseNode(ListNode head, int k) {
ListNode fast = head, slow = head;
while (k-- != 0) {
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}

6、回文链表

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则返回 false

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
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) {
return true;
}

ListNode fast = head, slow = head;
ListNode cur = head, pre = null;
while (fast != null && fast.next != null) { //快慢指针寻找中点并反转前半部分
cur = slow;
fast = fast.next.next;
slow = slow.next;
cur.next = pre;
pre = cur;
}
if (fast != null) {
slow = slow.next; //slow指向后半部分
}

while (slow != null && pre != null) { // 比较
if (slow.val != pre.val) {
return false;
}
slow = slow.next;
pre = pre.next;
}
return true;
}

7、删除排序链表中的重复元素

给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。

1
2
3
4
5
6
7
8
9
10
11
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;
}

8、指定区间反转链表

给你单链表的头指针 head 和两个整数 leftright ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表

image.png
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummy=new ListNode(-1);
dummy.next=head;
ListNode pre=dummy;

for(int i=1;i<left;i++){
pre=pre.next;
}

// 在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。
// //pre-cur-node-node.next
ListNode cur=pre.next;
for(int i=left;i<right;i++){
ListNode node=cur.next;
cur.next=node.next;
node.next=pre.next;
pre.next=node;
}
return dummy.next;
}

9、环形链表的入环结点

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public ListNode detectCycle(ListNode head) {
ListNode fast = head, slow = head;
while (fast != null && fast.next != null) {
fast=fast.next.next;
slow=slow.next;

if (fast == slow) {
ListNode index1 = fast;
ListNode index2 = head;
while (index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}

10、重排链表

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

1
L0 → L1 → … → Ln - 1 → Ln

请将其重新排列后变为:

1
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 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
53
54
/**
* 10、重排链表
* 【思路】:1、找到中间结点,反转后半部分链表,然后交替合并。
*/
public void reorderList(ListNode head) {
if (head == null) {
return;
}
// 1、获得中间结点
ListNode mid = findMid(head);
ListNode head2=mid.next;
// 2、获得前半部分链表
mid.next=null;
// 3、反转后半部分链表
head2=reverse(head2);
// 4、合并
ListNode head1=head;
mergeLists(head1,head2);
}

private void mergeLists(ListNode head1, ListNode head2) {
while (head1 != null && head2 != null) {
ListNode temp1 = head1.next;
ListNode temp2 = head2.next;

// 此处需要改变head1.next的值,因此需要上面的暂存操作。
head1.next = head2;
head1 = temp1;

head2.next = head1;
head2 = temp2;
}
}

private ListNode findMid(ListNode head) {
ListNode fast = head, slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}

private ListNode reverse(ListNode head){
ListNode pre=null;
ListNode cur=head;
while (cur!=null){
ListNode temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
}

11、排序链表

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

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
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
// 快慢指针找到链表中点,将链表拆分为两个子链表
ListNode slow = head;
ListNode fast = head;
ListNode pre = null;
while (fast != null && fast.next != null) {
pre = slow;
fast = fast.next.next;
slow = slow.next;
}
//将链表从中点处分割成两个独立的链表。
pre.next = null;

// 递归对两个子链表进行排序
ListNode left = sortList(head);
ListNode right = sortList(slow);

// 合并两个有序链表
return merge(left, right);
}

private ListNode merge(ListNode left, ListNode right) {
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
while (left != null && right != null) {
if (left.val < right.val) {
cur.next = left;
left = left.next;
} else {
cur.next = right;
right = right.next;
}
cur = cur.next;
}
cur.next = left == null ? right : left;
return dummy.next;
}

12、K 个一组翻转链表

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

思路:找到第K个节点,翻转前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
public ListNode reverseKGroup(ListNode head, int k) {
if (head == null || head.next == null) {
return head;
}

ListNode tail = head;
for (int i = 0; i < k; i++) {
if (tail == null) { //注意用tail判空,不能用tail.next,存在NPC。
return head;
}
tail = tail.next;
}

ListNode newHead = reverse(head, tail);
head.next = reverseKGroup(tail, k);
return newHead;

}

private ListNode reverse(ListNode head, ListNode tail) {
ListNode pre = null;
ListNode cur = head;
while (cur != tail) {
ListNode temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}

13、奇偶链表

给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public ListNode oddEvenList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
// 分别定义奇偶结点
ListNode odd = head;
ListNode even = head.next;

// 暂存偶结点头部
ListNode temp = even;

// 循环,even更快到尾部
while (even != null && even.next != null) {
odd.next = even.next;
odd = odd.next;

even.next = odd.next;
even = even.next;
}
// 将所有奇结点的末尾接上偶结点的头
odd.next = temp;
return head;
}

14、旋转链表

给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 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
public ListNode rotateRight(ListNode head, int k) {
if (head == null || head.next == null || k == 0) {
return head;
}
// 计算链表长度并将尾节点与头节点相连
int length = 1;
ListNode tail = head;
while (tail.next != null) {
tail = tail.next;
length++;
}
tail.next = head;

// 计算实际需要右移的步数
int steps = length - k % length;

// 找到新的头节点和尾节点
ListNode newTail = tail;
while (steps > 0) {
newTail = newTail.next;
head = head.next;
steps--;
}
newTail.next = null;

return head;
}

15、分隔链表

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你应当 保留 两个分区中每个节点的初始相对位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public ListNode partition(ListNode head, int x) {
ListNode smallerHead = new ListNode(0);
ListNode greaterHead = new ListNode(0);
ListNode smaller = smallerHead;
ListNode greater = greaterHead;

while (head != null) {
if (head.val < x) {
smaller.next = head;
smaller = smaller.next;
} else {
greater.next = head;
greater = greater.next;
}
head = head.next;
}

greater.next = null;
smaller.next = greaterHead.next;

return smallerHead.next;
}

16、两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

2—>4—>6

1—>2—>5

结果: 3—>6—>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
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;

int carry = 0;
while (l1 != null || l2 != null) {
int val1 = l1 == null ? 0 : l1.val;
int val2 = l2 == null ? 0 : l2.val;

int sum = val1 + val2 + carry;
carry = sum / 10;
cur.next = new ListNode(sum % 10);
cur = cur.next;

if (l1 != null) {
l1 = l1.next;
}
if (l2 != null) {
l2 = l2.next;
}
}
if (carry == 1) {
cur.next = new ListNode(1);
}
return dummy.next;
}

17、两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。

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

ListNode temp=head.next;
head.next=swapPairs(temp.next);
temp.next=head;
return temp;
}

18、删除链表中的倒数第K个结点

思路:需要定义一个哑结点,如删除第一个元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode fast = dummy, slow = dummy;

// 将快指针向前移动n步
for (int i = 0; i <= n; i++) {
fast = fast.next;
}

// 快慢指针一起向前移动,直到快指针到达链表末尾
while (fast != null) {
fast = fast.next;
slow = slow.next;
}

// 删除倒数第k个节点
slow.next = slow.next.next;

return dummy.next;
}

19、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
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
class LRUCache {
class Node {
int key;
int value;
Node prev;
Node next;

public Node(int key, int value) {
this.key = key;
this.value = value;
}
}

private int capacity;
private Map<Integer, Node> cache;
private Node head;
private Node tail;

// HashMap+双链表
public LRUCache(int capacity) {
this.capacity = capacity;
this.cache = new HashMap<>();
this.head = new Node(0, 0);
this.tail = new Node(0, 0);
head.next = tail;
tail.prev = head;
}

// get方法根据给定的键获取对应的值,如果存在则返回值,并将该节点移动到链表头部。
public int get(int key) {
if (cache.containsKey(key)) {
Node node = cache.get(key);
removeNode(node);
addToHead(node);
return node.value;
}
return -1;
}

// put方法根据给定的键和值插入或更新缓存中的节点,如果插入操作导致容量超过限制,则逐出最久未使用的节点。
public void put(int key, int value) {
if (cache.containsKey(key)) {
Node node = cache.get(key);
node.value = value;
removeNode(node);
addToHead(node);
} else {
if (cache.size() == capacity) {
Node tailPrev = tail.prev;
removeNode(tailPrev);
cache.remove(tailPrev.key);
}
Node newNode = new Node(key, value);
cache.put(key, newNode);
addToHead(newNode);
}
}

private void removeNode(Node node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}

private void addToHead(Node node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
}

五、二叉树

1、二叉树的中序遍历

给定一个二叉树的根节点 root ,返回 它的 中序 遍历

递归:

1
2
3
4
5
6
7
8
9
10
11
12
13
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res=new ArrayList<>();
inOrder(root,res);
return res;
}
private void inOrder(TreeNode root, List<Integer> res) {
if(root==null){
return;
}
inOrder(root.left,res);
res.add(root.val);
inOrder(root.right,res);
}

迭代:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public List<Integer> inorderTraversalByIterator(TreeNode root) {
List<Integer> res=new ArrayList<>();
Deque<TreeNode> stack=new LinkedList<>();
while (root!=null||!stack.isEmpty()){
while (root!=null){
stack.push(root);
root=root.left;
}
root=stack.pop();
res.add(root.val);
root=root.right;
}
return res;
}

2、二叉树的前序遍历

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

递归:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
preorder(root, result);
return result;
}

public void preorder(TreeNode root, List<Integer> result) {
if (root == null) {
return;
}
result.add(root.val);
preorder(root.left, result);
preorder(root.right, result);
}

迭代:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public List<Integer> preOrderTraversalByIterator(TreeNode root) {
List<Integer> res=new ArrayList<>();
if(root==null){
return res;
}

Stack<TreeNode> stack=new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
// 入栈,根右左,那么出栈就是根左右(前序)
TreeNode node=stack.pop();
res.add(node.val);
while (node.right!=null){
stack.push(node.right);
}
while (node.left!=null){
stack.push(node.left);
}
}
return res;
}

3、二叉树的后序遍历

递归:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
postorder(root, res);
return res;
}

void postorder(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
postorder(root.left, list);
postorder(root.right, list);
list.add(root.val);
}

迭代:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
//入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果。
TreeNode node = stack.pop();
result.add(node.val);
if (node.left != null){
stack.push(node.left);
}
if (node.right != null){
stack.push(node.right);
}
}
Collections.reverse(result);
return result;
}

4、二叉树的层序遍历

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

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
public static List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) {
return result;
}

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

while (!queue.isEmpty()) {
int levelSize = queue.size();
List<Integer> currentLevel = new ArrayList<>();

for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
currentLevel.add(node.val);

if (node.left != null) {
queue.offer(node.left);
}

if (node.right != null) {
queue.offer(node.right);
}
}

result.add(currentLevel);
}

return result;
}

5、二叉树的最大深度

给定一个二叉树 root ,返回其最大深度。

二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。

1
2
3
4
5
6
7
8
public int maxDepth(TreeNode root) {
if(root==null){
return 0;
}
int left=maxDepth(root.left);
int right=maxDepth(root.right);
return Math.max(left,right)+1;
}

6、平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。

1
2
3
4
5
6
7
8
9
public boolean isBalanced(TreeNode root) {
if (root == null) return true;
return Math.abs(depth(root.left) - depth(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}

private int depth(TreeNode root) {
if (root == null) return 0;
return Math.max(depth(root.left), depth(root.right)) + 1;
}

7、对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public boolean isSymmetric(TreeNode root) {
if (root == null) {
return true;
}
return symmetric(root.left, root.right);
}

private boolean symmetric(TreeNode left, TreeNode right) {
if (left == null && right == null) {
return true;
}
if (left == null || right == null || left.val != right.val) {
return false;
}
return symmetric(left.left, right.right) && symmetric(left.right, right.left);
}

8、二叉树的直径

给你一棵二叉树的根节点,返回该树的 直径

二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root

两节点之间路径的 长度 由它们之间边数表示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public int diameterOfBinaryTree(TreeNode root) {
getHeight(root);
return res;
}

private int getHeight(TreeNode root){
if(root==null){
return 0;
}
int left=getHeight(root.left);
int right=getHeight(root.right);
res=Math.max(res,left+right);
return Math.max(left,right)+1;
}

9、路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum

1
2
3
4
5
6
7
8
9
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) {
return false;
}
if (targetSum - root.val == 0 && root.left == null && root.right == null) {
return true;
}
return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
}

10、翻转二叉树

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

相当于对每个结点,交换其左右结点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
}
return root;
}

11、另一个树的子树

给你两棵二叉树 rootsubRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if (root == null) {
return false;
}
return isSubtree(root.left,subRoot)||isSubtree(root.right,subRoot)||dfs(root,subRoot);
}

private boolean dfs(TreeNode root, TreeNode subRoot) {
if(subRoot==null&&root==null){
return true;
}
if(root==null||subRoot==null||subRoot.val!=root.val){
return false;
}
return dfs(root.left,subRoot.left)&&dfs(root.right,subRoot.right);
}

11、二叉树的所有路径

给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public List<List<Integer>> binaryTreePaths(TreeNode root) {
List<List<Integer>> paths = new ArrayList<>();
if (root == null) {
return paths;
}
List<Integer> list = new ArrayList<>();
backTrack(root, paths, list);
return paths;
}

private void backTrack(TreeNode root, List<List<Integer>> path, List<Integer> list) {
if (root == null) {
return;
}
list.add(root.val);
if (root.left == null && root.right == null) {
path.add(new ArrayList<>(list));
} else {
backTrack(root.left, path, list);
backTrack(root.right, path, list);
}
list.remove(list.size() - 1);
}

12、二叉树的右视图

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

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 List<Integer> rightSideView(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}

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

while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();

if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}

if (i == size - 1) {
result.add(node.val);
}
}
}
return result;
}

13、验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
long pre = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if (root == null) {
return true;
}
// 访问左子树
if (!isValidBST(root.left)) {
return false;
}
// 访问当前节点:如果当前节点小于等于中序遍历的前一个节点,说明不满足BST,返回 false;否则继续遍历。
if (root.val <= pre) {
return false;
}
pre = root.val;
// 访问右子树
return isValidBST(root.right);
}

14、二叉搜索树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

1
2
3
4
5
6
7
8
9
10
11
12
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while(root!=null){
if(p.val<root.val&&q.val<root.val){
root=root.left;
}else if(p.val>root.val&&q.val>root.val){
root=root.right;
}else{
return root;
}
}
return root;
}

15、二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

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

16、序列化与反序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树,不对序列化之后的字符串进行约束,但要求能够根据序列化之后的字符串重新构造出一棵与原二叉树相同的树。

二叉树的序列化(Serialize)是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串。

二叉树的反序列化(Deserialize)是指:根据某种遍历顺序得到的序列化字符串结果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
int index=-1;
String Serialize(TreeNode root) {
if(root==null){
return "#";
}else{
return root.val+","+Serialize(root.left)+","+Serialize(root.right);
}

}
TreeNode Deserialize(String str) {
String[] s=str.split(",");
index++;
int len=s.length;
if(index>len){
return null;
}
TreeNode node=null;
if(!s[index].equals("#")){
node=new TreeNode(Integer.parseInt(s[index]));
node.left=Deserialize(str);
node.right=Deserialize(str);
}
return node;
}

17、构造二叉树

(1)从前序与中序遍历序列构造二叉树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public TreeNode buildTree(int[] preorder, int[] inorder) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
return buildTreeHelper(preorder, 0, preorder.length, inorder, 0, inorder.length, map);
}

private TreeNode buildTreeHelper(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd, HashMap<Integer, Integer> map) {
if (preStart > preEnd || inStart > inEnd) {
return null;
}
int rootVal = preorder[preStart];
TreeNode root = new TreeNode(rootVal);
//获取根结点在中序中的索引
int rootIndex = map.get(rootVal);
int leftSize = rootIndex - inStart;

root.left = buildTreeHelper(preorder, preStart + 1, preStart + leftSize, inorder, inStart, rootIndex - 1, map);
root.right = buildTreeHelper(preorder, preStart + leftSize + 1, preEnd, inorder, rootIndex + 1, inEnd, map);

return root;
}

(2)从后序与中序遍历序列构造二叉树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public TreeNode buildTree(int[] inorder, int[] postorder) {
HashMap<Integer,Integer> map=new HashMap<>();
for(int i=0;i<inorder.length;i++){
map.put(inorder[i],i);
}
return buildTreeHelper(inorder,0,inorder.length-1,postorder,0,postorder.length-1,map);
}

public TreeNode buildTreeHelper(int[] inorder,int i_start,int i_end,int[] postorder,int p_start,int p_end,HashMap<Integer,Integer> map){
if(p_start>p_end||i_start>i_end){
return null;
}
int root_val=postorder[p_end];
TreeNode root=new TreeNode(root_val);
int i_root_index=map.get(root_val);
int leftnum=i_root_index-i_start;

root.left=buildTreeHelper(inorder,i_start,i_root_index-1,postorder,p_start,p_start+leftnum-1,map);
root.right=buildTreeHelper(inorder,i_root_index+1,i_end,postorder,p_start+leftnum,p_end-1,map);
return root;

}

18、有序数组转换为二叉搜索树

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public TreeNode sortedArrayToBST(int[] nums) {
if (nums == null || nums.length == 0) {
return null;
}
return dfs(nums, 0, nums.length - 1);

}

private TreeNode dfs(int[] nums, int i, int j) {
if (i > j) {
return null;
}
int mid = i + (j - i) / 2;
TreeNode root = new TreeNode(nums[mid]);
root.left = dfs(nums, i, mid - 1);
root.right = dfs(nums, mid + 1, j);
return root;
}

19、求根节点到叶节点数字之和

给你一个二叉树的根节点 root ,树中每个节点都存放有一个 09 之间的数字。

每条从根节点到叶节点的路径都代表一个数字:

  • 例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123

计算从根节点到叶节点生成的 所有数字之和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public int sumNumbers(TreeNode root) {
return dfs(root, 0);
}

private int dfs(TreeNode node, int sum) {
if (node == null) {
return 0;
}

int currentSum = sum * 10 + node.val;
if (node.left == null && node.right == null) {
return currentSum;
}

int leftSum = dfs(node.left, currentSum);
int rightSum = dfs(node.right, currentSum);

return leftSum + rightSum;
}

20、路径总和 II

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();

backtrack(res, list, root, targetSum);
return res;
}

private void backtrack(List<List<Integer>> res, List<Integer> list, TreeNode root, int targetSum) {
if (root == null) {
return;
}
list.add(root.val);
targetSum -= root.val;

if (targetSum == 0 && root.left == null && root.right == null) {
res.add(new ArrayList<>(list));
}

backtrack(res, list, root.left, targetSum);//上面有判空,此处省略
backtrack(res, list, root.right, targetSum);
list.remove(list.size() - 1);
}

21、不同的二叉搜索树

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int numTrees(int n) {
//初始化 dp 数组
int[] dp = new int[n + 1];
//初始化0个节点和1个节点的情况
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i; j++) {
//对于第i个节点,需要考虑1作为根节点直到i作为根节点的情况,所以需要累加
//一共i个节点,对于根节点j时,左子树的节点个数为j-1,右子树的节点个数为i-j
dp[i] += dp[j - 1] * dp[i - j];
}
}
return dp[n];
}

22、二叉搜索树的第k个节点

给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 小的元素(从 1 开始计数)。

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 kthSmallest(TreeNode root, int k) {
Stack<TreeNode> stack = new Stack<>();
int count = 0;

while (root != null || !stack.isEmpty()) {
// 先遍历右子树
while (root != null) {
stack.push(root);
root = root.left;
}

// 访问节点
root = stack.pop();
count++;
if (count == k) {
return root.val; // 找到了第k大的元素
}

// 遍历左子树
root = root.right;
}
return -1;
}

六、贪心算法

1、买卖股票的最佳时机 I

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

1
2
3
4
5
6
7
8
public int maxProfit(int[] prices) {
int cost = prices[0], profit = 0;
for (int price : prices) {
cost = Math.min(cost, price);
profit = Math.max(profit, price - cost);
}
return profit;
}

2、买卖股票的最佳时机 II

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。返回 你能获得的 最大 利润。

1
2
3
4
5
6
7
8
9
10
public int maxProfit(int[] prices) {
int profit = 0;
for (int i = 1; i < prices.length; i++) {
int temp = prices[i] - prices[i - 1];
if (temp > 0) {
profit += temp;
}
}
return profit;
}

七、动态规划

1、爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 12 个台阶。你有多少种不同的方法可以爬到楼顶呢?

1
2
3
4
5
6
7
8
9
public int climbStairs(int n) {
int[] dp=new int[n+1];
dp[0]=1;
dp[1]=1;
for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}

2、最长递增子序列

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

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

思路:动态规划,初始化dp数组默认值1。

(1)双层遍历,i从1到nums.length,j从0到i,如果nums[j] < nums[i],更新dp[i] = Math.max(dp[i], dp[j] + 1)

(2)外层循环更新maxLen = Math.max(maxLen, dp[i])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int[] dp = new int[nums.length];
dp[0] = 1;
Arrays.fill(dp, 1);
int maxLen = 1;

for (int i = 1; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
maxLen = Math.max(maxLen, dp[i]);
}
return maxLen;
}

3、最长公共子序列

给定两个字符串 text1text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[][] dp = new int[m + 1][n + 1];

for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
char c1 = text1.charAt(i-1);
char c2 = text2.charAt(j-1);
if (c1 == c2) {
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[m][n];
}

4、打家劫舍

(1)一排

1
2
3
4
5
6
7
8
9
10
11
12
13
public int rob(int[] nums) {
if (nums == null || nums.length == 0) return 0;
if (nums.length == 1) return nums[0];

int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = Math.max(dp[0], nums[1]);
for (int i = 2; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
}

return dp[nums.length - 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
public int rob(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
if (nums.length == 1) {
return nums[0];
}
// 分别计算从第一间房屋到倒数第二间房屋和从第二间房屋到最后一间房屋的最大金额
int max1 = robRange(nums, 0, nums.length - 2);
int max2 = robRange(nums, 1, nums.length - 1);
// 返回两种情况下的最大金额
return Math.max(max1, max2);
}

private int robRange(int[] nums, int start, int end) {
int prevMax = 0; // 前一间房屋的最大金额
int currMax = 0; // 当前房屋的最大金额
for (int i = start; i <= end; i++) {
int temp = currMax;
currMax = Math.max(prevMax + nums[i], currMax);
prevMax = temp;
}
return currMax;
}

5、最小路径和

给定一个包含非负整数的 *m* x *n* 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

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 minPathSum(int[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}

int m = grid.length, n = grid[0].length;
int[][] dp = new int[m][n];
dp[0][0] = grid[0][0];

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

for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[m - 1][n - 1];
}

6、最大正方形

在一个由 '0''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
// 思路:我们用 dp(i, j)表示以 (i, j) 为右下角,且只包含 1 的正方形的边长最大值。
// 如果该位置的值是 1,则 dp(i, j) 的值由其上方、左方和左上方的三个相邻位置的 dp值决定。具体而言,当前位置的元素值等于三个相邻位置的元素中的最小值加 1
public int maximalSquare(char[][] matrix) {
int row = matrix.length, col = matrix[0].length;
if (matrix == null || row == 0 || col == 0) {
return 0;
}

int[][] dp = new int[row][col];
int maxSide = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (matrix[i][j] == '1') {
if (i == 0 || j == 0) {
dp[i][j] = 1;
} else {
dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
}
maxSide = Math.max(maxSide, dp[i][j]);
}
}
}
return maxSide * maxSide;
}

7、最长重复子数组

给两个整数数组 nums1nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度

1
2
3
4
5
6
7
8
9
10
11
12
13
public int findLength(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length;
int[][] dp = new int[m + 1][n + 1];
int res = 0;

for (int i = m - 1; i >= 0; i--) {
for (int j = n - 1; j >= 0; j--) {
dp[i][j] = nums1[i] == nums2[j] ? dp[i + 1][j + 1] + 1 : 0;
res = Math.max(res, dp[i][j]);
}
}
return res;
}

8、零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public int coinChange(int[] coins, int amount) {
//创建一个长度为 amount + 1 的 dp 数组,初始值都设置为 amount + 1 (表示不可能的情况)
int[] dp=new int[amount+1];
Arrays.fill(dp,amount+1);
//将 dp[0] 设置为0,表示兑换金额为0时不需要任何硬币。
dp[0]=0;

//遍历金额从1到 amount ,并尝试使用每种硬币来兑换。
for(int i=1;i<=amount;i++){
for(int coin:coins){
if(coin<=i){
dp[i]=Math.min(dp[i],dp[i-coin]+1);
}
}
}
//如果其值大于 amount ,则表示无法兑换,返回-1。
return dp[amount]>amount?-1:dp[amount];
}

9、完全平方数

给你一个整数 n ,返回和为 n 的完全平方数的最少数量。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,14916 都是完全平方数。

1
2
3
4
5
6
7
8
9
10
11
12
13
public int numSquares(int n) {
int dp[] = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
//依次求出 1, 2... 直到 n 的解
for (int i = 1; i <= n; i++) {
//依次减去一个平方数
for (int j = 1; j * j <= i; j++) {
dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
}
}
return dp[n];
}

八、深度/广度优先遍历

1、岛屿数量

给你一个由 '1'(陆地)和 '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
public int numIslands(char[][] grid) {
int count = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
if (grid[i][j] == '1') {
dfs(grid, i, j);
count++;
}
}
}
return count;
}

private void dfs(char[][] grid, int i, int j) {
if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == '0') {
return;
}
//将已经访问过的岛屿单元格标记为0,以避免重复计算。
grid[i][j] = '0';
dfs(grid, i + 1, j);
dfs(grid, i, j + 1);
dfs(grid, i - 1, j);
dfs(grid, i, j - 1);
}

2、括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public List<String> generateParenthesis(int n) {
List<String> res=new ArrayList<>();
dfs(n,n,res,"");
return res;
}

private void dfs(int left, int right, List<String> res, String cur) {
if (left == 0 && right == 0) {
res.add(cur);
return;
}
if (left > 0) {
dfs(left - 1, right, res, cur + "(");
}
if (right > left) {
dfs(left, right - 1, res, cur + ")");
}
}

3、太平洋大西洋水流问题

有一个 m × n 的矩形岛屿,与 太平洋大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界,而 “大西洋” 处于大陆的右边界和下边界。

返回既能流向太平洋,也能流向大西洋的数字下标list。

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
public List<List<Integer>> pacificAndAtlantic(int[][] matrix){
if(matrix==null||matrix.length==0){
return new ArrayList<>();
}
int m=matrix.length;
int n=matrix[0].length;
int[][] pacific=new int[m][n];
int[][] atlantic=new int[m][n];

for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(i==0||j==0){
dfs(matrix,pacific,i,j,matrix[i][j]);
}
if(i==m-1||j==n-1){
dfs(matrix,atlantic,i,j,matrix[i][j]);
}
}
}

List<List<Integer>> res=new ArrayList<>();
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(pacific[i][j]==1&&atlantic[i][j]==1){
res.add(Arrays.asList(i,j));
}
}
}
return res;
}
public void dfs(int[][] matrix,int[][] ocean,int i,int j,int pre){
if(i<0 || i>=matrix.length || j<0 || j>=matrix[0].length || ocean[i][j]==1 || matrix[i][j]<pre){
return;
}
ocean[i][j]=1;
dfs(matrix, ocean, i - 1, j, matrix[i][j]);
dfs(matrix, ocean, i + 1, j, matrix[i][j]);
dfs(matrix, ocean, i, j - 1, matrix[i][j]);
dfs(matrix, ocean, i, j + 1, matrix[i][j]);
}

4、被围绕的区域

给你一个 m x n 的矩阵 board ,由若干字符 'X''O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O''X' 填充。

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
public void sounded(char[][] board){
if(board==null||board.length==0){//非常重要
return;
}
int m=board.length;
int n=board[0].length;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
boolean isEdge=(i==0||i==m-1||j==0||j==n-1);
if(isEdge&&board[i][j]=='O'){
dfs(board,i,j);
}
}
}
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(board[i][j]=='O'){
board[i][j]='X';
}
if(board[i][j]=='#'){
board[i][j]='O';
}
}
}
}
public void dfs(char[][] board,int i,int j){
//注意此处多个或运算符连接需空隔好。
if(i < 0 || j < 0 || i >= board.length || j >= board[0].length || board[i][j] == 'X' || board[i][j] == '#'){
return;
}
// 将边界上的O临时替换为#
board[i][j]='#';
dfs(board,i+1,j);
dfs(board,i-1,j);
dfs(board,i,j+1);
dfs(board,i,j-1);
}

九、回溯算法

1、全排列

(1)给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列。你可以 按任意顺序 返回答案。

思路:回溯

(1)list的大小和nums数组长度相同时,加入list到结果集res。

(2)for循环遍历数组,如果list存在nums[i],跳过该数,并进行常规添加-回溯下一层-撤销操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
backTrack(nums, res, list);
return res;
}

private void backTrack(int[] nums, List<List<Integer>> res, List<Integer> list) {
if (list.size() == nums.length) {
res.add(new ArrayList<>(list));
}
for (int i = 0; i < nums.length; i++) {
if(list.contains(nums[i])){
continue;
}
list.add(nums[i]);
backTrack(nums, res, list);
list.remove(list.size() - 1);
}
}

(2)给定一个可包含重复数字的序列 nums按任意顺序 返回所有不重复的全排列。

思路:由于存在重复元素,添加boolean[] used数组记录当前数字是否用过。

(1)为方便去重,先对nums进行排序。

(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 List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
boolean[] used = new boolean[nums.length];

Arrays.sort(nums);
backtrack(res, list, nums, used);
return res;
}

private void backtrack(List<List<Integer>> res, List<Integer> list, int[] nums, boolean[] used) {
if (list.size() == nums.length) {
res.add(new ArrayList<>(list));
return;
}

for (int i = 0; i < nums.length; i++) {
//如果当前数字已经被使用过,或者前一个相同的数字未被使用,则跳过当前数字。
if (used[i] || (i > 0 && nums[i] == nums[i - 1] && !used[i - 1])) {
continue;
}
used[i] = true;
list.add(nums[i]);
backtrack(res, list, nums, used);
used[i] = false;
list.remove(list.size() - 1);
}
}

2、子集

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
List<Integer> currentSubset = new ArrayList<>();
// 子集,不需要包括所有元素,因此传入一个起始索引
backtrack(nums, 0, currentSubset, result);
return result;
}

private void backtrack(int[] nums, int start, List<Integer> currentSubset, List<List<Integer>> result) {
result.add(new ArrayList<>(currentSubset));
for (int i = start; i < nums.length; i++) {
currentSubset.add(nums[i]);
backtrack(nums, i + 1, currentSubset, result);
currentSubset.remove(currentSubset.size() - 1);
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public List<List<Integer>> subsetsWithDup(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums); // 对数组进行排序,以便处理重复元素
backtrack(nums, 0, new ArrayList<>(), result);
return result;
}

private void backtrack(int[] nums, int start, List<Integer> currentSubset, List<List<Integer>> result) {
result.add(new ArrayList<>(currentSubset));
for (int i = start; i < nums.length; i++) {
if (i > start && nums[i] == nums[i - 1]) {
continue; // 跳过重复元素,避免生成重复的子集
}
currentSubset.add(nums[i]);
backtrack(nums, i + 1, currentSubset, result);
currentSubset.remove(currentSubset.size() - 1);
}
}

3、单词搜索

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false

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
public boolean exist(char[][] board, String word) {
if (board == null || board.length == 0 || word == null || word.length() == 0) {
return false;
}

int rows = board.length;
int cols = board[0].length;
boolean[][] visited = new boolean[rows][cols];

for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (board[i][j] == word.charAt(0) && backtrack(board, word, i, j, 0, visited)) {
return true;
}
}
}
return false;
}

private boolean backtrack(char[][] board, String word, int row, int col, int index, boolean[][] visited) {
if (index == word.length()) {
return true;
}

if (row < 0 || col < 0 || row >= board.length || col >= board[0].length
|| visited[row][col] || board[row][col] != word.charAt(index)) {
return false;
}

visited[row][col] = true;

boolean result =
backtrack(board, word, row + 1, col, index + 1, visited) ||
backtrack(board, word, row - 1, col, index + 1, visited) ||
backtrack(board, word, row, col + 1, index + 1, visited) ||
backtrack(board, word, row, col - 1, index + 1, visited);

visited[row][col] = false;

return result;
}

4、组合

(1)给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合

candidates 中的 同一个 数字可以 无限制重复被选取

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

private void backtrack(List<List<Integer>> combinations,List<Integer> res,int[] candidates,int target,int start){
if(target==0){
combinations.add(new ArrayList<>(res));
return;
}
for(int i=start;i<candidates.length;i++){
if(candidates[i]<=target){
res.add(candidates[i]);
backtrack(combinations,res,candidates,target-candidates[i],i);
res.remove(res.size()-1);
}
}
}

(2)给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> result = new ArrayList<>();
List<Integer> current = new ArrayList<>();
backtrack(result, current, n, k, 1);
return result;
}

private void backtrack(List<List<Integer>> result, List<Integer> current, int n, int k, int start) {
if (k == 0) {
result.add(new ArrayList<>(current));
return;
}

for (int i = start; i <= n; i++) {
current.add(i);
backtrack(result, current, n, k - 1, i + 1);
current.remove(current.size() - 1);
}
}

5、分割回文串

给定一个字符串 s ,请将 s 分割成一些子串,使每个子串都是 回文串 ,返回 s 所有可能的分割方案。

示例 1:

1
2
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
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
public List<List<String>> partition(String s) {
List<List<String>> partitions=new ArrayList<>();
List<String> curpartion=new ArrayList<>();
doPartition(s,partitions,curpartion);
return partitions;

}
private void doPartition(String s,List<List<String>> partitions,List<String> curpartion){
// 字符串遍历结束,添加本轮分割结果
if(s.length()==0){
partitions.add(new ArrayList<>(curpartion));
return;
}
for(int i=0;i<s.length();i++){
// 不断截取字符串前i个字符判断是否回文
if(isPalindrome(s,0,i)){
curpartion.add(s.substring(0,i+1));
doPartition(s.substring(i+1),partitions,curpartion);
curpartion.remove(curpartion.size()-1);
}
}
}
private boolean isPalindrome(String s,int i,int j){
while(i<j){
if(s.charAt(i++)!=s.charAt(j--)){
return false;
}
}
return true;
}

6、复原IP地址

给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址。有效 IP 地址 正好由四个整数(每个整数位于 0255 之间组成,且不能含有前导 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
public List<String> restoreIpAddresses(String s) {
List<String> result = new ArrayList<>();
if (s.length() < 4 || s.length() > 12) {
return result;
}

backtrack(s, 0, 0, "", result);
return result;
}

private void backtrack(String s, int start, int segmentId, String currentIp, List<String> result) {
// 如果找到了 4 段 IP 地址并且遍历完了字符串,那么就是一种答案
if (segmentId == 4 && start == s.length()) {
result.add(currentIp.substring(0, currentIp.length() - 1)); // 去掉最后一个点
return;
}

// 如果已经找到了 4 个数字段,但输入字符串还有剩余部分未被处理,则直接返回
if (segmentId == 4) {
return;
}

// start + 3:表示当前数字段最多可以包含 3 个字符,因为 IP 地址的每个数字段的最大值为 255
for (int end = start; end < Math.min(start + 3, s.length()); end++) {
String segment = s.substring(start, end + 1);
if (isValid(segment)) {
backtrack(s, end + 1, segmentId + 1, currentIp + segment + ".", result);
}
}
}

private boolean isValid(String segment) {
// 检查是否以 0 开头
if (segment.startsWith("0") && segment.length() > 1) {
return false;
}
// 检查是否在 0 到 255 之间
int num = Integer.parseInt(segment);
return num >= 0 && num <= 255;
}

十、多线程

1、线程a、b、c顺序执行

要求线程a执行完才开始线程b,线程b执行完才开始线程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
33
34
35
public class ThreadExecutionOrder {
public static void main(String[] args) {
Thread threadA = new Thread(new Task("Thread A"));
Thread threadB = new Thread(new Task("Thread B"));
Thread threadC = new Thread(new Task("Thread C"));

try {
threadA.start();
threadA.join(); // 等待线程A执行完毕

threadB.start();
threadB.join(); // 等待线程B执行完毕

threadC.start();
threadC.join(); // 等待线程C执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
}

static class Task implements Runnable {
private final String name;

public Task(String name) {
this.name = name;
}

@Override
public void run() {
System.out.println(name + " is running.");
// 执行任务逻辑
System.out.println(name + " is done.");
}
}
}

2、两个线程轮流打印数字

两个线程轮流打印数字,从1到100。

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
public class PrintNumbers {
private static final int MAX_NUMBER = 100;
private static int number = 1;
private static final Object lock = new Object();

public static void main(String[] args) {
Thread thread1 = new Thread(new PrintNumberTask(1));
Thread thread2 = new Thread(new PrintNumberTask(2));

thread1.start();
thread2.start();
}

static class PrintNumberTask implements Runnable {
private final int threadId;

public PrintNumberTask(int threadId) {
this.threadId = threadId;
}

@Override
public void run() {
while (number <= MAX_NUMBER) {
synchronized (lock) {
if (number % 2 == threadId - 1) {
System.out.println("Thread " + threadId + ": " + number);
number++;
lock.notifyAll();
} else {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}

解释:

判断 number % 2 == threadId - 1 的目的是为了确定当前线程是否应该打印当前的数字。

在这里, number 表示当前要打印的数字, threadId 表示线程的ID(1或2)。根据这个判断条件,两个线程将交替打印数字。

具体解释如下:

  • threadId 为1时,判断条件为 number % 2 == 0 。这意味着只有当 number 为偶数时,线程1才会打印该数字。
  • threadId 为2时,判断条件为 number % 2 == 1 。这意味着只有当 number 为奇数时,线程2才会打印该数字。

通过这种方式,两个线程在获取到锁后会进行条件判断,只有符合条件的线程才会执行打印操作,并将 number 加1。否则,线程会通过 wait() 方法释放锁并等待,直到被其他线程通过 notifyAll() 方法唤醒。

3、两个线程按序轮流打印

写两个线程,一个线程打印1~ 52,另一个线程打印A~Z,打印顺序是12A34B…5152Z。

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
public class PrintNumbersAndLetters {
private static final Object lock = new Object();
private static int number = 1;
private static char letter = 'A';

public static void main(String[] args) {
Thread numberThread = new Thread(new PrintNumberTask());
Thread letterThread = new Thread(new PrintLetterTask());

numberThread.start();
letterThread.start();
}

static class PrintNumberTask implements Runnable {
@Override
public void run() {
synchronized (lock) {
while (number <= 52) {
if (number % 2 == 1) {
System.out.print(number);
number++;
System.out.print(number);
number++;
lock.notify();
} else {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}

static class PrintLetterTask implements Runnable {
@Override
public void run() {
synchronized (lock) {
while (letter <= 'Z') {
System.out.print(letter);
letter++;
lock.notify();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}

解释:

PrintNumberTask 中,使用 while 循环判断 number 是否小于等于52。如果是奇数,则打印当前数字和下一个数字,并将 number 加2。然后通过 notify() 方法唤醒等待的线程,并自己进入等待状态。如果是偶数,则通过 wait() 方法释放锁并等待。

PrintLetterTask 中,使用 while 循环判断 letter 是否小于等于字母’Z’。每次循环打印当前字母,并将 letter 递增。然后通过 notify() 方法唤醒等待的线程,并自己进入等待状态。

4、三个线程按次序轮流打印

编写一个程序,启动三个线程,三个线程的ID分别是A,B,C;,每个线程将自己的ID值在屏幕上打印5遍,打印顺序是ABCABC…

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
public class PrintABC {
private static final Object lock = new Object();
private static int currentThread = 0;
private static final int MAX_COUNT = 5;

public static void main(String[] args) {
Thread threadA = new Thread(new PrintTask("A", 0));
Thread threadB = new Thread(new PrintTask("B", 1));
Thread threadC = new Thread(new PrintTask("C", 2));

threadA.start();
threadB.start();
threadC.start();
}

static class PrintTask implements Runnable {
private final String threadId;
private final int id;

public PrintTask(String threadId, int id) {
this.threadId = threadId;
this.id = id;
}

@Override
public void run() {
for (int i = 0; i < MAX_COUNT; i++) {
synchronized (lock) {
while (currentThread != id) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(threadId);
currentThread = (currentThread + 1) % 3;
lock.notifyAll();
}
}
}
}
}

解释:

PrintTask 中,使用 for 循环来打印ID值。在每次循环中,线程首先通过 synchronized 块获取到锁,并检查 currentThread 的值是否等于自己的ID值。如果不是,则线程进入等待状态,释放锁并等待被唤醒。如果是,则打印自己的ID值,并更新 currentThread 的值为下一个线程的ID。最后,通过 notifyAll() 方法唤醒其他等待的线程,并释放锁。

5、十个线程打印求和

编写10个线程,第一个线程从1加到10,第二个线程从11加20…第十个线程从91加到100,最后再把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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 编写10个线程,第一个线程从1加到10,第二个线程从11加20…第十个线程从91加到100,最后再把10个线程结果相加
*/
public class TenThreadSum {

public static class SumThread extends Thread{

int forct = 0; int sum = 0;

SumThread(int forct){
this.forct = forct;
}

@Override
public void run() {
for(int i = 1; i <= 10; i++){
sum += i + forct * 10;
}
System.out.println(getName() + " " + sum);
}
}

public static void main(String[] args) {

int result = 0;

for(int i = 0; i < 10; i++){
SumThread sumThread = new SumThread(i);
sumThread.start();
try {
sumThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
result = result + sumThread.sum;
}
System.out.println("result " + result);
}
}

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
public class TicketSeller {
private static int tickets = 100; // 总票数,假设为100张

public static void main(String[] args) {
Thread seller1 = new Thread(new Seller(), "窗口1");
Thread seller2 = new Thread(new Seller(), "窗口2");
Thread seller3 = new Thread(new Seller(), "窗口3");

seller1.start();
seller2.start();
seller3.start();
}

static class Seller implements Runnable {
@Override
public void run() {
while (true) {
synchronized (TicketSeller.class) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售出票号:" + tickets);
tickets--;
} else {
break; // 所有票已售完,退出循环
}
}
}
}
}
}

7、交替打印两个数组

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
public class AlternatePrint {
private static final Object lock = new Object();
private static boolean printFirstArray = true;

public static void main(String[] args) {
int[] array1 = {1, 3, 5, 7, 9};
int[] array2 = {2, 4, 6, 8, 10};

Thread thread1 = new Thread(new PrintTask(array1));
Thread thread2 = new Thread(new PrintTask(array2));

thread1.start();
thread2.start();
}

static class PrintTask implements Runnable {
private final int[] array;

public PrintTask(int[] array) {
this.array = array;
}

@Override
public void run() {
for (int num : array) {
synchronized (lock) {
while ((printFirstArray && array != array1) || (!printFirstArray && array != array2)) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(num);
printFirstArray = !printFirstArray;
lock.notifyAll();
}
}
}
}
}

解释:

我们创建了两个线程 thread1thread2 ,它们分别负责打印 array1array2 的元素。每个线程通过循环遍历数组,并使用 synchronized 关键字获取到共享的锁对象 lock

在每次循环中,线程首先通过 synchronized 块获取到锁,并检查当前应该打印的数组是否与自己负责打印的数组相符。如果不相符,则线程进入等待状态,释放锁并等待被唤醒。如果相符,则打印当前元素,并将 printFirstArray 的值取反,以便下一个线程可以打印另一个数组的元素。最后,通过 notifyAll() 方法唤醒其他等待的线程,并释放锁。

8、生产者消费者模式

1、BlockingQueue 实现生产者消费者模式

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
public static void main(String[] args) {
BlockingQueue<Object> queue=new ArrayBlockingQueue<>(10);
Runnable producer=()->{
while (true){
try {
queue.put(new Object());
System.out.println("producing...");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
new Thread(producer).start();

Runnable consumer=()->{
while (true){
try {
queue.take();
System.out.println("taking...");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
new Thread(consumer).start();

}

2、Condition 实现生产者消费者模式

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
private Queue queue;
private int size;
private ReentrantLock lock=new ReentrantLock();
private Condition notFull=lock.newCondition();
private Condition notEmpty=lock.newCondition();

public ProducerAndConsumerMode(int size){
this.size=size;
queue=new LinkedList();
}

public void put(Object o) throws InterruptedException{
lock.lock();
try{
while (queue.size()==size){
notFull.await();
}
queue.add(o);
notEmpty.signalAll();
}finally {
lock.unlock();
}
}

public void take(Object o) throws InterruptedException{
lock.lock();
try {
while (queue.size()==0){
notEmpty.await();
}
queue.remove(o);
notFull.signalAll();
}finally {
lock.unlock();
}
}

3、wait/notify方法实现生产者消费者

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
private int maxSize;

private LinkedList<Object> storage=new LinkedList<>();

public ProducerAndConsumerMode(int size, LinkedList<Object> storage) {

this.maxSize = size;

this.storage = storage;

}

public synchronized void put() throws InterruptedException {

while (storage.size() == maxSize) {

wait();

}

storage.add(new Object());

notifyAll();

}

public synchronized void take() throws InterruptedException {

while (storage.size() == 0) {

wait();

}

System.out.println(storage.remove());

notifyAll();

}


本站总访问量