题目
- 剑指offer
- leetcode
- 数组
- 1.两数之和
- 2.两数相加
- 4.寻找两个正序数组的中位数
- 15.三数之和
- 33.搜索旋转排序数组
- 34.在排序数组中查找元素的第一个和最后一个位置
- 40.组合总和II
- 41.缺失的第一个正数
- 42.接雨水
- 45.跳跃游戏II
- 56.合并区间
- 88.合并两个有序数组
- 179.最大数
- 189.旋转数组
- 215.数组中的第K个最大元素
- 239.滑动窗口的最大值(同剑指offer59-1题)
- 349.两个数组的交集 I
- 350.两个数组的交集 II
- 416.分割等和子集
- 442.数组中重复的元素
- 448.找到所有数组中消失的数字
- 556.下一个更大元素II
- 560.和为K的子数组
- 1299.将每个元素替换为右侧最大元素
- 1464.数组中的两元素的最大乘积
- 字符串
- 最长问题
- 动态规划
- 岛屿问题
- 链表
- 树
- 其他问题
剑指offer
数组
03.找出数组中重复的数字
解法:
class Solution {
public int findRepeatNumber(int[] nums) {
// 方法一:利用额外空间Set
// Set<Integer> set = new HashSet<>();
// for (int i = 0; i < nums.length; i++) {
// if (!set.contains(nums[i])) {
// set.add(nums[i]);
// } else {
// return nums[i];
// }
// }
// return -1;
// 方法二:原地交换法,数组长度为n,数字范围在0~n-1,那么在不重复的情况下,索引i上的数字nums[i]就应该等于索引i
int i = 0;
while (i < nums.length) {
// 数字在合理位置,跳过
if (nums[i] == i) {
i++;
continue;
}
// 索引i上的数字nums[i]不是i,则判断索引nums[i]上的位置是不是nums[i]
if (nums[i] == nums[nums[i]]) {
// 数字存在重复,直接返回
return nums[i];
}
int temp = nums[i];
nums[i] = nums[temp];
nums[temp] = temp;
}
return -1;
}
}
04.二维数组中的查找
解法:
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if (matrix == null || matrix.length <= 0 || matrix[0].length <= 0) {
return false;
}
int row = 0;
int col = matrix[0].length-1;
while (col >= 0 && row <= matrix.length-1) {
if (target > matrix[row][col]) {
row++;
} else if (target < matrix[row][col]) {
col--;
} else return true;
}
return false;
}
}
12.矩阵中的路径
解法:
class Solution {
public boolean exist(char[][] board, String word) {
char[] words = word.toCharArray();
// 遍历图
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
// 如果找到了,就返回true。否则继续找
if (dfs(board, words, i ,j ,0)) {
return true;
}
}
}
return false;
}
boolean dfs(char[][] board, char[] words, int i, int j, int k) {
// i,j初始都是0,都在图左上角。k是传入字符串当前索引,一开始是0,如果当前字符串索引和图当前索引对应的值不相等,表示第一个数就不相等
if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] != words[k]) return false;
// 表示找完了,每个字符都找到了
if (k == words.length - 1) return true;
// 如果找到了,但是还没找完,将访问过的标记空字符串
board[i][j] = '\0';
// 如果board[i][j] == word[k],则表明当前找到了对应的数,就继续执行(标记找过,继续dfs 下上右左)
boolean res = dfs(board, words, i+1, j, k+1)
|| dfs(board, words, i-1, j, k+1)
|| dfs(board, words, i, j+1, k+1)
|| dfs(board, words, i, j-1, k+1);
// 还原找过的元素,因为之后可能还会访问到(不同路径)
board[i][j] = words[k];
return res;
}
}
13.机器人的运动范围
解法:
class Solution {
int res = 0;
public int movingCount(int m, int n, int k) {
// 用于存放已到达格子情况的数组
boolean[][] visit = new boolean[m][n];
// 从坐标(0, 0)开始搜索路径
dfs(visit, m, n, k, 0, 0);
return res;
}
private void dfs(boolean[][] visit, int m ,int n, int k, int i, int j) {
// 边界条件
if (i < 0 || j < 0 || i >= m || j >= n || visit[i][j]
|| (i % 10 + i / 10 % 10) + (j % 10 + j / 10 % 10) > k) {
return ;
}
// 格子访问数+1
res ++;
// 数组相应位置设置为已访问
visit[i][j] = true;
// 继续向上、下、左、右遍历
dfs(visit, m, n, k, i-1, j);
dfs(visit, m, n, k, i+1, j);
dfs(visit, m, n, k, i, j-1);
dfs(visit, m, n, k, i, j+1);
}
}
21.调整数组顺序使奇数位于偶数前面
解法:
class Solution {
private boolean isOdd(int number) {
return number % 2 != 0;
}
private boolean isEven(int number) {
return number % 2 == 0;
}
public int[] exchange(int[] nums) {
int[] newArray = new int[nums.length];
int start = 0;
int end = nums.length-1;
for (int i = 0; i <= nums.length-1; i++) {
// 从头到尾遍历
if (isOdd(nums[i])) {
newArray[start++] = nums[i];
}
// 从尾到头遍历
if (isEven(nums[nums.length-1-i])) {
newArray[end--] = nums[nums.length-1-i];
}
}
return newArray;
}
}
29.顺时针打印矩阵
解法:
class Solution {
public int[] spiralOrder(int[][] matrix) {
// 边界条件
if (matrix.length == 0 || matrix[0].length == 0)
return new int[0];
ArrayList<Integer> res = new ArrayList<>();
// 左上角行数
int LR = 0;
// 左上角列数
int LC = 0;
// 右下角行数
int RR = matrix.length-1;
// 右下角列数
int RC = matrix[0].length-1;
// 循环打印
while (LR <= RR && LC <= RC) {
res.addAll(printEdge(matrix, LR++, LC++, RR--, RC--));
}
// ArrayList转int数组
return res.stream().mapToInt(Integer::intValue).toArray();
}
private ArrayList<Integer> printEdge(int[][] m, int LR, int LC, int RR, int RC) {
ArrayList<Integer> list = new ArrayList<>();
// 只剩最后一行
if (LR == RR) {
for (int i = LC; i <= RC; i++) {
list.add(m[LR][i]);
}
}
// 只剩最后一列
else if (LC == RC) {
for (int i = LR; i <= RR; i++) {
list.add(m[i][LC]);
}
}
// 至少两行两列
else {
// 当前行
int curR = LR;
// 当前列
int curC = LC;
// 打印上边框-从左向右打印
while (curC < RC) {
list.add(m[LR][curC++]);
}
// 打印右边框-从上往下打印
while (curR < RR) {
list.add(m[curR++][RC]);
}
// 打印下边框-从右向左打印
while (curC > LC) {
list.add(m[RR][curC--]);
}
// 打印左边框-从下往上打印
while (curR > LR) {
list.add(m[curR--][LC]);
}
}
return list;
}
}
38.字符串的排列
解法:
class Solution {
// 结果集
List<String> res = new ArrayList<>();
// 一次排列方案的暂存数组
char[] c;
public String[] permutation(String s) {
c = s.toCharArray();
// 从第一层开始递归
dfs(0);
// 将ArrayList数组转化为String数组
return res.toArray(new String[res.size()]);
}
// 递归
private void dfs(int x) {
if (x == c.length - 1) {
// 添加排列方案
res.add(String.valueOf(c));
return;
}
HashSet<Character> set = new HashSet<>();
for (int i = x; i < c.length; i++) {
// 存在重复元素,剪枝
if (set.contains(c[i])) continue;
set.add(c[i]);
// 交换,将 c[i] 固定在第 x 位
swap(i, x);
// 开启固定第 x + 1 位字符
dfs(x + 1);
// 恢复交换
swap(i, x);
}
}
// 交换元素
private void swap(int a, int b) {
char tmp = c[a];
c[a] = c[b];
c[b] = tmp;
}
}
39.数组中出现次数超过一半的数字
解法:
class Solution {
public int majorityElement(int[] nums) {
// // 方法1,借用map
// Map<Integer, Integer> map = new HashMap<>();
// for (int i = 0; i < nums.length; i++) {
// if (map.containsKey(nums[i])) {
// map.put(nums[i], map.get(nums[i])+1);
// } else {
// map.put(nums[i], 1);
// }
// int times = map.get(nums[i]);
// if (times > nums.length>>1) {
// return nums[i];
// }
// }
// return 0;
//方法2,相同元素比较法
if (nums == null || nums.length <= 0) {
return -1;
}
// 找出数组中出现次数最多的元素
int number = nums[0];
int count = 1;
for (int i = 1; i < nums.length; i++) {
if (nums[i] == number) {
count++;
} else {
count--;
}
if (count == 0) {
number = nums[i];
count = 1;
}
}
// 计算出现次数最多的元素出现的次数times
int times = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == number) {
times++;
}
}
// 校验出现次数是否超过数组长度的一半
if (times > nums.length>>1) {
return number;
}
return -1;
}
}
40.最小的k个数
解法:
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (k >= arr.length) return arr;
return quickSort(arr, k, 0, arr.length - 1);
}
private int[] quickSort(int[] arr, int k, int left, int right) {
// 数组第一个元素为数轴
int i = left, j = right;
while (i < j) {
// 从右往左找出小于枢轴的元素
while (i < j && arr[j] >= arr[left]) j--;
// 从左往右找出大于枢轴的元素
while (i < j && arr[i] <= arr[left]) i++;
// 交换位置
swap(arr, i, j);
}
// 把枢轴数值放到位置i上。枢轴左边的数都小于它,右边的数都大于它
swap(arr, i, left);
// 排好序的枢轴位置不是k,则继续排序
if (i > k) return quickSort(arr, k, left, i - 1);
if (i < k) return quickSort(arr, k, i + 1, right);
return Arrays.copyOf(arr, k);
}
private void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
42.连续子数组的最大和
解法:
class Solution {
// 解题思路:对于一个数A,如果A左边的累计数非负,则加上A的值不小于A,认为累计值对整体总和是有贡献的。若累计值为负,则认为不益于整体总和,就摒弃之前的累计数,从当前数字A开始累计。
private int getMax(int a, int b) {
return a > b ? a : b;
}
public int maxSubArray(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
// 当前累计值
int sum = nums[0];
// 最大累计值
int max = nums[0];
for (int i = 1; i < nums.length; i++) {
// 对总和有贡献的情况下累计
sum = getMax(sum + nums[i], nums[i]);
// 过程中如果遇到大于当前max的sum,更新max
max = getMax(max, sum);
}
return max;
}
}
45.把数组排成最小的数
解法:
class Solution {
// 思路:先将整型数组转换成String数组,然后将String数组排序,最后将排好序的字符串数组拼接出来。关键就是制定排序规则。
public String minNumber(int[] nums) {
if (nums == null) {
return null;
}
int len = nums.length;
String[] str = new String[len];
for (int i = 0; i < nums.length; i++) {
str[i] = String.valueOf(nums[i]);
}
Arrays.sort(str, new Comparator<String>() {
public int compare(String s1, String s2) {
String c1 = s1 + s2;
String c2 = s2 + s1;
// <0 升序排列 >0降序排列
return c1.compareTo(c2);
}
});
StringBuffer sb = new StringBuffer();
for (String s : str) {
sb.append(s);
}
return sb.toString();
}
}
51.数组中的逆序对
解法:
class Solution {
// 思路:用归并排序解法,在数组合并和过程中计算逆序数
// 定义全局变量count
int count = 0;
public int reversePairs(int[] nums) {
merge(nums, 0, nums.length-1);
return count;
}
private void merge(int[] nums, int left, int right) {
if (left < right) {
int mid = (left + right) >> 1;
// 分治
merge(nums, left, mid);
merge(nums, mid+1, right);
mergesort(nums, left ,mid, right);
}
}
private void mergesort(int[] nums, int left, int mid, int right) {
// 临时数组,用于存储排好序的元素
int[] temp = new int[right - left + 1];
int index = 0;
int l = left, r = mid+1;
while(l <= mid && r <= right) {
if (nums[l] <= nums[r]) {
temp[index++] = nums[l++];
} else {
// 累加逆序数
count += (mid - l + 1);
temp[index++] = nums[r++];
}
}
// 把左边剩余的数移入数组
while (l <= mid) {
temp[index++] = nums[l++];
}
// 把右边剩余的数移入数组
while (r <= right) {
temp[index++] = nums[r++];
}
// 把新数组中的数覆盖nums数组
for (int i = 0; i < temp.length; i++) {
nums[left+i] = temp[i];
}
}
}
53-1.在排序数组当中查找数字
解法:
class Solution {
// 二分查找法
public int getFirstIndex(int[] nums, int target, int start, int end) {
if (start > end) {
return -1;
}
int mid = (start + end) >> 1;
if (nums[mid] == target) {
// 当前位置为第一个target
if (mid == 0 || nums[mid-1] != target) {
return mid;
} else {
end = mid - 1;
}
} else if (nums[mid] > target) {
end = mid - 1;
} else {
start = mid + 1;
}
return getFirstIndex(nums, target, start, end);
}
public int getLastIndex(int[] nums, int target, int start, int end) {
if (start > end) {
return -1;
}
int mid = (start + end) >> 1;
if (nums[mid] == target) {
// 当前位置为最后一个target
if (mid == nums.length-1 || nums[mid+1] != target) {
return mid;
} else {
start = mid + 1;
}
} else if (nums[mid] > target) {
end = mid - 1;
} else {
start = mid + 1;
}
return getLastIndex(nums, target, start, end);
}
public int search(int[] nums, int target) {
if (nums == null || nums.length <= 0) {
return 0;
}
int first = getFirstIndex(nums, target, 0, nums.length-1);
int last = getLastIndex(nums, target, 0, nums.length-1);
if (first >= 0 && last >= 0) {
return last - first + 1;
}
return 0;
}
}
53-2.0~n-1中缺失的数字
解法:
class Solution {
public int missingNumber(int[] nums) {
if (nums == null || nums.length <= 0) {
return 0;
}
// 利用二分查找,由于在缺少值前面位置所有值满足nums[i]==i,所以当二分的mid值不等于nums[mid]时,可知缺少值一定在mid左边,此时对左边部分进行二分查找;否则缺少值在右边,对右边进行二分查找
int start = 0;
int end = nums.length-1;
while (start <= end) {
int mid = (start + end) >> 1;
if (mid == nums[mid]) {
start = mid + 1;
} else {
end = mid - 1;
}
}
return start;
}
}
56-1.数组中数字出现的次数
解法:
class Solution {
public int[] singleNumbers(int[] nums) {
// 第一个数
int x = 0;
// 第二个数
int y = 0;
// 两个只出现一次的数字的异或结果
int n = 0;
// 异或结果中从低位向高位寻找到的第一个1的位置坐标,用于分组
int m = 1;
// 计算出数组全员异或结果
for (int num : nums) {
n ^= num;
}
// 异或结果中,从低位向高位寻找第一个1的位置
while ((n & m) == 0) {
m <<= 1;
}
// 重新遍历数组,根据坐标m,把数组分为两部分(一个数组所有元素二进制数在m位置上的数为0,另一个数组所有元素二进制数在m位置上的数为1)
for (int num : nums) {
if ((num & m) == 0) {
// 组内全员异或的结果就是只出现一次的x
x ^= num;
} else {
// 组内全员异或的结果就是只出现一次的y
y ^= num;
}
}
return new int[] {x, y};
}
}
56-2.数组中数字出现的次数
解法:
class Solution {
public int singleNumber(int[] nums) {
// map的key存放数组元素,value存放当次存放之前是否存放过元素,只存放过一次的元素value为false
Map<Integer, Boolean> map = new HashMap<>();
for (int num : nums) {
map.put(num, map.containsKey(num));
}
for (int num : nums) {
if (!map.get(num)) {
return num;
}
}
return -1;
}
}
57.和为s的两个数字
解法:
class Solution {
public int[] twoSum(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return new int[0];
}
// 左右指针
int left = 0;
int right = nums.length - 1;
while (left < right) {
if (nums[left] + nums[right] == target) {
return new int[] {nums[left], nums[right]};
} else if (nums[left] + nums[right] < target) {
left++;
} else {
right--;
}
}
return new int[0];
}
}
57-2.和为s的连续正数序列
解法:
class Solution {
// 思路:考虑用两个数small和big分别表示序列的最小值和最大值。首先把small初始化为1,big初始化为2。如果从small到big的序列和大于target,我们可以从序列中去掉最小值,也就是增大small的值。如果从small到big的序列和小于s,我们可以增大big,让这个序列包含更多的数字。因为这个序列至少要有两个数字,我们一直增加small到小于(1+ target)/2为止。
public int[][] findContinuousSequence(int target) {
if (target < 3) {
return new int[0][0];
}
ArrayList<int[]> res = new ArrayList<>();
// 中位数值
int middle = (1 + target) >> 1;
int small = 1;
int big = 2;
int curSum = small + big;
// 大前提:最小数值small不能超过中位数值middle(比如target是10,small不能大于5;target是15,small不能大于8)
while (small < middle) {
// 当前加和等于目标值
if (curSum == target) {
res.add(toArray(small, big));
}
// 当前加和仍大于目标值,则不断删除小元素
while (curSum > target) {
curSum -= small;
small++;
if (curSum == target) {
res.add(toArray(small, big));
}
}
// 当前加和小于目标值,添加大元素
big++;
curSum += big;
}
return res.toArray(new int[res.size()][]);
}
public int[] toArray(int small, int big) {
ArrayList<Integer> list = new ArrayList<>();
for (int i = small; i <= big; i++) {
list.add(i);
}
return list.stream().mapToInt(Integer::intValue).toArray();
}
}
59-1.滑动窗口的最大值
解法:
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || k == 0) return nums;
// 遍历到的元素从大到小排列
Deque<Integer> deque = new LinkedList<>();
// 最终结果
int[] res = new int[nums.length - k + 1];
int index = 0;
// 未形成窗口区间
for (int i = 0; i < k; i++) {
while (!deque.isEmpty() && nums[i] > deque.peekLast()) {
deque.removeLast();
}
// 执行完上面的循环后,队列中要么为空,要么值都比当前值大,然后就把当前值添加到队列中
deque.add(nums[i]);
}
res[index++] = deque.peekFirst();
// 窗口已经形成,窗口开始滑动,更新最大值
for (int i = k; i < nums.length; i++) {
// i-k是已经在区间外了,如果首位等于nums[i-k],那么说明此时首位值已经不再区间内了,需要删除
if (deque.peekFirst() == nums[i - k]) {
deque.removeFirst();
}
while (!deque.isEmpty() && nums[i] > deque.peekLast()) {
deque.removeLast();
}
deque.add(nums[i]);
res[index++] = deque.peekFirst();
}
return res;
}
}
61.扑克牌中的顺子
解法:
class Solution {
// 思路:需要做三件事:首先把数组排序,再统计数组中0的个数,最后统计排序后的数组中相邻数字之间的空缺总数。如果空缺总数小于等于0的个数,那么这个数组就是连续的,反之不连续。
public boolean isStraight(int[] nums) {
// 0的个数
int count = 0;
// 空缺数
int diff = 0;
// 数组排序
Arrays.sort(nums);
for (int i = 0; i <= 3; i++) {
if (nums[i] == 0) {
count++;
continue;
}
if (nums[i] != nums[i+1]) {
diff += nums[i+1]-nums[i]-1;
}
// 数组中存在对子,不满足顺子条件
else {
return false;
}
}
return diff <= count;
}
}
堆栈
09.用两个栈实现队列
解法:
import java.util.Stack;
class CQueue {
Stack<Integer> s1,s2;
public CQueue() {
s1 = new Stack<Integer>();
s2 = new Stack<Integer>();
}
public void appendTail(int value) {
s1.push(value);
}
public int deleteHead() {
if (s1.size() + s2.size() <= 0) {
return -1;
}
if (s2.isEmpty()) {
while (!s1.isEmpty()) {
s2.push(s1.pop());
}
}
return s2.pop();
}
}
/**
* Your CQueue object will be instantiated and called as such:
* CQueue obj = new CQueue();
* obj.appendTail(value);
* int param_2 = obj.deleteHead();
*/
30.包含min函数的栈
解法:
class MinStack {
public Stack<Integer> data = new Stack<>();
public Stack<Integer> min = new Stack<>();
// 入栈
public void push(int x) {
data.push(x);
// 辅助栈min的栈顶始终放对应数据栈中数据的最小元素
if (min.isEmpty()) {
min.push(x);
} else {
if (min.peek() >= x) {
min.push(x);
} else {
min.push(min.peek());
}
}
}
// 出栈
public void pop() {
if (!data.isEmpty()) {
data.pop();
}
if (!min.isEmpty()) {
min.pop();
}
}
// 栈顶元素
public int top() {
if (!data.isEmpty()) {
return data.peek();
} else {
return 0;
}
}
// 最小元素
public int min() {
if (!min.isEmpty()) {
return min.peek();
} else {
return 0;
}
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.min();
*/
31.栈的压入、弹出序列
解法:
class Solution {
// 思路:借用一个辅助栈,遍历压栈顺序,先将第一个放入栈中,这里是1,然后判断栈顶元素是不是出栈顺序的第一个元素,这里是4,很显然1≠4,所以我们继续压栈,直到相等以后开始出栈。出栈一个元素,则将出栈顺序向后移动一位,这样循环等压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。
// 举例:
// 入栈1,2,3,4,5
// 出栈4,5,3,2,1
// 首先1入辅助栈,此时栈顶1≠4,继续入栈2
// 此时栈顶2≠4,继续入栈3
// 此时栈顶3≠4,继续入栈4
// 此时栈顶4=4,出栈4,弹出序列向后移一位,此时为5,辅助栈里面是1,2,3
// 此时栈顶3≠5,继续入栈5
// 此时栈顶5=5,出栈5,弹出序列向后一位,此时为3,,辅助栈里面是1,2,3
public boolean validateStackSequences(int[] pushed, int[] popped) {
// 辅助栈
Stack<Integer> stack = new Stack<>();
// 遍历出栈数组的起始位置
int index = 0;
for (int i : pushed) {
// 将入栈数组的元素依次压入辅助栈
stack.push(i);
// 当辅助栈顶元素等于出栈数组当前遍历元素,栈顶元素出栈,index右移一位
while (!stack.isEmpty() && stack.peek() == popped[index]) {
stack.pop();
index ++;
}
}
return stack.isEmpty();
}
}
数学问题
10-1.斐波那契数列
解法:
class Solution {
public int fib(int n) {
if (n < 2) {
return n;
}
int low = 0;
int high = 1;
int sum = 0;
for (int i = 2; i <= n; i++) {
sum = (low + high) % 1000000007;
low = high;
high = sum;
}
return sum;
}
}
10-2.青蛙跳台阶问题
解法:
class Solution {
public int numWays(int n) {
// 边界条件
// if (n < 1) {
// return 1;
// }
// // 递归方法
// if (n == 1 || n == 2) {
// return n;
// } else {
// return (numWays(n-1) + numWays(n-2))% 1000000007;
// }
// 循环方法
if (n <= 1) {
return 1;
}
if (n == 2) {
return 2;
}
int n1 = 1;
int n2 = 2;
int n3 = 0;
for (int i = 3; i <= n; i++) {
n3 = (n1 + n2) % 1000000007;
n1 = n2 % 1000000007;
n2 = n3 % 1000000007;
}
return n3;
}
}
14-1.剪绳子
解法:
class Solution {
// 算法流程:
// 当n<=3时,按规则应不切分,但由于题目要求必须剪成m>1段,因此必须剪出一段长为1的绳子,即返回n-1
// 当n>3时,求n除以3的整数部分a个余数部分b,分为以下三种情况:
// 当b=0时,直接返回3^a
// 当b=1时,要将一个1+3转化为2+2,因此返回3^(a-1)*4
// 当b=2时,返回(3^a)*2
public int cuttingRope(int n) {
if (n <= 3) return n-1;
int a = n / 3;
int b = n % 3;
if (b == 0) return (int)Math.pow(3, a);
if (b == 1) return (int)Math.pow(3, a-1) * 4;
return (int)Math.pow(3, a) * 2;
}
}
14-2.剪绳子
解法:
class Solution {
// 算法流程:
// 当n<=3时,按规则应不切分,但由于题目要求必须剪成m>1段,因此必须剪出一段长为1的绳子,即返回n-1
// 当n>3时,求n除以3的整数部分a个余数部分b,分为以下三种情况:
// 当b=0时,直接返回3^a % 1000000007
// 当b=1时,要将一个1+3转化为2+2,因此返回3^(a-1)*4 % 1000000007
// 当b=2时,返回(3^a)*2 % 1000000007
public int cuttingRope(int n) {
if (n <= 3) return n-1;
// 线段被我们分成以3为大小的小线段个数
int lineNums = n / 3;
int b = n % 3;
long res = 1;
// 从第一段线段开始验算,3的res次方是否越界。注意是验算lineNums-1次
for (int i = 1; i < lineNums; i++) {
res = res * 3 % 1000000007;
}
// 刚好被3整数的,要算上前一段
if (b == 0) return (int)(res * 3 % 1000000007);
// 被3整数余1的,要算上前一段
if (b == 1) return (int)(res * 4 % 1000000007);
// 被3整数余2的,要算上前一段
return (int)(res * 6 % 1000000007);
}
}
15.二进制中1的个数
解法:
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count = 0;
while(n != 0) {
++count;
n = n & (n-1);
}
return count;
}
}
17.打印从1到最大的n位数
解法:
class Solution {
public int[] printNumbers(int n) {
if (n <= 0) {
return new int[0];
}
char[] number = new char[n];
for (int i = 0; i < number.length; i++) {
number[i] = '0';
}
int[] res = new int[(int)(Math.pow(10, n) - 1)];
int start = 0;
while (!incr(number)) {
res[start++] = printNumber(number);
}
return res;
}
// 自增并判断是否高位溢位(高位溢位则代表打印到了该n位数的最大数值)
boolean incr(char[] number) {
// 溢位符
boolean overflow = false;
// 进位符
int carry = 1;
for (int i = number.length-1; i >= 0; i--) {
// 末尾数自增
int curNum = number[i] - '0' + carry;
// 有进位
if (curNum >= 10) {
// 最高位有进位
if (i == 0) {
overflow = true;
break;
}
curNum = curNum - 10;
carry = 1;
number[i] = (char)(curNum + '0');
} else {
number[i] = (char)(curNum + '0');
break;
}
}
return overflow;
}
Integer printNumber(char[] number) {
StringBuilder sb = new StringBuilder();
// 从左向右找到第一个不为0的数字开始打印
int index = 0;
while(number[index] == 0) {
index++;
}
for (int i = index; i < number.length; i++) {
sb.append(number[i]);
}
return Integer.parseInt(sb.toString());
}
}
43.1~n整数中1出现的次数
解法:
class Solution {
// 规律&例子:
// 把给定整数"2354"拆成高位high(23)、当前位cur(5)、低位low(4)三部分。设定digit为cur对应位置的10的幂次方数值(10^1 = 10)。依次计算每一位上为1的情况,再累加起来。当cur上的数字=0时,此位为1的情况有(high*digit = 23*10 = 230)种;当cur上的数字=1时,此位为1的情况有(high*digit+low+1 = 23*10+4+1 = 235)种;当cur上的数字>1时,此位为1的情况有(high+1)*digit=(23+1)*10 = 240种。cur从最低位置(4的位置)一直累加到最高位置(2的位置)后的结果就是最终答案!
public int countDigitOne(int n) {
int res = 0;
// 最低位开始计算digit=10^0=1
int digit = 1;
// 初始化高位部分
int high = n / 10;
// 初始化当前位置
int cur = n % 10;
// 初始化低位部分
int low = 0;
while (high != 0 || cur != 0) {
if (cur == 0) {
res += high * digit;
} else if (cur == 1) {
res += high * digit + low + 1;
} else{
res += (high + 1) * digit;
}
low += cur * digit;
cur = high % 10;
high /= 10;
digit *= 10;
}
return res;
}
}
49.丑数
解法:
class Solution {
public int nthUglyNumber(int n) {
// 使用dp数组来存放丑数序列
int[] dp = new int[n];
dp[0] = 1;
// 下个通过乘2获得新丑数的数据是第a个,下个通过乘3获得新丑数的数据是第b个,同理c
// 三者先初始化为数组第一个位置
int a = 0, b = 0, c = 0;
for (int i = 1; i < n; i++) {
// n2是第a个丑数需要通过乘2来获取的丑数,n3是第b个丑数需要通过乘3来获取的丑数,c同理
int n2 = dp[a] * 2, n3 = dp[b] * 3, n5 = dp[c] * 5;
dp[i] = Math.min(Math.min(n2, n3), n5);
// 第a个数已经通过乘2得到了一个新的丑数,那下个需要通过乘2得到一个新的丑数的数应该是第(a+1)个数
if (dp[i] == n2) {
a++;
}
// 第 b个数已经通过乘3得到了一个新的丑数,那下个需要通过乘3得到一个新的丑数的数应该是第(b+1)个数
if (dp[i] == n3) {
b++;
}
// 第 c个数已经通过乘5得到了一个新的丑数,那下个需要通过乘5得到一个新的丑数的数应该是第(c+1)个数
if (dp[i] == n5) {
c++;
}
}
return dp[n-1];
}
}
62.圆圈中最后剩下的数字
解法:
class Solution {
// 方法二:经过复杂的分析,终于找到了一个递归公式。要得到n个数字的序列中最后剩下的数字,只需要得到n-1个数字的序列中最后剩下的数字,并以此类推。当n=1时,也就是序列中开始只有一个数字0,那么很显然最后剩下的数字也是0。当n=1时,f(n,m)恒为0;当n > 1时,f(n,m) = [f(n-1,m) + m] % n
public int lastRemaining(int n, int m) {
if (n < 1 || m < 1) {
return -1;
}
int last = 0;
// i表示当次迭代的圆圈长度
for (int i = 2; i <= n; i++) {
last = (last + m) % i;
}
return last;
}
}
64.求1+2+···+n的和
解法:
class Solution {
// 公式法: n(n+1)/2
public int sumNums(int n) {
int res = (int) Math.pow(n, 2) + n;
return res >> 1;
}
}
65.不用加减乘除做加法
解法:
class Solution {
public int add(int a, int b) {
// 边界条件
if (a == 0)
return b;
if (b == 0)
return a;
// 加和
int sum = 0;
// 进位
int carry = 0;
while (b != 0) {
sum = a ^ b;
carry = (a & b) << 1;
a = sum;
b = carry;
}
return sum;
}
}
字符串
05.替换空格
解法:
class Solution {
public String replaceSpace(String s) {
if (s == null || s.length() <=0) {
return "";
}
// 空格数
int spaceNum = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == ' ') {
spaceNum += 1;
}
}
StringBuffer sb = new StringBuffer(s);
int head = sb.length()-1;
int tail = sb.length()-1 + spaceNum*2;
sb.setLength(tail+1);
while(head >= 0) {
// 遇到空格,替换数据
if (sb.charAt(head) == ' ') {
sb.setCharAt(tail--, '0');
sb.setCharAt(tail--, '2');
sb.setCharAt(tail--, '%');
} else {
sb.setCharAt(tail-- , sb.charAt(head));
}
head--;
}
return sb.toString();
}
}
20.表示数值的字符串
解法:
class Solution {
// 思路:
// 0.(数字)出现[0-9]的数字为合法字符
// 1.(小数点)小数点之前可以没有整数,但是不能重复出现小数点、或出现‘e’、'E'
// 2.(e或者E)‘e’或'E'前面必须有整数,且前面不能重复出现‘e’或'E';‘e’或'E'之后也必须接上整数,防止出现 123e或者123e+的非法情况
// 3.(+-)正负号只可能出现在第一个位置,或者出现在‘e’或'E'的后面一个位置
public boolean isNumber(String s) {
if (s == null || s.length() == 0) return false;
// 标记是否遇到数位、小数点、‘e’或'E'
boolean isNum = false, isDot = false, iseOrE = false;
// 删除首尾空格,转换为字符数组,方便后续遍历
char[] arr = s.trim().toCharArray();
for (int i = 0; i < arr.length; i++) {
if (arr[i] >= '0' && arr[i] <= '9') {
isNum = true;
} else if (arr[i] == '.') {
if (isDot || iseOrE) {
return false;
}
isDot = true;
} else if (arr[i] == 'e' || arr[i] == 'E') {
if (!isNum || iseOrE) {
return false;
}
iseOrE = true;
isNum = false;
} else if (arr[i] == '+' || arr[i] == '-') {
if (i != 0 && arr[i-1] != 'e' && arr[i-1] != 'E') {
return false;
}
} else
return false;
}
return isNum;
}
}
46.把数字翻译成字符串
解法:
class Solution {
public int translateNum(int num) {
// 只有两位数及以上才需要考虑组合情况
if (num < 10) return 1;
int tmp = num % 100;
if (tmp >= 10 && tmp <= 25) {
return translateNum(num / 10) + translateNum(num / 100);
} else {
return translateNum(num / 10);
}
}
}
48.最长不含重复字符的子字符串
解法:
class Solution {
// 方法:双指针+哈希表
// 1、哈希表dic统计:指针j遍历字符串,哈希表统计字符s[j]最后一次出现的索引位置
// 2、出现重复字符则更新左指针:根据上轮左指针i和dic[s[j]],每轮更新左边界i,保证区间[i+1,j]内无重复字符且最大。i = max(dic[s[j]], i)
// 3、更新结果res:取上轮res和本轮双指针区间[i+1, j](即j-1)的宽度做比较,取最大值。res = max(res, j-i)
public int lengthOfLongestSubstring(String s) {
HashMap<Character, Integer> dic = new HashMap<>();
// 初始化左边界
int i = -1;
int res = 0;
for (int j = 0; j < s.length(); j++) {
// 出现重复字符,将左边界替换为前一个重复字符所在位置
if (dic.containsKey(s.charAt(j))) {
i = Math.max(i, dic.get(s.charAt(j)));
}
// 记录当前索引位置
dic.put(s.charAt(j), j);
// 每轮更新结果
res = Math.max(res, j-i);
}
return res;
}
}
50.第一个只出现一次的字符
解法:
class Solution {
public char firstUniqChar(String s) {
if (s == null || s.length() == 0) {
return ' ';
}
HashMap<Character, Integer> map = new HashMap<>();
char[] charArray = s.toCharArray();
for (int i = 0; i < charArray.length; i++) {
if (!map.containsKey(charArray[i])) {
map.put(charArray[i], 1);
} else {
map.put(charArray[i], map.get(charArray[i])+1);
}
}
for (int i = 0; i < charArray.length; i++) {
if (map.get(charArray[i]) == 1) {
return charArray[i];
}
}
return ' ';
}
}
58-1.翻转单词顺序
解法:
class Solution {
// 思路:双指针倒序遍历字符串s,记录单词左右索引边界head,tail,每确定1个单词的边界,则将其添加到单词列表res中
public String reverseWords(String s) {
if (s == null || s.length() == 0 || s.trim().equals("")) {
return "";
}
// 删除首尾空格
s = s.trim();
// 单词尾指针
int tail = s.length() - 1;
// 单词头指针
int head = tail;
StringBuilder res = new StringBuilder();
while (head >= 0) {
// 从右向左找到单词左边界
while (head >= 0 && s.charAt(head) != ' ') head--;
// 截取子串,拼接空格
res.append(s.substring(head+1, tail+1) + " ");
// 跳过空格
while (head >= 0 && s.charAt(head) == ' ') head--;
// 新单词的尾部起点
tail = head;
}
return res.toString().trim();
}
}
58-2.左旋转字符串
解法:
class Solution {
public String reverseLeftWords(String s, int n) {
if (s == null || s.length() == 0 || n <= 0) {
return "";
}
StringBuilder res = new StringBuilder();
res.append(s.substring(n, s.length()));
res.append(s.substring(0, n));
return res.toString();
}
}
67.把字符串转换成整数
解法:
class Solution {
public int strToInt(String str) {
// 去除前后空格,转换为字符数组
char[] array = str.trim().toCharArray();
if (array.length == 0) return 0;
// 正负符号
int sign = 1;
// 开始遍历的位置
int startIndex = 1;
// 如果首个字符为负号,那么从位置1开始遍历字符串,并且结果需要变成负数;如果首个字符不是负号也不是加号,那么从第一个元素开始遍历
if (array[0] == '-') {
sign = -1;
} else if (array[0] != '+') {
startIndex = 0;
}
// 32位int类型取值范围为(-2147483648 ~ 2147483647)
int number = Integer.MAX_VALUE / 10;
int res = 0;
for (int j = startIndex; j < array.length; j++) {
// 遇到非数字直接退出
if (array[j] > '9' || array[j] < '0') break;
// 这里这个条件的意思为,因为题目要求不能超过int范围,所以需要判断结果是否越界因为res每次都会 * 10 ,所以外面定义了一个int最大值除以10的数字,此时只需要保证本次循环的res * 10 + chars[j] 不超过int即可保证不越界。res > number 意思是,此时res已经大于number了,他 * 10 一定越界。res == number && chars[j] > '7' 的意思是,当res == number时,即:214748364。此时res * 10 变成 2147483640 此时没越界,但是还需要 + chars[j],而int最大值为 2147483647,所以当chars[j] > 7 时会越界
if (res > number || (res == number && array[j] > '7')) {
// 根据字符串首负号判断返回最大值还是最小值
return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
// 字符获取数字需要 - '0' 的位移
res = res * 10 + (array[j] - '0');
}
// 返回结果,需要判断正负
return sign * res;
}
}
链表
06.从尾到头打印链表
解法:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
Stack<Integer> stack = new Stack<>();
while (head != null) {
stack.push(head.val);
head = head.next;
}
int[] result = new int[stack.size()];
if (result.length > 0) {
for (int i = 0; i < result.length; i++) {
result[i] = stack.pop();
}
}
return result;
}
}
18.删除链表的节点
解法:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if (head == null) {
return null;
}
ListNode node = head;
while (node.val != val) {
node = node.next;
}
// 待删除节点不是尾节点
if (node.next != null) {
node.val = node.next.val;
node.next= node.next.next;
}
// 待删除节点是唯一节点(既是头节点又是尾节点)
else if (node == head){
head = null;
node = null;
}
// 待删除节点是尾节点
else {
ListNode p = head;
while (p.next != node) {
p = p.next;
}
p.next = null;
}
return head;
}
}
21.删除链表的倒数第n个节点
解法:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
// 虚拟头节点
ListNode dummy = new ListNode(0, head);
ListNode fast = head;
ListNode slow = dummy;
for (int i = 0; i < n-1; i++) {
fast = fast.next;
}
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
// 要删除的节点时slow的下一个节点
slow.next = slow.next.next;
return dummy.next;
}
}
22.链表中倒数第k个节点
解法:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode slow = head;
ListNode fast = head;
for (int i = 0; i < k-1; i++) {
fast = fast.next;
}
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
24.反转链表
解法:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
*
*/
class Solution {
public ListNode reverseList(ListNode head) {
// 要反转的节点
ListNode cur = head;
// 虚拟前节点
ListNode pre = null;
// 当前节点不为空,则遍历列表
while (cur != null) {
// 缓存要反转的节点的下一个节点
ListNode next = cur.next;
// 反转
cur.next = pre;
// 前一个节点后移
pre = cur;
// 当前节点后移
cur = next;
}
return pre;
}
}
25.合并两个排序的链表
解法:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
//递归方式
if (l1 == null) return l2;
if (l2 == null) return l1;
if (l1.val <= l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
// 非递归方式
// ListNode newHead = new ListNode(0);
// ListNode cur = newHead;
// while (l1 != null && l2 != null) {
// if (l1.val <= l2.val) {
// ListNode node = new ListNode(l1.val);
// cur.next = node;
// cur = cur.next;
// l1 = l1.next;
// } else {
// ListNode node = new ListNode(l2.val);
// cur.next = node;
// cur = cur.next;
// l2 = l2.next;
// }
// }
// while (l1 != null) {
// cur.next = new ListNode(l1.val);
// cur = cur.next;
// l1 = l1.next;
// }
// while(l2 != null) {
// cur.next = new ListNode(l2.val);
// cur = cur.next;
// l2 = l2.next;
// }
// return newHead.next;
}
}
35.复杂链表的复制
解法:
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head) {
if (head == null) return null;
Node cur = head;
// 1.复制各节点,加在相应原节点头面,并构建拼接链表
while (cur != null) {
Node copy = new Node(cur.val);
copy.next = cur.next;
cur.next = copy;
cur = copy.next;
}
// 2.构建新节点的random指针指向
cur = head;
while (cur != null) {
if (cur.random != null) {
cur.next.random = cur.random.next;
}
cur = cur.next.next;
}
// 3.拆分链表
// 拷贝链表头节点
cur = head.next;
// 原链表头节点
Node pre = head;
Node res = head.next;
while (cur.next != null) {
pre.next = pre.next.next;
cur.next = cur.next.next;
pre = pre.next;
cur = cur.next;
}
// 单独处理原链表尾节点
pre.next = null;
return res;
}
}
52.两个链表的第一个公共节点
解法:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
// 计算链表长度
public int getLength(ListNode head) {
int length = 0;
ListNode node = head;
while (node != null) {
length++;
node = node.next;
}
return length;
}
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
int lengthA = getLength(headA);
int lengthB = getLength(headB);
ListNode shortHead;
ListNode longHead;
// 链表长度差值
int lengthDiff = 0;
if (lengthA > lengthB) {
lengthDiff = lengthA - lengthB;
longHead = headA;
shortHead = headB;
} else {
lengthDiff = lengthB - lengthA;
longHead = headB;
shortHead = headA;
}
// 长链表跳跃长度差,到与短链表等长的位置
for (int i = 0; i < lengthDiff; i++) {
longHead = longHead.next;
}
while (longHead != null && shortHead != null && longHead != shortHead) {
longHead = longHead.next;
shortHead = shortHead.next;
}
return longHead;
}
}
二叉树
07.重建二叉树
解法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
// 存放中序遍历索引值的map
public Map<Integer, Integer> map = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
if (preorder == null || preorder.length < 1
|| inorder == null || inorder.length < 1) return null;
// 记录中序数组元素值和下标索引位置
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
return rebuildTree(preorder, inorder, 0, preorder.length-1, 0, inorder.length-1);
}
private TreeNode rebuildTree(int[] preorder, int[] inorder, int pStart, int pEnd, int iStart, int iEnd) {
// 前序数组首位元素为根节点
TreeNode tree = new TreeNode(preorder[pStart]);
// 初始化左右子树
tree.left = null;
tree.right = null;
if (pStart == pEnd && iStart == iEnd) {
return tree;
}
// 从中序数组索引map中,找到根节点位置
int root = map.get(preorder[pStart]);
// 划分左右子树长度
int leftLen = root - iStart;
int rightLen = iEnd - root;
// 递归构造左右子树
if (leftLen > 0) {
tree.left = rebuildTree(preorder, inorder, pStart+1, pStart+leftLen, iStart, root-1);
}
if (rightLen > 0) {
tree.right = rebuildTree(preorder, inorder, pStart+leftLen+1, pEnd, root+1, iEnd);
}
return tree;
}
}
26.树的子结构
解法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
// 思想:首先判断两个树的根节点是否为空,如果有一者为空,则返回false。否则在A树中遍历查找跟B树根节点值一样的节点R,找到的话,比较以R为根节点的子树是否包含B树。
public boolean isSubStructure(TreeNode A, TreeNode B) {
return (A != null && B != null) && (recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B));
}
boolean recur(TreeNode A, TreeNode B) {
if (B == null) return true;
if (A == null || A.val != B.val) return false;
return recur(A.left, B.left) && recur(A.right, B.right);
}
}
27.二叉树的镜像
解法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if (root == null || (root.left == null && root.right == null)) {
return root;
}
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
if (root.left != null) {
mirrorTree(root.left);
}
if (root.right != null) {
mirrorTree(root.right);
}
return root;
}
}
28.对称的二叉树
解法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
// 利用递归进行判断,若左子树的左孩子等于右子树的右孩子且左子树的右孩子等于右子树的左孩子,并且左右子树节点的值相等,则是对称的。
public boolean isSymmetric(TreeNode root) {
if (root == null) {
return true;
}
// 比较根节点的左右孩子
return isEquals(root.left, root.right);
}
private boolean isEquals(TreeNode left, TreeNode right) {
if (left == null && right == null) {
return true;
}
if (left != null && right != null) {
return left.val == right.val
&& isEquals(left.left, right.right)
&& isEquals(left.right, right.left);
}
return false;
}
}
32-1.从上到下打印二叉树
解法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int[] levelOrder(TreeNode root) {
if (root == null) {
return new int[0];
}
ArrayList<Integer> res = new ArrayList<>();
LinkedList<TreeNode> queue = new LinkedList<>();
// 根节点加入队列尾部
queue.offer(root);
while(!queue.isEmpty()) {
// 头部节点弹出队列
TreeNode node = queue.pop();
// 加入到数组中
res.add(node.val);
// 左孩子入队列
if (node.left != null) {
queue.offer(node.left);
}
// 右孩子入队列
if (node.right != null) {
queue.offer(node.right);
}
}
return res.stream().mapToInt(Integer::intValue).toArray();
}
}
32-2.从上到下打印二叉树
解法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) {
return res;
}
LinkedList<TreeNode> queue = new LinkedList<>();
// 层级列表
List<Integer> layerRes = new ArrayList<>();
// 根节点入队列
queue.offer(root);
// 层级首尾标记(用于标记该层节点是否打印完毕,因为根节点所在层级就一个节点,所以end为1)
int start = 0;
int end = 1;
while (!queue.isEmpty()) {
// 队列头部元素弹出
TreeNode node = queue.pop();
// 加入到层级打印列表中
layerRes.add(node.val);
// 标记位置+1
start++;
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
// 该层级节点全部加入层级列表layerRes完毕
if (start == end) {
res.add(layerRes);
// 重新标记下一层级的首尾位置
start = 0;
end = queue.size();
// 初始化层级列表
layerRes = new ArrayList<>();
}
}
return res;
}
}
32-3.从上到下打印二叉树
解法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
// 思路:利用两个辅助栈,一个栈存储奇数层的数据,一个栈存储偶数层的数据。利用栈先进后出的规律,刚好之字形。
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) {
return res;
}
// 存放奇数层级节点的栈
Stack<TreeNode> stackOdd = new Stack<>();
// 存放偶数层级节点的栈
Stack<TreeNode> stackEven = new Stack<>();
// 根节点入奇数栈
stackOdd.push(root);
// 初始化层级标识
int layer = 1;
while (!stackOdd.isEmpty() || !stackEven.isEmpty()) {
// 存储层级节点的列表
List<Integer> layerRes = new ArrayList<>();
// 当前遍历到奇数层
while (!stackOdd.isEmpty() && (layer % 2 == 1)) {
TreeNode node = stackOdd.pop();
if (node != null) {
layerRes.add(node.val);
stackEven.push(node.left);
stackEven.push(node.right);
}
}
// 当前遍历到偶数层
while (!stackEven.isEmpty() && (layer % 2 == 0)) {
TreeNode node = stackEven.pop();
if (node != null) {
layerRes.add(node.val);
stackOdd.push(node.right);
stackOdd.push(node.left);
}
}
if (layerRes.size() > 0) {
res.add(layerRes);
}
layer++;
}
return res;
}
}
33.二叉搜索树的后序遍历序列
解法:
class Solution {
// 思路:递归法
public boolean verifyPostorder(int[] postorder) {
return recur(postorder, 0, postorder.length-1);
}
private boolean recur(int[] postorder, int left, int right) {
// 数组只有一个元素的情况,直接返回
if (left >= right) {
return true;
}
// 后序遍历数组的最后一个元素一定是根节点
int root = postorder[right];
// 找到left~right之间第一个比root大的点,即root右子树中最小的点(右子树后序遍历的起点)
int firstBig = left;
while (postorder[firstBig] < root) {
firstBig++;
}
// 记录右子树后序遍历起点位置,用于递归
int m = firstBig;
// 如果firstBig~right区间(root的右子树)出现了比root小的节点,则不可能是后序遍历
while (firstBig < right) {
if (postorder[firstBig] < root) {
return false;
}
firstBig++;
}
// 此时能保证left ~ m-1都比root小,m ~ right-1都比root大,但这两个子区间内部的情况需要继续递归判断
return recur(postorder, left, m-1) && recur(postorder, m, right-1);
}
}
34.二叉树中和为某一数值的路径
解法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
// 解题思路:路径从根节点开始,用类似于前序遍历的方式访问树节点。我们需要整个路径,就需要一个容器保存经过路径上的节点,以及一个变量记录当前已有节点元素的和。当前序遍历到某一个节点时,添加该节点到路径,累加节点值。如果该节点为叶子节点并节点值累计等于目标整数,则找到一条路径。如果不是叶子节点,则继续访问子节点。一个节点访问结束后,递归函数自动回到其父节点。
List<List<Integer>> pathList = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
if (root == null) {
return pathList;
}
path.add(root.val);
target -= root.val;
// 路径值等于0,且当前节点是叶子节点 则找到一条路径
if (target == 0 && root.left == null && root.right == null) {
pathList.add(new ArrayList<Integer> (path));
}
// 递归左子树
if (root.left != null) {
pathSum(root.left, target);
}
// 递归右子树
if (root.right != null) {
pathSum(root.right, target);
}
// 当访问到叶子节点,且此时的target不为0,需要删除路径中最后一个节点,回退至父节点
path.remove(path.size() - 1);
return pathList;
}
}
37.序列化二叉树
解法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
if (root == null) return "[]";
StringBuilder res = new StringBuilder("[");
// 根节点入队列
Queue<TreeNode> queue = new LinkedList<>() {{ add(root); }};
// 队列中有元素
while (!queue.isEmpty()) {
// 获取队首元素,并从队列中删除
TreeNode node = queue.poll();
if (node != null) {
res.append(node.val + ",");
// 将该节点的左右孩子加入队列中
queue.add(node.left);
queue.add(node.right);
} else {
res.append("null,");
}
}
// 删除字符串中最后一个逗号
res.deleteCharAt(res.length()-1);
res.append("]");
return res.toString();
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
if (data == "[]") return null;
String[] arr = data.substring(1, data.length()-1).split(",");
TreeNode root = new TreeNode(Integer.parseInt(arr[0]));
Queue<TreeNode> queue = new LinkedList<>() {{ add(root); }};
// 数组遍历的起始位置
int index = 1;
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (!arr[index].equals("null")) {
node.left = new TreeNode(Integer.parseInt(arr[index]));
queue.add(node.left);
}
index++;
if (!arr[index].equals("null")) {
node.right = new TreeNode(Integer.parseInt(arr[index]));
queue.add(node.right);
}
index++;
}
return root;
}
}
54.二叉搜索树的第k大节点
解法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
ArrayList<Integer> list = new ArrayList<>();
public int kthLargest(TreeNode root, int k) {
if (root == null || k == 0) {
return -1;
}
inOrder(root);
return list.get(list.size() - k);
}
// 中序遍历
private void inOrder(TreeNode root) {
if (root == null) {
return;
}
if (root.left != null) {
inOrder(root.left);
}
list.add(root.val);
if (root.right != null) {
inOrder(root.right);
}
}
}
55-1.二叉树的深度
解法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
int leftDepth = maxDepth(root.left);
int rightDepth = maxDepth(root.right);
return Math.max(leftDepth, rightDepth) + 1;
}
}
55-2.平衡二叉树
解法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isBalanced(TreeNode root) {
if (root == null) {
return true;
}
return Math.abs(maxDepth(root.left) - maxDepth(root.right)) <= 1
&& isBalanced(root.left) && isBalanced(root.right);
}
// 计算节点的最大深度
private int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
68-1.二叉搜索树的最近公共祖先
解法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
// 思路:从树的根节点开始遍历,如果根节点的值大于其中一个节点,小于另外一个节点,则根节点就是最低公共祖先。否则如果根节点的值小于两个节点的值,则递归求根节点的右子树,如果大于两个节点的值则递归求根的左子树。如果根节点正好是其中的一个节点,那么说明这两个节点在一条路径上,所以最低公共祖先则是根节点
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || p == null || q == null) {
return null;
}
// 根节点的值大于其中一个节点,小于另外一个节点
if ((root.val - p.val) * (root.val - q.val) < 0) {
return root;
}
else if ((root.val - p.val) * (root.val- q.val) > 0) {
// 根节点的值大于两个节点的值
if (root.val > p.val && root.val > q.val) {
return lowestCommonAncestor(root.left, p, q);
}
// 根节点的值小于两个节点的值
else {
return lowestCommonAncestor(root.right, p, q);
}
}
// 根节点正好是其中的一个节点
else {
return root;
}
}
}
68-2.二叉树的最近公共祖先
解法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || p == null || q == null) {
return null;
}
// 如果p和q中有等于root的,那么它们的最近公共祖先即为root(一个节点也可以是它自己的祖先)
if (root == p || root == q) {
return root;
}
// 递归遍历左子树,只要在左子树中找到了p或q,则先找到谁就返回谁
TreeNode left = lowestCommonAncestor(root.left, p, q);
// 递归遍历右子树,只要在右子树中找到了p或q,则先找到谁就返回谁
TreeNode right = lowestCommonAncestor(root.right, p, q);
// 如果在左子树中p和q都找不到,则 p和q一定都在右子树中,右子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)
if (left == null) {
return right;
}
// 如果在右子树中p和q都找不到,则p和q一定都在左子树中,左子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)
if (right == null) {
return left;
}
// 当left和right均不为空时,说明 p、q节点分别在root异侧, 最近公共祖先即为root
return root;
}
}
leetcode
数组
1.两数之和
解法:
class Solution {
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])) {
return new int[] {map.get(target - nums[i]), i};
} else {
map.put(nums[i], i);
}
}
return null;
}
}
2.两数相加
解法:
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head = new ListNode(0);
head.next = null;
ListNode curNode = head;
// 加法进位
int temp = 0;
// 可看作是从左向右逐位加和进位
while (l1 != null || l2 != null) {
int cur = 0;
int x = (l1 == null) ? 0 : l1.val;
int y = (l2 == null) ? 0: l2.val;
int sum = x + y + temp;
if (sum < 10) {
cur = sum;
temp = 0;
} else {
cur = sum % 10;
temp = sum / 10;
}
curNode.next = new ListNode(cur);
curNode = curNode.next;
if (l1 != null) {
l1 = l1.next;
}
if (l2 != null) {
l2 = l2.next;
}
}
if (temp != 0) {
curNode.next = new ListNode(temp);
}
return head.next;
}
}
4.寻找两个正序数组的中位数
解法:
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int length1 = nums1.length;
int length2 = nums2.length;
int totalLen = length1 + length2;
// left为左侧中位数,right为右侧中位数
int left = -1, right = -1;
// start1为数组1开始遍历的下标,start2为数组2开始遍历的下标
int start1 = 0, start2 = 0;
// 对于两个排好序的数组,遍历到总长度的1/2时就可以找到整体中位数了
for (int i = 0; i <= totalLen / 2; i++) {
// 新一轮遍历的开始,将right赋值给left后,right继续向前遍历赋值
left = right;
// 左数组没遍历完 && (右数组遍历完 || 左数组当前元素小于右数组当前元素)的时候
if (start1 < length1 && (start2 >= length2 || nums1[start1] < nums2[start2])) {
right = nums1[start1++];
} else {
right = nums2[start2++];
}
}
// 总长度为奇数时,结果为left与right的平均值;总长度为偶数时,结果为right
if ((totalLen & 1) == 0) {
return (left + right) / 2.0;
} else
return right;
}
}
15.三数之和
解法:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);// 排序保证nums是递增数组
List<List<Integer>> res = new ArrayList<>();
// k < nums.length - 2是为了保证后面还能存在两个数字
for (int k = 0; k < nums.length - 2; k++) {
if (nums[k] > 0) break;// 若nums[k]大于0,则后面的数字也是大于零(排序后是递增的)
if (k > 0 && nums[k] == nums[k - 1]) continue;// 去重
int i = k + 1, j = nums.length - 1;// 定义左右指针
while (i < j) {
// 三数之和
int sum = nums[k] + nums[i] + nums[j];
if (sum < 0) {
while(i < j && nums[i] == nums[++i]);//左指针前进并去重
} else if (sum > 0) {
while (i < j && nums[j] == nums[--j]);//右指针后退并去重
} else {
res.add(new ArrayList<>(Arrays.asList(nums[k], nums[i], nums[j])));
while(i < j && nums[i] == nums[++i]);//左指针前进并去重
while(i < j && nums[j] == nums[--j]);//右指针后退并去重
}
}
}
return res;
}
}
33.搜索旋转排序数组
解法:
class Solution {
public int search(int[] nums, int target) {
int start = 0;
int end = nums.length - 1;
if (nums.length == 1) return nums[0] == target ? 0 : -1;
while (start <= end) {
int mid = (start + end) >> 1;
if (nums[mid] == target) {
return mid;
}
// 数组前半部分顺序递增
if (nums[0] <= nums[mid]) {
if (nums[0] <= target && target < nums[mid]) {
end = mid - 1;
} else {
start = mid + 1;
}
}
// 数组前半部分非严格顺序递增
else {
if (nums[mid] < target && target <= nums[nums.length - 1]) {
start = mid + 1;
} else {
end = mid - 1;
}
}
}
return -1;
}
}
34.在排序数组中查找元素的第一个和最后一个位置
解法:
class Solution {
public int[] searchRange(int[] nums, int target) {
if (nums == null || nums.length == 0) return new int[] {-1, -1};
int start = 0;
int end = nums.length - 1;
while (start <= end) {
int mid = (start + end) >> 1;
if (target < nums[mid]) {
end = mid - 1;
} else if (target > nums[mid]) {
start = mid + 1;
} else {
int left = mid;
int right = mid;
while (left > 0 && target == nums[left - 1]) {
left--;
}
while (right < nums.length - 1 && target == nums[right + 1]) {
right++;
}
return new int[] {left, right};
}
}
return new int[] {-1, -1};
}
}
40.组合总和II
解法:
class Solution {
private List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
// 数组排序
Arrays.sort(candidates);
List<Integer> path = new ArrayList<>();
backtrack(path, candidates, target, 0, 0);
return res;
}
// 回溯法
private void backtrack(List<Integer> path, int[] candidates, int target, int sum, int index) {
// 若当前路径path上的元素和sum等于target,则将path加入结果集res中
if(sum == target) {
res.add(new ArrayList<>(path));
return;
}
for(int i = index; i < candidates.length; i++) {
// 结果集去重
if(i > index && candidates[i] == candidates[i-1]) continue;
int rs = candidates[i] + sum;
if(rs <= target) {
path.add(candidates[i]);
backtrack(path, candidates, target, rs, i+1);
path.remove(path.size()-1);// 剪枝
} else {
break;
}
}
}
}
41.缺失的第一个正数
解法:
class Solution {
public int firstMissingPositive(int[] nums) {
int len = nums.length;
// 遍历数组,将数组中的元素放到正确位置,例如:[3,4,-1,1]
for (int i = 0; i < len; i++) {
// 满足在指定范围内、并且没有放在正确的位置上,才交换
while (nums[i] >= 1 && nums[i] <= len && nums[nums[i] - 1] != nums[i]) {
swap(nums, nums[i] - 1, i);// 例如:数值 3 应该放在索引 2 的位置上
}
}
// 元素放在正确位置后的数组:[1, -1, 3, 4]
for (int i = 0; i < len; i++) {
if (nums[i] != i + 1) {
return i + 1;
}
}
// 都正确则返回数组长度 + 1
return len + 1;
}
// 交换
private void swap(int[] nums, int a, int b) {
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
}
42.接雨水
解法:
class Solution {
public int trap(int[] height) {
// 双指针
int i = 0, j = height.length - 1;
// 左右柱子最大高度
int leftMax = 0, rightMax = 0;
// 接到的雨水量
int rain = 0;
while(i < j) {
// 更新左右柱子的最大高度
leftMax = Math.max(leftMax, height[i]);
rightMax = Math.max(rightMax, height[j]);
if (leftMax < rightMax) {
rain += leftMax - height[i++];
} else {
rain += rightMax - height[j--];
}
}
return rain;
}
}
45.跳跃游戏II
解法:
class Solution {
// 思路:遍历数组,每次都在本次能跳到的范围边界(end)内,选择一个能跳到最远的位置作为下一次的范围边界
public int jump(int[] nums) {
int end = 0; // 本次能跳到的范围边界
int maxPosition = 0; // 最远位置
int steps = 0; // 步数
for (int i = 0; i < nums.length - 1; i++) {
// 更新最远位置
maxPosition = Math.max(maxPosition, i + nums[i]);
// 遍历到本次范围边界后,需要更新下次的范围边界,且步数+1
if (i == end) {
end = maxPosition;// 更新下次跳跃的范围边界
steps++;
}
}
return steps;
}
}
56.合并区间
解法:
class Solution {
// 对二维数组分别以行、列从小到大排序。
// 行 Arrays.sort(nums,(a,b)->a[0]-b[0]);
// 列 Arrays.sort(nums,(a,b)->a[1]-b[1]);
public int[][] merge(int[][] intervals) {
List<int[]> res = new ArrayList<>();
// 将二维数组按行从小到大排列,即每一个子数组左边界比较大小
Arrays.sort(intervals, (x, y) -> x[0] - y[0]);
// 定义初始左右边界
int left = intervals[0][0], right = intervals[0][1];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] > right) {
// 区间加入结果集
res.add(new int[] {left, right});
// 重新设置左右边界值
left = intervals[i][0];
right = intervals[i][1];
} else {
// 重新定义右边界
right = Math.max(right, intervals[i][1]);
}
}
res.add(new int[] {left, right});
return res.toArray(new int[0][]);
}
}
88.合并两个有序数组
解法:
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int index = m + n -1 ;
int index1 = m - 1;
int index2 = n - 1;
while (index1 >= 0 && index2 >= 0) {
// 两个数组元素比较大小,较大者放在num1数组尾部
nums1[index--] = nums1[index1] >= nums2[index2] ? nums1[index1--] : nums2[index2--];
}
//如果num1的数全部遍历完毕了,但len2还没有遍历,所以只要将len2剩余的元素全部复制到len1中,此时len2的长度根据len2中剩下的元素的长度来决定
//但是如果num2全部遍历完了,那么此时的len2就是为-1了,所以当你在将num2复制到num1时,复制的长度为0,所以不复制任何元素过去
System.arraycopy(nums2, 0, nums1, 0, index2 + 1);
}
}
179.最大数
解法:
class Solution {
// 思路:先将整型数组转换成String数组,然后将String数组排序,最后将排好序的字符串数组拼接出来。关键就是制定排序规则。
public String largestNumber(int[] nums) {
if (nums == null) return null;
String[] str = new String[nums.length];
for (int i = 0; i < nums.length; i++) {
str[i] = String.valueOf(nums[i]);
}
Arrays.sort(str, new Comparator<String>() {
public int compare(String s1, String s2) {
String c1 = s1 + s2;
String c2 = s2 + s1;
// <0 升序排列 >0降序排列
return c2.compareTo(c1);
}
});
// 极端场景判断
if (Objects.equals(str[0], "0")) {
return "0";
}
StringBuffer sb = new StringBuffer();
for (String s : str) {
sb.append(s);
}
return sb.toString();
}
}
189.旋转数组
解法:
class Solution {
public void rotate(int[] nums, int k) {
k %= nums.length;
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
}
private void reverse(int[] nums, int start, int end) {
while (start < end) {
// 交换元素
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
// 左指针进1
start += 1;
// 右边针退1
end -= 1;
}
}
}
215.数组中的第K个最大元素
解法:
class Solution {
public int findKthLargest(int[] nums, int k) {
quickSort(nums, 0, nums.length - 1);//随机快排
return nums[nums.length - k];
}
public void quickSort(int[] arr, int L, int R) {
if (L < R) {
swap(arr, L+(int)(Math.random()*(R-L+1)), R);//随机选择数组中的一个数和数组最后一个数进行交换
int[] p = patition(arr, L, R);
quickSort(arr, L, p[0]-1);
quickSort(arr, p[1]+1, R);
}
}
public int[] patition(int[] arr, int L, int R) {
int less = L-1;
int more = R;
int cur = L;
while (cur < more) {
if (arr[cur] < arr[R]) {
swap(arr, ++less, cur++);
} else if (arr[cur] > arr[R]) {
swap(arr, --more, cur);
} else {
cur++;
}
}
swap(arr, more, R);//当遍历结束之后(指针相遇),将枢轴x与大于x的第一位上的数(数组下标为more的数)进行交换
return new int[] {less+1, more};//less+1和more为数组中等于x的数在排序后的下标的左右临界位置
}
public void swap(int[] arr,int i,int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
239.滑动窗口的最大值(同剑指offer59-1题)
解法:
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || k == 0) return nums;
// 遍历到的元素从大到小排列
Deque<Integer> deque = new LinkedList<>();
// 最终结果
int[] res = new int[nums.length - k + 1];
int index = 0;
// 未形成窗口区间
for (int i = 0; i < k; i++) {
while (!deque.isEmpty() && nums[i] > deque.peekLast()) {
deque.removeLast();
}
// 执行完上面的循环后,队列中要么为空,要么值都比当前值大,然后就把当前值添加到队列中
deque.add(nums[i]);
}
// deque队头元素为当前窗口最大值
res[index++] = deque.peekFirst();
// 窗口已经形成,窗口开始滑动,更新最大值
for (int i = k; i < nums.length; i++) {
// i-k是已经在区间外了,如果首位等于nums[i-k],那么说明此时首位值已经不再区间内了,需要删除
if (deque.peekFirst() == nums[i - k]) {
deque.removeFirst();
}
while (!deque.isEmpty() && nums[i] > deque.peekLast()) {
deque.removeLast();
}
deque.add(nums[i]);
res[index++] = deque.peekFirst();
}
return res;
}
}
349.两个数组的交集 I
解法:
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
// 存放nums1中的元素(自动去重)
Set<Integer> set1 = new HashSet<>();
// 存放nums1和nums2的交集元素
Set<Integer> set2 = new HashSet<>();
for (int i : nums1) {
set1.add(i);
}
for (int i : nums2) {
if (set1.contains(i)) {
set2.add(i);
}
}
int[] res = new int[set2.size()];
int index = 0;
for (int i : set2) {
res[index++] = i;
}
return res;
}
}
350.两个数组的交集 II
解法:
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
// 存放nums中的元素和出现次数
Map<Integer, Integer> map1 = new HashMap<>();
for (int i : nums1) {
if (map1.containsKey(i)) {
map1.put(i, map1.get(i) + 1);
} else {
map1.put(i, 1);
}
}
int index = 0;
int[] res = new int[Math.min(nums2.length, nums2.length)];
for (int i : nums2) {
if (map1.containsKey(i)) {
res[index++] = i;
map1.put(i, map1.get(i) - 1);
if (map1.get(i) == 0) {
map1.remove(i);
}
}
}
return Arrays.copyOfRange(res, 0, index);
}
}
416.分割等和子集
解法:
class Solution {
// 思路:01背包问题,从数组中选取元素,能否在一半数组和容量的背包中恰好放入等于一半数组和的元素
public boolean canPartition(int[] nums) {
int len = nums.length;// 数组长度
int sum = 0;// 数组元素之和
for (int i : nums) {
sum += i;
}
// 如果数组的元素之和为奇数,则肯定不能分割出等和的子集
if ((sum & 1) == 1) {
return false;
}
// target为数组元素和的一半
int target = sum >> 1;
boolean dp[] = new boolean[target + 1];
if (nums[0] <= target) {
dp[nums[0]] = true;
}
for (int i = 1; i < len; i++) {
for (int j = target; j >= nums[i]; j--) {
// nums[i]表示当前元素,j表示背包容量
if (nums[i] <= j) {
// 要么考虑当前元素,要么不考虑
dp[j] = dp[j] || dp[j - nums[i]];
}
}
}
return dp[target];
}
}
442.数组中重复的元素
解法:
class Solution {
// 原地交换法,数组长度为n,数字范围在1~n,那么在不重复的情况下,索引i上的数字nums[i]就应该等于i+1
public List<Integer> findDuplicates(int[] nums) {
List<Integer> res = new ArrayList<>();
if (nums.length <= 1) return res;
// 遍历数组,把元素放在正确位置上,比如元素4应该放在数组下标3的位置
for (int i = 0; i < nums.length; i++) {
while (nums[i] != nums[nums[i] - 1]) {
// 位置交换
swap(nums, i, nums[i] - 1);
}
}
// 遍历数组,不在正确位置上的元素就是重复出现的元素
for (int i = 0; i < nums.length; i++) {
if (nums[i] != i + 1) {
res.add(nums[i]);
}
}
return res;
}
private void swap(int[] arr, int a, int b) {
int tmp = arr[a];
arr[a] = arr[b];
arr[b] = tmp;
}
}
448.找到所有数组中消失的数字
解法:
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
List<Integer> res = new ArrayList<>();
// 原地交换法
for (int i = 0; i < nums.length; i++) {
while (nums[i] != nums[nums[i] - 1]) {
swap(nums, i, nums[i] - 1);
}
}
for (int i = 0; i < nums.length; i++) {
if (nums[i] != i + 1) {
res.add(i + 1);
}
}
return res;
}
private void swap(int[] nums, int a, int b) {
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
}
556.下一个更大元素II
解法:
class Solution {
// 思路:首先观察一个用例 136542。转换成数组, 从最后往前找到第一个倒序非递增的值, 倒序递增产生的是最大值。即找到了"3"。接下来要将前面的位"1"不动, 当前位"3"调大, 又要求是最小的一个更大值, 所以要将当前位"3"调为它后面比它大的第一个值。得到146532,还要将之后的值"6532"从小到大顺序排好, 以得到这几个数字可组成的最小值, 好在找的时候是从后往前递增找的, 所以将后半部分翻转成"2356"即排好序.得到142356
public int nextGreaterElement(int n) {
char[] cs = String.valueOf(n).toCharArray();
int len = cs.length;
int pos = len - 1;// 存放最后一个倒序递增的数的下标位置,比如"6"的位置
// 从最后往前找到最后一个倒序递增的值
while (pos > 0 && cs[pos] <= cs[pos - 1]) {
pos--;
}
if (pos == 0) return -1; // 从最后往前找到第一个倒序非递增的值的下标位置是0的话,表示该数本身就是最大值,没有比该数字更大的数了
int pre = pos - 1;// 存放从最后往前找到第一个倒序非递增的值,比如"3"的位置
while (pos < len && cs[pos] > cs[pre]) pos++; // 从后续序列中找比nums[pre]大的最小数,比如是"4",因为后续序列左到右是降序的,所以在遇到最数之前指针一直右移
swap(cs, --pos, pre);// 将当前位调换成它后面比它大的最小数
reverse(cs, pre + 1, len - 1);// 后半部分是降序排列的了,只要做一下翻转就能得到后半部分组成的最小值,如"2356"
Long res = Long.valueOf(String.valueOf(cs));
return res > Integer.MAX_VALUE ? -1 : res.intValue();
}
private void reverse(char[] cs, int left, int right) {
while (left < right) {
swap(cs, left++, right--);
}
}
private void swap(char[] cs, int a, int b) {
char temp = cs[a];
cs[a] = cs[b];
cs[b] = temp;
}
}
560.和为K的子数组
解法:
class Solution {
// 思路:
// 1、遍历,计算从索引0开始到当前位置的所有子串和total,并将该和的出现次数存入哈希表map中
// 2、在遍历过程中,若子串和为k,数量res + 1
// 3、若当前子串和total-k在哈希表中出现过,数量res + 该差值出现的次数
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
int total = 0, res = 0;
for (int i = 0; i < nums.length; i++) {
total += nums[i];
if (total == k) {
res++;
}
if (map.containsKey(total - k)) {
res += map.get(total - k);
}
map.put(total, map.getOrDefault(total, 0) + 1);
}
return res;
}
}
1299.将每个元素替换为右侧最大元素
解法:
class Solution {
// 本题等价于对于数组 arr 中的每个元素 arr[i],将其替换成 arr[i + 1], arr[i + 2], ..., arr[n - 1] 中的最大值。因此我们可以逆序地遍历整个数组,同时维护从数组右端到当前位置所有元素的最大值 的数组res。
public int[] replaceElements(int[] arr) {
int[] res = new int[arr.length];
if (arr.length == 0) {
return res;
}
// 数组最后一位元素右侧没有其他元素,则替换为-1
res[arr.length - 1] = -1;
// 从倒数第二位元素开始往左边遍历
for (int i = arr.length - 2; i >= 0; i--) {
res[i] = Math.max(arr[i + 1], res[i + 1]);
}
return res;
}
}
1464.数组中的两元素的最大乘积
解法:
class Solution {
// 思路:遍历nums数组,一次进行最大值和次大值的赋值,再计算(max1 - 1) * (max2 - 1)
public int maxProduct(int[] nums) {
// 初始化max1为最大值, max2为次大值
int max1 = 0, max2 = 0;
for (int num : nums) {
if (num > max1) {
max2 = max1;
max1 = num;
}
else if (num > max2) {
max2 = num;
}
}
return (max1 - 1) * (max2 - 1);
}
}
字符串
22.括号生成
解法:
class Solution {
// 规律:剩余左括号总数要 <= 右括号,递归把所有符合要求的加上去就行了
// 剩余左右括号数相等,下一个只能用左括号
// 剩余左括号小于右括号,下一个可以用左括号也可以用右括号
List<String>res = new ArrayList<>();
public List<String> generateParenthesis(int n) {
if (n <= 0) return res;
dfs("", n, n);
return res;
}
// left和right分别表示左括号和右括号的剩余个数
private void dfs(String str, int left, int right) {
if (left == 0 && right == 0) {
res.add(str);
return;
}
// 剩余左右括号数相等,下一个只能用左括号
if (left == right) {
dfs(str+"(", left - 1, right);
}
// 剩余左括号小于右括号,下一个可以用左括号也可以用右括号
else if (left < right) {
if (left > 0) {
dfs(str+"(", left - 1, right);
}
dfs(str+")", left, right - 1);
}
}
}
67.二进制求和
解法:
class Solution {
public String addBinary(String a, String b) {
StringBuilder res = new StringBuilder();
int lenA = a.length(), lenB = b.length(), carry = 0;
for (int i = lenA - 1, j = lenB - 1; i >= 0 || j >= 0; i--, j--) {
// 上一轮计算得到的进位carry作为下一轮总和初始值sum
int sum = carry;
sum += i >= 0 ? a.charAt(i) - '0' : 0;
sum += j >= 0 ? b.charAt(j) - '0' : 0;
res.append(sum % 2);
carry = sum / 2;
}
// 单独处理最后一次进位
if (carry == 1) res.append("1");
return res.reverse().toString();
}
}
139.单词拆分
解法:
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> set = new HashSet<>(wordDict);
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int fast = 1; fast <= s.length(); fast++) {
for (int slow = 0; slow < fast; slow++) {
if (dp[slow] && set.contains(s.substring(slow ,fast))) {
dp[fast] = true;
break;
}
}
}
return dp[s.length()];
}
}
415.字符串相加
解法:
class Solution {
public String addStrings(String num1, String num2) {
StringBuilder res = new StringBuilder("");
int i = num1.length() - 1, j = num2.length() - 1, carry = 0;
while (i >= 0 || j >= 0) {
int n1 = i >= 0 ? num1.charAt(i) - '0' : 0;
int n2 = j >= 0 ? num2.charAt(j) - '0' : 0;
int temp = n1 + n2 + carry;
// 进位
carry = temp / 10;
// 当前位
res.append(temp % 10);
i--;
j--;
}
// 处理最后一个进位
if (carry == 1) res.append(1);
return res.reverse().toString();
}
}
557.反转字符串中的单词III
解法:
class Solution {
public String reverseWords(String s) {
if (s == null || s.length() == 0) return null;
String[] strArr = s.split(" ");
StringBuilder res = new StringBuilder();
for (int i = 0; i < strArr.length; i++) {
if (i == strArr.length - 1) {
res.append(reverse(strArr[i]));
} else {
res.append(reverse(strArr[i]) + " ");
}
}
return res.toString();
}
// 字符串反转
private String reverse(String str) {
StringBuilder sb = new StringBuilder(str);
int left = 0;
int right = str.length() - 1;
while(left < right) {
char tmp = sb.charAt(left);
sb.setCharAt(left, sb.charAt(right));
sb.setCharAt(right, tmp);
left++;
right--;
}
return sb.toString();
}
}
647.回文子串
解法:
class Solution {
public int countSubstrings(String s) {
char[] chars = s.toCharArray();
// 一个字母本身也算一个回文子串
int count = s.length();
for (int i = 0; i < chars.length; i++) {
// 记录当前i的位置
int tmp = i;
// 遇到相同字符 aa aaa aaaa
while (i < chars.length - 1 && chars[i+1] == chars[i]) {
i++;
count += i - tmp;
}
int left = tmp - 1;
int right = i + 1;
while (left >= 0 && right <= chars.length - 1 && chars[left] == chars[right]) {
count++;
left--;
right++;
}
}
return count;
}
}
最长问题
3.无重复字符的最长子串(同剑指offer48题)
解法:
class Solution {
// 方法:双指针+哈希表
// 1、哈希表dic统计:指针j遍历字符串,哈希表统计字符s[j]最后一次出现的索引位置
// 2、出现重复字符则更新左指针:根据上轮左指针i和dic[s[j]],每轮更新左边界i,保证区间[i+1,j]内无重复字符且最大。i = max(dic[s[j]], i)
// 3、更新结果res:取上轮res和本轮双指针区间[i+1, j](即j-1)的宽度做比较,取最大值。res = max(res, j-i)
public int lengthOfLongestSubstring(String s) {
HashMap<Character, Integer> dic = new HashMap<>();
// 初始化左边界
int i = -1;
int res = 0;
for (int j = 0; j < s.length(); j++) {
// 出现重复字符,将左边界替换为前一个重复字符所在位置
if (dic.containsKey(s.charAt(j))) {
i = Math.max(i, dic.get(s.charAt(j)));
}
// 记录当前索引位置
dic.put(s.charAt(j), j);
// 每轮更新结果
res = Math.max(res, j-i);
}
return res;
}
}
5.最长回文子串
解法:
class Solution {
public String longestPalindrome(String s) {
String res = "";
for (int i = 0; i < s.length(); i++) {
String str1 = findStr(s, i, i); // 中心只有一个字符的回文串
String str2 = findStr(s, i, i+1); // 中心有两个字符的回文串
// 选择str1、str2、res中最长者
if (str1.length() > str2.length()) {
if (str1.length() > res.length()) {
res = str1;
}
} else {
if (str2.length() > res.length()) {
res = str2;
}
}
}
return res;
}
// 寻找回文子串
private String findStr(String str, int left, int right) {
while (left >= 0 && right <= str.length() - 1 && str.charAt(left) == str.charAt(right)) {
left--;
right++;
}
return str.substring(left + 1, right); // substring为左闭右开
}
}
32.最长有效括号
解法:
class Solution {
// 思路:建立一个栈,利用找到一对()就出栈的原则。利用当前位置减去栈顶位置可得到pop出的括号数。
public int longestValidParentheses(String s) {
if (s == null || s.length() == 0) return 0;
Stack<Integer> stack = new Stack<>();
stack.push(-1); // 这一步可以防止当第一个Character是')'的时候发生越界异常
int res = 0;
// 遍历栈查找合适的左右括号
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
stack.push(i); // 如果找到左括号则入栈,为寻找对应右括号做铺垫
} else {
stack.pop(); //如果是右括号则将栈顶元素出栈
if (stack.isEmpty()) {
stack.push(i); // 如果栈是空的话还得把(落单的)右括号放进来
} else {
// 当前全部括号数减去剩余无法配对的括号数,即res
res = Math.max(res, i - stack.peek());
}
}
}
return res;
}
}
128.最长连续序列
解法:
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> numSet = new HashSet<Integer>();
for (int num : nums) {
numSet.add(num);
}
int longestSeq = 0;
for (int num : numSet) {
if (!numSet.contains(num - 1)) {
int currentNum = num;
int currentSeq = 1;
while (numSet.contains(currentNum + 1)) {
currentNum += 1;
currentSeq += 1;
}
longestSeq = Math.max(longestSeq, currentSeq);
}
}
return longestSeq;
}
}
300.最长递增子序列
解法:
class Solution {
// 思路:动态规划
// dp[i]的值代表nums以nums[i]结尾的最长子序列长度。
// 当nums[i] > nums[j]时:nums[i]可以接在nums[j]之后(此题要求严格递增),此情况下最长上升子序列长度为 dp[j]+1;
// 当nums[i] <= nums[j]时:nums[i]无法接在nums[j]之后,此情况上升子序列不成立,跳过。
// 转移方程:dp[i] = max(dp[i], dp[j] + 1) for j in [0, i)
public int lengthOfLIS(int[] nums) {
if(nums.length == 0) return 0;
int[] dp = new int[nums.length];
int res = 0;
Arrays.fill(dp, 1);
for(int i = 0; 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);
}
res = Math.max(res, dp[i]);
}
return res;
}
}
516.最长回文子序列
解法:
class Solution {
public int longestPalindromeSubseq(String s) {
int n = s.length();
int[][] dp = new int[n][n];
for (int i = n - 1; i >= 0; i--) {
dp[i][i] = 1;
char c1 = s.charAt(i);
for (int j = i + 1; j <= n - 1; j++) {
char c2 = s.charAt(j);
if (c1 == c2) {
dp[i][j] = dp[i + 1][j - 1] + 2;
} else {
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
}
}
}
return dp[0][n - 1];
}
}
1143.最长公共子序列
解法:
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int len1 = text1.length(), len2 = text2.length();
int[][] dp = new int[len1 + 1][len2 + 1];
for (int i = 1; i <= len1; i++) {
char c1 = text1.charAt(i - 1);
for (int j = 1; j <= len2; j++) {
char c2 = text2.charAt(j - 1);
if (c1 == c2) {
dp[i][j] = dp[i - 1][j - 1] + 1; // 该字符可以加入LCS
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); // 该位置的字符不相等,至少有一个不能加入LCS,先选择当前局部最优解,即选择前面的较大值
}
}
}
return dp[len1][len2];
}
}
动态规划
0-1背包问题
解法:
public class Main {
private static int[] weights = {20,40,80,10};
private static int[] values = {100,60,200,40};
public static int dp(int a, int n, int[] weights, int[] values) {
int[][] map = new int[a+1][n+1];
for(int i = 1; i <= n; i++) {
int value = values[i-1];
int weight = weights[i-1];
for(int j = 1; j <= a; j++) {
if(j < weight) {
map[j][i] = map[j][i-1];
continue;
}
map[j][i] = Math.max(map[j][i-1],map[j-weight][i-1]+value);
}
}
return map[a][n];
}
public static void main(String[] args){
System.out.println(dp(100,4,weights,values));
}
}
//运行结果为300,符合最佳解决方案。
53.最大子数组和(同剑指Offer42题)
解法:
class Solution {
// 思路:对于一个数A,如果A左边的累计数非负,则加上A的值不小于A,认为累计值对整体总和是有贡献的。若累计值为负,则认为不益于整体总和,就摒弃之前的累计数,从当前数字A开始累计。
public int maxSubArray(int[] nums) {
int sum = nums[0];
int max = nums[0];
for (int i = 1; i < nums.length; i++) {
sum = Math.max(sum + nums[i], nums[i]);
max = Math.max(max, sum);
}
return max;
}
}
121.买卖股票的最佳时机(一次交易)
解法:
class Solution {
// 思路:只要用一个变量minPrice记录一个历史最低价格,我们就可以假设自己的股票是在那天买的。那么我们在第 i 天卖出股票能得到的利润就是 prices[i] - minPrice。因此,我们只需要遍历价格数组一遍,记录历史最低点,然后在每一天考虑这么一个问题:如果我是在历史最低点买进的,那么我今天卖出能赚多少钱?当考虑完所有天数之时,我们就得到了最好的答案。
public int maxProfit(int[] prices) {
int minPrice = Integer.MAX_VALUE; // 最低价格(买入价格)
int res = 0; // 最大利润
for (int i = 0; i < prices.length; i++) {
if (prices[i] < minPrice) {
minPrice = prices[i]; // 更新最低价格
} else {
if (prices[i] - minPrice > res) {
res = prices[i] - minPrice;
}
}
}
return res;
}
}
122.买卖股票的最佳时机II(多次交易)
解法:
class Solution {
// 思路:这道题「贪心」的地方在于,对于「今天的股价 - 昨天的股价」,得到的结果有3种可能:① 正数,② 0,③负数。贪心算法的决策是:只加正数 。
public int maxProfit(int[] prices) {
if (prices.length < 2) return 0;
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;
}
}
134.加油站
解法:
class Solution {
// 思路:解只能存在于从累计亏损最大的加油站的下一个站出发。将累计亏损最大的那些加油站放在行程的后面,保证油在前面的加油站尽可能加足。如果跑完一周后,若剩余油量>=0,则成功
public int canCompleteCircuit(int[] gas, int[] cost) {
int remain = 0; // 累计剩余油量
int minRemain = Integer.MAX_VALUE; // 累计最小剩余油量(或者累计最大亏损油量)
int start = 0; // 开始出发位置
for (int i = 0; i < gas.length; i++) {
remain += gas[i] - cost[i];
if (remain < minRemain) {
minRemain = remain; // 更新最小剩余油量
start = i + 1; // 从累计亏损最大的加油站的下一个站出发
}
}
return remain >= 0 ? start % gas.length : -1;
}
}
198.打家劫舍
解法:
class Solution {
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(nums[0], nums[1]);// 偷2号房和偷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];
}
}
213.打家劫舍II
解法:
class Solution {
// 房子呈环形的话,第一间房和最后一间房只能偷一个了
public int rob(int[] nums) {
if (nums.length == 1) return nums[0];
if (nums.length == 2) return Math.max(nums[0], nums[1]);
return Math.max(robRange(nums, 0, nums.length - 2),
robRange(nums, 1, nums.length - 1));
}
// 查找房屋范围内偷盗的最大价值
private int robRange(int[] nums, int start, int end) {
// pre2代表当前房间左边第二间房为止的偷盗价值,pre1代表当前房间左边第一间房为止的偷盗价值
int pre2 = nums[start], pre1 = Math.max(nums[start], nums[start + 1]);
for (int i = start + 2; i <= end; i++) {
int tmp = pre1;// 缓存前一间房为止偷盗的最大价值
pre1 = Math.max(pre1, pre2 + nums[i]);// 当前房间偷盗价值:要么不偷要么偷
pre2 = tmp;
}
return pre1;
}
}
322.零钱兑换
解法:
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 j = 0; j < coins.length; j++) {
if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] <= amount ? dp[amount] : -1;
}
}
518.零钱兑换II
解法:
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 = coin; i <= amount; i++) {
dp[i] += dp[i - coin];
}
}
return dp[amount];
}
}
岛屿问题
200.岛屿数量
解法:
class Solution {
public int numIslands(char[][] grid) {
int num = 0;
for (int row = 0; row < grid.length; row++) {
for (int col = 0; col < grid[0].length; col++) {
if (grid[row][col] == '1') {// 遇到陆地
num++;
dfs(grid, row, col);// 递归遍历与陆地接壤的格子,把访问过的格子都置为2,防止重复递归
}
}
}
return num;
}
private void dfs(char[][] grid, int row, int col) {
if (row < 0 || row >= grid.length || col < 0 || col >= grid[0].length) {// 越界
return;
}
if (grid[row][col] != '1') {// 遇到海洋返回
return;
}
grid[row][col] = '2';// 将当前访问到的'1'格子置为2,避免重复递归
// 往四面八方去寻找下一个'1'
dfs(grid, row, col - 1);
dfs(grid, row, col + 1);
dfs(grid, row - 1, col);
dfs(grid, row + 1, col);
}
}
463.岛屿的周长
解法:
class Solution {
public int islandPerimeter(int[][] grid) {
for (int row = 0; row < grid.length; row++) {
for (int col = 0; col < grid[0].length; col++) {
if (grid[row][col] == 1) {
return dfs(grid, row, col);
}
}
}
return 0;
}
int dfs(int[][] grid, int row, int col) {
if (!inArea(grid, row, col)) return 1;// 当前格子超出网格范围,对应最外层边界线
if (grid[row][col] == 0) return 1;// 当前格子是海洋格子,对应陆地和海洋之间边界线
if (grid[row][col] != 1) return 0;// 当前格子是已遍历的陆地格子,和周长没关系
grid[row][col] = 2;
return dfs(grid, row, col - 1)
+ dfs(grid, row, col + 1)
+ dfs(grid, row - 1, col)
+ dfs(grid, row + 1, col);
}
// 判断坐标 (row, col) 是否在网格中
boolean inArea(int[][] grid, int row, int col) {
return row >= 0 && row < grid.length && col >= 0 && col < grid[0].length;
}
}
695.岛屿的最大面积
解法:
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int maxArea = 0;
for (int row = 0; row < grid.length; row++) {
for (int col = 0; col < grid[0].length; col++) {
if (grid[row][col] == 1) {
maxArea = Math.max(maxArea, area(grid, row, col));
}
}
}
return maxArea;
}
// 计算岛屿面积
int area(int[][] grid, int row, int col) {
if (!inArea(grid, row, col)) return 0;
if (grid[row][col] != 1) return 0;// 非岛屿格子或者计算过的格子就不再计算了
grid[row][col] = 2;// 访问过的格子标记为2
return 1
+ area(grid, row, col - 1)
+ area(grid, row, col + 1)
+ area(grid, row - 1, col)
+ area(grid, row + 1, col);
}
// 判断是否在网格内
boolean inArea(int[][] grid, int row, int col) {
return row >=0 && row < grid.length && col >= 0 && col < grid[0].length;
}
}
链表
21.合并两个有序链表
解法:
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if (list1 == null) return list2;
if (list2 == null) return list1;
ListNode newHead = new ListNode(-1);
ListNode cur = newHead;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
ListNode node = new ListNode(list1.val);
cur.next = node;
cur = cur.next;
list1 = list1.next;
} else {
ListNode node = new ListNode(list2.val);
cur.next = node;
cur = cur.next;
list2 = list2.next;
}
}
while (list1 != null) {
ListNode node = new ListNode(list1.val);
cur.next = node;
cur = cur.next;
list1 = list1.next;
}
while (list2 != null) {
ListNode node = new ListNode(list2.val);
cur.next = node;
cur = cur.next;
list2 = list2.next;
}
return newHead.next;
}
}
23.合并K个升序链表
解法:
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0) return null;
return merge(lists, 0, lists.length - 1);
}
// 分治
private ListNode merge(ListNode[] lists, int left, int right) {
if (left == right) return lists[left];
int mid = (left + right) / 2;
ListNode l1 = merge(lists, left, mid);
ListNode l2 = merge(lists, mid + 1, right);
return mergeTwoList(l1, l2);
}
// 合并两个有序链表
private ListNode mergeTwoList(ListNode node1, ListNode node2) {
if (node1 == null) return node2;
if (node2 == null) return node1;
if (node1.val <= node2.val) {
node1.next = mergeTwoList(node1.next, node2);
return node1;
} else {
node2.next = mergeTwoList(node1, node2.next);
return node2;
}
}
}
82.删除排序链表中的重复元素II
解法:
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) return head;
ListNode dummy = new ListNode(0, head);
ListNode cur = dummy;
while (cur.next != null && cur.next.next != null) {
if (cur.next.val == cur.next.next.val) {
int x = cur.next.val;
while (cur.next != null && cur.next.val == x) {
cur.next = cur.next.next;
}
} else {
cur = cur.next;
}
}
return dummy.next;
}
}
83.删除排序链表中的重复元素
解法:
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) return 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;
}
}
109.有序链表转换二叉搜索树
解法:
class Solution {
public TreeNode sortedListToBST(ListNode head) {
if (head == null) return null;
if (head.next == null) return new TreeNode(head.val);// 只有一个节点
ListNode slow = head;
ListNode fast = head;
ListNode pre = slow;
// 快慢指针找中点,slow指向的位置就是中位数
while (fast != null && fast.next != null) {
pre = slow;
slow = slow.next;
fast = fast.next.next;
}
pre.next = null;// 把慢指针与其之前的节点断开(即断开根节点与左子树)
TreeNode root = new TreeNode(slow.val);// 根节点
root.left = sortedListToBST(head);// 左子树
root.right = sortedListToBST(slow.next);// 右子树
return root;
}
}
141.环形链表(是否有环)
解法:
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) return false;
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;// 快慢指针相遇,则表示有环
}
return false;
}
}
142.环形链表II(环形入口)
解法:
public class Solution {
public ListNode detectCycle(ListNode head) {
if (head == null || head.next == null) return null;
ListNode slow = head, fast = head;
while (true) {
if (fast == null || fast.next == null) return null;
slow = slow.next;
fast = fast.next.next;
if (slow == fast) { // 快慢指针相遇处
break;
}
}
slow = head; // 慢指针重回头节点,快慢指针继续各前进一步,待快慢指针再相遇之处就是环形入口
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
146.LRU缓存机制
解法:
public class LRUCache {
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {}
public DLinkedNode(int _key, int _value) {key = _key; value = _value;}
}
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
private int size;
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode newNode = new DLinkedNode(key, value);
// 添加进哈希表
cache.put(key, newNode);
// 添加至双向链表的头部
addToHead(newNode);
++size;
if (size > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode tail = removeTail();
// 删除哈希表中对应的项
cache.remove(tail.key);
--size;
}
}
else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}
160.相交链表(同剑指Offer52题)
解法:
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int lengthA = length(headA);
int lengthB = length(headB);
ListNode longHead = null;
ListNode shortHead = null;
int diff = 0;
if (lengthA > lengthB) {
diff = lengthA - lengthB;
longHead = headA;
shortHead = headB;
} else {
diff = lengthB - lengthA;
longHead = headB;
shortHead = headA;
}
// 快指针先走长度差的步数
for (int i = 0; i < diff; i++) {
longHead = longHead.next;
}
while (longHead != null && shortHead != null && longHead != shortHead) {
longHead = longHead.next;
shortHead = shortHead.next;
}
return shortHead;
}
int length(ListNode head) {
int length = 0;
while (head != null) {
length++;
head = head.next;
}
return length;
}
}
206.反转链表(同剑指Offer24题)
解法:
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null) return null;
ListNode cur = head;//当前节点
ListNode pre = null;
while (cur != null) {
ListNode next = cur.next;//缓存当前节点的下一个节点
cur.next = pre;//反转
pre = cur;
cur = next;//节点后移
}
return pre;
}
}
328.奇偶链表
解法:
class Solution {
public ListNode oddEvenList(ListNode head) {
if (head == null) return null;
ListNode evenHead = head.next;
ListNode odd = head, even = evenHead;
while (even != null && even.next != null) {
// 拆分奇偶索引节点
odd.next = even.next;
odd = odd.next;
even.next = odd.next;
even = even.next;
}
odd.next = evenHead;// 偶索引链表放在奇索引链表后边
return head;
}
}
树
92.二叉树的中序遍历
解法:
class Solution {
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);
}
}
144.二叉树的前序遍历
解法:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
preorder(root, res);
return res;
}
// 递归法-前序遍历
private void preorder(TreeNode root, List<Integer> res) {
if (root == null) return;
res.add(root.val);
preorder(root.left, res);
preorder(root.right, res);
}
}
145.二叉树的后序遍历
解法:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
postorder(root, res);
return res;
}
// 递归法-后序遍历
private void postorder(TreeNode root, List<Integer> res) {
if (root == null) return;
postorder(root.left, res);
postorder(root.right, res);
res.add(root.val);
}
}
222.完全二叉树的节点个数
解法:
class Solution {
public int countNodes(TreeNode root) {
return countNum(root);
}
private int countNum(TreeNode root) {
if (root == null) return 0;
return countNum(root.left) + countNum(root.right) + 1;
}
}
其他问题
135.分发糖果
解法:
class Solution {
// 思路:设学生A和学生B左右相邻,A在B左边:
// 左规则:当ratings_b > ratings_a时,B的糖比A的糖数量多
// 右规则:当ratings_a > ratings_b时,A的糖比B的糖数量多
// 相邻的学生中,评分高的学生必须获得更多的糖果 等价于 所有学生满足左规则且满足右规则
public int candy(int[] ratings) {
int[] left = new int[ratings.length];
int[] right = new int[ratings.length];
Arrays.fill(left, 1);
Arrays.fill(right, 1);
for (int i = 1; i < ratings.length; i++) {
if (ratings[i] > ratings[i - 1]) {
left[i] = left[i - 1] + 1;
}
}
int candyCount = left[ratings.length - 1];//左规则遍历赋值的最后一个位置上的数一定是符合规则要求的,所以将它作为初始值,开启右规则遍历赋值
for (int i = ratings.length - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1]) {
right[i] = right[i + 1] + 1;
}
candyCount += Math.max(left[i], right[i]);
}
return candyCount;
}
}
252.会议室
解法:
class Solution {
public boolean canAttendMeetings(int[][] intervals) {
// 按会议开始时间升序排列
Arrays.sort(intervals, (o1, o2) -> o1[0] - o2[0]);
for(int i=0;i<intervals.length-1;i++){
// 如果当前会议结束时间大于下一个会议开始时间,则false
if(intervals[i][1]>intervals[i+1][0])
return false;
}
return true;
}
}
253.会议室II
样例描述:
示例 1:
输入:intervals = [[0,30],[5,10],[15,20]]
输出:2
示例 2:
输入:intervals = [[7,10],[2,4]]
输出:1
解法:
//思路:排序 + 优先队列(最小堆)
// 将所有会议按照开始时间排序,优先队列存储会议的结束时间由小到大排序
// 对于当前的会议,如果开始时间小于优先队列元素的会议结束时间,就需要新开一个房间,因此将它重新入队
// 最后优先队列剩余的就是最少的会议室数(结合下面的过程图来理解,也可以另外用一个变量记录房间数)
class Solution {
public int minMeetingRooms(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> (a[0] - b[0]));
//存结束时间由小到大
PriorityQueue<Integer> pq = new PriorityQueue<>();
int n = intervals.length;
int res = 1;
for (int i = 0; i < n; i ++ ) {
if (!pq.isEmpty()) {
int end = pq.poll();
//如果开始时间比上一个的结束时间小,就需要新开一个房间
//同时让上一个的结束时间重新入队
if (intervals[i][0] < end) {
pq.offer(end);
res ++;
}
}
pq.add(intervals[i][1]);
}
return res;
}
}