1. 第73题 矩阵置零
给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。
这题如果不适用原地算法非常简单,可以用做初学者的数组练习题,直接开一个n * m
的矩阵,然后让它的元素和原数组完全对应,之后遍历原数组的每一个元素,如果是0
在新数组改变对应行列的元素即可。(时空复杂度较高)
如果空间复杂度压缩到O(n + m)
的话,我们只需要开两个一维数组,记录行列是否存在0
,最后在遍历修改值即可。
但是要求原地算法,那就不能多开数组,我们可以借助原数组的第一行和第一列作为上面的两个数组,但是导致的问题就是第一行和第一列无法被计算,所以我们多开两个遍历记录第一行和第一列是否应该变为0
class Solution {
public void setZeroes(int[][] matrix) {
int r0 = 1, c0 = 1; // r0记录第一行是否存在0, c0记录第一列是否存在0
int n = matrix.length, m = matrix[0].length; // n:行数 m:列数
for (int i = 0; i < m; i ++ ) if (matrix[0][i] == 0) r0 = 0; // 如果第一列存在0,就把r0标志改变
for (int i = 0; i < n; i ++ ) if (matrix[i][0] == 0) c0 = 0;
for (int i = 1; i < m; i ++ ) // 判断除了第一列的每一列是否有0
for (int j = 0; j < n; j ++ )
if (matrix[j][i] == 0) // 如果存在0就把第一列当成额外的一维数组,标记成0
matrix[0][i] = 0;
for (int i = 1; i < n; i ++ ) // 和列的操作一样
for (int j = 0; j < m; j ++ )
if (matrix[i][j] == 0)
matrix[i][0] = 0;
for (int i = 1; i < m; i ++ )
if (matrix[0][i] == 0) // 如果某一列存在0
for (int j = 0; j < n; j ++ ) // 这一列全部赋值为0
matrix[j][j] = 0;
for (int i = 1; i < n; i ++ )
if (matrix[i][0] == 0) // 如果某一行存在0
for (int j = 0; j < m; j ++ ) // 这一行全部赋值为0
matrix[i][j] = 0;
// 此时就剩第一行第一列没有解决了,单独判断一下是否应该全部赋值0
if (r0 == 0) for (int i = 0; i < m; i ++ ) matrix[0][i] = 0;
if (c0 == 0) for (int i = 0; i < n; i ++ ) matrix[i][0] = 0;
}
}
时间复杂度从O(n*n*m*m)
变成了O(4n*n)
,空间复杂度变成了O(1)
2. 第75题 颜色分类
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
这就是一个普通的排序,如果遇到了那我肯定选择直接一个快排搞定它,像这样:
class Solution {
void quick_sort(int l, int r, int[] nums) {
if (l >= r) return;
int x = nums[l + r >> 1], i = l - 1, j = r + 1;
while (i < j) {
do i ++ ; while (nums[i] < x);
do j -- ; while (nums[j] > x);
if (i < j) {
int t = nums[i]; // java的swap
nums[i] = nums[j];
nums[j] = t;
}
}
quick_sort(l, j, nums);
quick_sort(j + 1, r, nums);
}
public void sortColors(int[] nums) {
quick_sort(0, nums.length - 1, nums);
}
}
当然快排一遍还是较为复杂,可以直接扫描一遍记录下三个数分别出现多少次,在写回去就行了。
上面是两种非常容易想到的写法,还有一种较为巧妙地方法,我们姑且叫它三指针吧
首先维护i, j, k
三个变量,在数组上的指向让它符合j < i < k
,且下标0 ~ j - 1
这段区间全部是0
,j ~ i - 1
这段区间全部是1
,k + 1 ~ n - 1
这段全部是2。初始的时候i = j = 0, k = n - 1
,这样可以发现满足上述的定义,让i
尝试者向右移动,期间不断维护上述的性质,当i
与k
相交时整个区间就排完了。
class Solution {
public void sortColors(int[] nums) {
int t;
for (int i = 0, j = 0, k = nums.length - 1; i <= k; ) {
if (nums[i] == 2) {
t = nums[i];
nums[i] = nums[k];
nums[k] = t;
k -- ;
} else if (nums[i] == 1) i ++ ; // 如果i的位置是1只需要i继续移动就能维护该性质
else {
// i的位置等于0时候,这个0应该给下标0~j-1的区间,所以做交换
// 又因为j所在的位置根据性质肯定为1,所以交换完i的位置已经是1了,根据性质j~i-1是1,所以i和j都要移动
t = nums[i];
nums[i] = nums[j];
nums[j] = t;
i ++ ;
j ++ ;
}
}
}
}
3. 第78题 子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
元素互不相同,找所有子集,我直接上来就是一个爆搜,但是爆栈了,很奇怪,找了半天发现了自己的憨批操作
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
void dfs(int[] nums, int step) {
if (step == nums.length) {
res.add(new ArrayList(path));
return; // 我忘了return!!!
}
path.add(nums[step]);
dfs(nums, step + 1);
path.remove(path.size() - 1);
dfs(nums, step + 1);
}
public List<List<Integer>> subsets(int[] nums) {
dfs(nums, 0);
return res;
}
}
查看题解发现还有二进制的巧妙做法:
假设有n
个数,那么这些数的子集就有2^n
个,其中每一种方案都对应着一些数要被选择,一些数不被选择,那么选择的话设置1
,不选的设置0
,每一种方案都对应了一个01
串,把他们看成二进制的位的话,组成的就一定是大于等于0
且小于等于2^n
的数字(最大就是全选)
那么自己和二进制的数字就一一对应,就可以枚举这些数字,看看他们的每一位是不是1
,如果是的话就证明被选择了,加到答案里即可。
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < (1 << nums.length); i ++ ) {
List<Integer> path = new ArrayList();
for (int j = 0; j < nums.length; j ++ )
if ((i >> j & 1) == 1)
path.add(nums[j]);
res.add(path);
}
return res;
}
}
第一天,且有事,所以only三题