文章目录
- 一、数组
- 1、026——[删除有序数组中的重复项](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/)
- 2、027——[移除元素](https://leetcode-cn.com/problems/remove-element/)
- 3、031——[下一个排列](https://leetcode-cn.com/problems/next-permutation/)
- 4、041——[缺失的第一个正数](https://leetcode-cn.com/problems/first-missing-positive/)
- 5、054——[螺旋矩阵](https://leetcode-cn.com/problems/spiral-matrix/)
- 6、056——[合并区间](https://leetcode-cn.com/problems/merge-intervals/)
- 8、059——[ 螺旋矩阵 II](https://leetcode-cn.com/problems/spiral-matrix-ii/)
- 9、073——[矩阵置零](https://leetcode-cn.com/problems/set-matrix-zeroes/)
- 10、118——[杨辉三角](https://leetcode-cn.com/problems/pascals-triangle/)
- 11、119——[杨辉三角 II](https://leetcode-cn.com/problems/pascals-triangle-ii/)
- 12、164——[最大间距](https://leetcode-cn.com/problems/maximum-gap/)
- 13、189——[轮转数组](https://leetcode-cn.com/problems/rotate-array/)
- 14、228——[汇总区间](https://leetcode-cn.com/problems/summary-ranges/)
- 15、283——[移动零](https://leetcode-cn.com/problems/move-zeroes/)
- 16、289——[生命游戏](https://leetcode-cn.com/problems/game-of-life/)
一、数组
1、026——删除有序数组中的重复项
给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。将最终结果插入 nums 的前 k 个位置后返回 k 。不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
/**
* 思路:用temp记录当前位置的值,然后依次比较,如果相同数组下标移动,
* 如果不同,将当前数组下标的值,复制到ans位置
*
* @param nums 目标数组
* @return 新数组长度
*/
public int removeDuplicates(int[] nums) {
if (null == nums || nums.length == 0) {
return 0;
}
int temp = nums[0];
int ans = 1;
int index = 1;
//
while (index < nums.length) {
if (nums[index] != temp) {
nums[ans++] = nums[index];
temp = nums[index];
}
index++;
}
return ans;
}
2、027——移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
/**
* 思路:用ans下标记录不同于目标值的元素,返回ans值
*
* @param nums 目标数组
* @param val 目标值
* @return 返回新数组长度
*/
public static int removeElement(int[] nums, int val) {
if (null == nums || nums.length == 0) {
return 0;
}
int index = 0;
int ans = 0;
while (index < nums.length) {
if (nums[index] != val) {
nums[ans++] = nums[index];
}
index++;
}
return ans;
}
3、031——下一个排列
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。
给你一个整数数组 nums ,找出 nums 的下一个排列。
必须 原地 修改,只允许使用额外常数空间。
/**
* 思路:从后往前找到第一个arr[k] < arr[k + 1],这个时候从arr[k+1] 往后依次递减
* 然后再从后往前找到第一个大于arr[k]的值,两者交换,然后将从arr[k+1]开始往后这段反转
* 如果没找到arr[k] < arr[k + 1],此时k为-1,即数组为降序数组,直接倒序就行
*
* @param nums 目标数组
*/
public static void nextPermutation(int[] nums) {
if (null == nums || nums.length < 2) {
return;
}
int k = nums.length - 2;
while (k >= 0 && nums[k] >= nums[k + 1]) {
k--;
}
if (k >= 0) {
int i = nums.length - 1;
while (i >= 0 && nums[k] >= nums[i]) {
i--;
}
swap(nums, k, i);
}
reverse(nums, k + 1);
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
private static void reverse(int[] arr, int left) {
int l = left;
int r = arr.length - 1;
while (l < r) {
swap(arr, l, r);
l++;
r--;
}
}
4、041——缺失的第一个正数
给你一个未排序的整数数组 nums
,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n)
并且只使用常数级别额外空间的解决方案。
/**
* 思路:原地哈希,由题可知返回值为[1,N + 1],
* 把1放到下标为0,2放到下标为1的位置,以此类推,哪个位置的值不等于下标+1,就返回那个位置的下标+1值
*
* @param nums 目标数组
* @return 返回未出现的最小正整数
*/
public static int firstMissingPositive(int[] nums) {
if (null == nums || nums.length == 0) {
return 0;
}
int len = nums.length;
for (int i = 0; i < len; i++) {
while (nums[i] > 0 && nums[i] <= len && nums[nums[i] - 1] != nums[i]) {
// 满足在指定范围内、并且没有放在正确的位置上,才交换
swap(nums, nums[i] - 1, i);
}
}
for (int i = 0; i < len; i++) {
if (nums[i] != i + 1) {
return i + 1;
}
}
// 都正确则返回数组长度 + 1
return len + 1;
}
private static void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
5、054——螺旋矩阵
给你一个 m
行 n
列的矩阵 matrix
,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
/**
* 定义边界值,不断缩小边界值
*
* @param matrix 目标数组
* @return 顺时针遍历结果
*/
public static List<Integer> spiralOrder(int[][] matrix) {
List<Integer> list = new ArrayList<>();
if (null == matrix || matrix.length == 0) {
return list;
}
int u = 0;
int d = matrix.length - 1;
int l = 0;
int r = matrix[0].length - 1;
while (true) {
// 向右移动到最右
for (int i = l; i <= r; i++) {
list.add(matrix[u][i]);
}
// 若上边界大于下边界,则遍历遍历完成
if (++u > d) {
break;
}
// 向下遍历
for (int i = u; i <= d; i++) {
list.add(matrix[i][r]);
}
// 若右边界小于左边界,则遍历遍历完成
if (--r < l) {
break;
}
// 向左遍历
for (int i = r; i >= l; i--) {
list.add(matrix[d][i]);
}
// 若下边界小于上边界,则遍历遍历完成
if (--d < u) {
break;
}
for (int i = d; i >= u; i--) {
list.add(matrix[i][l]);
}
if (++l > r) {
break;
}
}
return list;
}
6、056——合并区间
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
/**
* 思路:排序 + 双指针
* 左指针指区间开始,一个临时变量存最大的区间结尾值,右指针也指向区间开始,
* 比较该区间开始值和临时最大值是否重合,重合取较大的值,更新右指针,
* 如果不重合,放入结果集,更新左指针
*
* @param intervals 目标数组
* @return 返回合并后的区间
*/
public int[][] merge(int[][] intervals) {
if (intervals == null || intervals.length == 0) {
return new int[0][2];
}
// 排序
Arrays.sort(intervals, (a, b) -> {
return a[0] - b[0];
});
List<int[]> res = new ArrayList<>();
//下一个数组的起始值必须要小于上一个数组的最大值才能合并
int leftIndex = 0;
int rightIndex = 0;
while (rightIndex < intervals.length) {
int start = intervals[leftIndex][0];
int end = intervals[rightIndex][1];
while (rightIndex < intervals.length && intervals[rightIndex][0] <= end) {
end = Math.max(end, intervals[rightIndex][1]);
rightIndex++;
}
res.add(new int[]{start, end});
leftIndex = rightIndex;
}
int[][] result = new int[res.size()][2];
for (int i = 0; i < res.size(); i++) {
result[i][0] = res.get(i)[0];
result[i][1] = res.get(i)[1];
}
return result;
}
7、057——插入区间
给你一个 无重叠的 *,*按照区间起始端点排序的区间列表。
在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。
/**
* 思路:因为是有序的,所以从左到右,先把左边不重合的区间添加进去,
* 在找到重合的区间,添加进去,再把右边不重合部分添加进去
*
* @param intervals 原数组
* @param newInterval 插入数组
* @return 合并后的区间
*/
public static int[][] insert2(int[][] intervals, int[] newInterval) {
if (intervals == null || intervals.length == 0) {
return new int[][]{{newInterval[0], newInterval[1]}};
}
if (newInterval == null || newInterval.length == 0) {
return intervals;
}
List<int[]> res = new ArrayList<>();
int len = intervals.length;
int index = 0;
// 判断左边不重合
while (index < len && intervals[index][1] < newInterval[0]) {
res.add(intervals[index]);
index++;
}
// 判断重合
while (index < len && intervals[index][0] <= newInterval[1]) {
newInterval[0] = Math.min(intervals[index][0], newInterval[0]);
newInterval[1] = Math.max(intervals[index][1], newInterval[1]);
index++;
}
res.add(newInterval);
// 判断右边不重合
while (index < len && intervals[index][0] > newInterval[1]) {
res.add(intervals[index]);
index++;
}
return res.toArray(new int[0][]);
}
8、059—— 螺旋矩阵 II
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
/**
* 思路:同54题旋转矩阵,定义边界值,不断更新边界值,遍历
*
* @param n 输入的正整数值
* @return 顺时针螺旋排列的数组
*/
public int[][] generateMatrix(int n) {
if (n == 1) {
return new int[][]{{1}};
}
int[][] result = new int[n][n];
int u = 0;
int d = n - 1;
int l = 0;
int r = n - 1;
int start = 1;
while (start <= n * n) {
// 向左遍历
for (int i = l; i <= r; i++) {
result[u][i] = start++;
}
if (++u > d) {
break;
}
// 向下遍历
for (int i = u; i <= d; i++) {
result[i][r] = start++;
}
if (--r < l) {
break;
}
// 向左遍历
for (int i = r; i >= l; i--) {
result[d][i] = start++;
}
if (--d < u) {
break;
}
for (int i = d; i >= u; i--) {
result[i][l] = start++;
}
if (++l > r) {
break;
}
}
return result;
}
9、073——矩阵置零
给定一个 *m* x *n*
的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法
输入:matrix = [[1,1,1],[1,0,1],[1,1,1]]
输出:[[1,0,1],[0,0,0],[1,0,1]]
/**
* 思路: 先判断第一行和第一列是否需要置0, 然后用第一行和第一列来标记该行或该列是否是要置0
* 然后处理第一行和第一列
*
* @param matrix 目标数组
*/
public void setZeroe1(int[][] matrix) {
if (null == matrix || matrix.length == 0) {
return;
}
int row = matrix.length;
int col = matrix[0].length;
boolean rowFlag = false;
boolean colFlag = false;
// 第一行是否有0
for (int i = 0; i < col; i++) {
if (matrix[0][i] == 0) {
rowFlag = true;
break;
}
}
// 第一列是否有0
for (int i = 0; i < row; i++) {
if (matrix[i][0] == 0) {
colFlag = true;
break;
}
}
// 把第一行和第一列作为标记位
for (int i = 1; i < row; i++) {
for (int j = 1; j < col; j++) {
if (matrix[i][j] == 0) {
matrix[i][0] = matrix[0][j] = 0;
}
}
}
// 置0
for (int i = 1; i < row; i++) {
for (int j = 1; j < col; j++) {
if (matrix[i][0] == 0 || matrix[0][j] == 0) {
matrix[i][j] = 0;
}
}
}
if (rowFlag) {
for (int j = 0; j < col; j++) {
matrix[0][j] = 0;
}
}
if (colFlag) {
for (int i = 0; i < row; i++) {
matrix[i][0] = 0;
}
}
}
10、118——杨辉三角
给定一个非负整数 *numRows
,*生成「杨辉三角」的前 numRows
行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
/**
* 思路:根据杨辉三角的特性可知,每一层第一个和最后一个都为1,其他位置等于上一层,左边和右边之和
*
* @param numRows 非负整数
* @return 杨辉三角集合
*/
public static List<List<Integer>> generate(int numRows) {
List<List<Integer>> ans = new ArrayList<List<Integer>>();
for (int i = 1; i <= numRows; i++) {
List<Integer> level = new ArrayList<>();
for (int j = 1; j <= i; j++) {
if (j == 1 || j == i) {
level.add(1);
} else {
level.add(ans.get(i - 2).get(j - 2) + ans.get(i - 2).get(j - 1));
}
}
ans.add(level);
}
return ans;
}
11、119——杨辉三角 II
给定一个非负索引 rowIndex
,返回「杨辉三角」的第 rowIndex
行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
/**
* 思路:本题仅需最后一行的数据,当处理到最后一行时,row正好是上一行的数据,直接使用
*
* @param rowIndex 要返回的行数
* @return 返回输入行的集合
*/
public List<Integer> getRow(int rowIndex) {
List<Integer> row = new ArrayList<>();
row.add(1);
for (int i = 1; i < rowIndex; i++) {
row.add(0);
for (int j = i; j > 0; j--) {
row.set(j, row.get(j) + row.get(j - 1));
}
}
return row;
}
12、164——最大间距
给定一个无序的数组 nums,返回 数组在排序之后,相邻元素之间最大的差值 。如果数组元素个数小于 2,则返回 0 。
您必须编写一个在「线性时间」内运行并使用「线性额外空间」的算法。
输入: nums = [3,6,9,1]
输出: 3
解释: 排序后的数组是 [1,3,6,9], 其中相邻元素 (3,6) 和 (6,9) 之间都存在最大差值 3。
/**
* 思路:桶排序时间复杂度O(n)
*/
public int maximumGap(int[] nums) {
if (nums == null || nums.length < 2) {
return 0;
}
radixSort(nums);
int max = 0;
for (int i = nums.length - 1; i > 0; i--) {
max = Math.max(max, nums[i] - nums[i - 1]);
}
return max;
}
private void radixSort(int[] arr) {
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
int maxLength = (max + "").length();
int[][] bucket = new int[10][arr.length];
int[] elementCount = new int[10];
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
for (int j = 0; j < arr.length; j++) {
int num = arr[j] / n % 10;
bucket[num][elementCount[num]] = arr[j];
elementCount[num]++;
}
int index = 0;
for (int j = 0; j < 10; j++) {
if (elementCount[j] > 0) {
for (int k = 0; k < elementCount[j]; k++) {
arr[index++] = bucket[j][k];
}
}
elementCount[j] = 0;
}
}
}
13、189——轮转数组
给你一个数组,将数组中的元素向右轮转 k
个位置,其中 k
是非负数。
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
/**
* 思路:用一个额外数组,偏移量+原下标 对数组长度取模就是新数组的下标
*/
public static void rotate(int[] nums, int k) {
int[] ans = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
int index = (i + k) % nums.length;
ans[index] = nums[i];
}
for (int i = 0; i < nums.length; i++) {
nums[i] = ans[i];
}
}
/**
* 思路:反转,先把整个数组反转,然后根据偏移量切分,分别反转 就是向右的偏移量的结果
*/
public static void rotate1(int[] nums, int k) {
k = k % nums.length;
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
}
private static void reverse(int[] arr, int start, int end) {
while (start < end) {
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start++;
end--;
}
}
14、228——汇总区间
给定一个 无重复元素 的 有序 整数数组 nums 。
返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums 的数字 x 。
列表中的每个区间范围 [a,b] 应该按如下格式输出:
“a->b” ,如果 a != b
“a” ,如果 a == b
public static List<String> summaryRanges1(int[] nums) {
ArrayList<String> list = new ArrayList<>();
int len = nums.length;
if (len == 0) return list;
int start = 0, end = 0;
for (int i = 1; i < len; i++) {
if (nums[end] + 1 == nums[i]) {
end++;
continue;
}
if (end == start) {
list.add(nums[start] + "");
} else {
list.add(nums[start] + "->" + nums[end]);
}
start = end + 1;
end = start;
}
if (end == start) {
list.add(nums[start] + "");
} else {
list.add(nums[start] + "->" + nums[end]);
}
return list;
}
15、283——移动零
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
/**
* 思路:遍历两次,第一次把不为0的数,统统按原有顺序左移,第二次把剩下的数全置0
*/
public void moveZeroes(int[] nums) {
if (nums == null) {
return;
}
int j = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
nums[j++] = nums[i];
}
}
for (int i = j; i < nums.length; i++) {
nums[i] = 0;
}
}
/**
* 思路:参考快排的思想,把等于0的放右边,不为0的放左边,按顺序放
*/
public void moveZeroes(int[] nums) {
if (nums == null) {
return;
}
int j = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
int temp = nums[i];
nums[i] = nums[j];
nums[j++] = temp;
}
}
}
16、289——生命游戏
根据 百度百科 , 生命游戏 ,简称为 生命 ,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。
给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态: 1 即为 活细胞 (live),或 0 即为 死细胞 (dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律:
如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;
如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;
如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;
如果死细胞周围正好有三个活细胞,则该位置死细胞复活;
下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。给你 m x n 网格面板 board 的当前状态,返回下一个状态。
输入:board = [[0,1,0],[0,0,1],[1,1,1],[0,0,0]]
输出:[[0,0,0],[1,0,1],[0,1,1],[0,1,0]]
原本活(1) 原本死(0)
----------------------------------------------------------------------------------------------------------
周围活的数量 < 2 1 -> 0 0 -> 0(状态未变,不用管了)
----------------------------------------------------------------------------------------------------------
周围活的数量 = 2 1 -> 1(状态未变,不用管了) 0 -> 0(状态未变,不用管了)
----------------------------------------------------------------------------------------------------------
周围活的数量 = 3 1 -> 1(状态未变,不用管了) 0 -> 1
----------------------------------------------------------------------------------------------------------
周围活的数量 > 3 1 -> 0 0 -> 0(状态未变,不用管了)
int[] neighbors = {0, 1, -1};
int rows = board.length;
int cols = board[0].length;
// 遍历面板每一个格子里的细胞
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
// 对于每一个细胞统计其八个相邻位置里的活细胞数量
int liveNeighbors = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (!(neighbors[i] == 0 && neighbors[j] == 0)) {
// 相邻位置的坐标
int r = (row + neighbors[i]);
int c = (col + neighbors[j]);
// 查看相邻的细胞是否是活细胞
if ((r < rows && r >= 0) && (c < cols && c >= 0) && (Math.abs(board[r][c]) == 1)) {
liveNeighbors += 1;
}
}
}
}
// 规则 1 或规则 3
if ((board[row][col] == 1) && (liveNeighbors < 2 || liveNeighbors > 3)) {
// -1 代表这个细胞过去是活的现在死了
board[row][col] = -1;
}
// 规则 4
if (board[row][col] == 0 && liveNeighbors == 3) {
// 2 代表这个细胞过去是死的现在活了
board[row][col] = 2;
}
}
}
// 遍历 board 得到一次更新后的状态
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
if (board[row][col] > 0) {
board[row][col] = 1;
} else {
board[row][col] = 0;
}
}
}