遍历
数组的遍历最好是考虑用二分查找,可以有效的讲时间复杂度降为logN。
class Solution {
public void nextPermutation(int[] nums) {
// 从后往前遍历找到第一个顺序对
int len = nums.length;
int i = len - 2;
while (i >= 0) {
if (nums[i] < nums[i + 1]) {
break;
}
i--;
}
if (i >= 0) {
int j = len - 1;
while (nums[j] <= nums[i]) {
j--;
}
swap(nums, i, j);
}
if (i + 1 < len - 1) {
reverse(nums, i + 1, len - 1);
}
}
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public void reverse(int[] nums, int i, int j) {
while (i < j) {
swap(nums, i++, j--);
}
}
}
// 时间复杂度:O(n)
// 空间复杂度:O(1)
原则就是可以确定哪边是递增的数组,就从这边开始判断。
class Solution {
public int search(int[] nums, int target) {
int n = nums.length;
if (n == 0) {
return -1;
}
if (n == 1) {
return nums[0] == target ? 0 : -1;
}
int l = 0, r = n - 1;
while (l <= r) {
int mid = (l + r) / 2;
// 找到了
if (nums[mid] == target) {
return mid;
}
// 左边是递增数列,说明旋转的节点肯定在右边
if (nums[0] <= nums[mid]) {
// 因为左边是递增的,所以用左边来比较
if (nums[0] <= target && target < nums[mid]) {
r = mid - 1;
} else {
l = mid + 1;
}
} else {
// 左边不是递增数列,说明旋转的节点肯定在左边,右边就是递增数列
if (nums[mid] < target && target <= nums[n - 1]) {
// 因为右边是递增数列,所以拿右边来比较
l = mid + 1;
} else {
r = mid - 1;
}
}
}
return -1;
}
}
// 时间复杂度:O(logn)
// 空间复杂度:O(1)
和33题的区别就是,需要处理一个特殊情况:
nums[l] == nums[mid] && nums[mid] == nums[r]
class Solution {
public boolean search(int[] nums, int target) {
int n = nums.length;
if (n == 1) {
return nums[0] == target;
}
int l = 0, r = n - 1;
while (l <= r) {
int mid = (l + r) / 2;
if (nums[mid] == target) {
return true;
}
if (nums[l] == nums[mid] && nums[mid] == nums[r]) {
l++;
r--;
} else if (nums[l] <= nums[mid]) {
// 左边是递增
if (nums[l] <= target && target < nums[mid]) {
r = mid - 1;
} else {
l = mid + 1;
}
} else {
// 右边是递增
if (nums[mid] < target && target <= nums[r]) {
l = mid + 1;
} else {
r = mid - 1;
}
}
}
return false;
}
}
class Solution {
public int findMin(int[] nums) {
int len = nums.length;
int left = 0;
int right = len - 1;
int min = nums[0];
while (left <= right) {
int mid = left + (right - left) / 2;
min = Math.min(min, nums[mid]);
if (nums[0] < nums[mid]) {
// 左边是升序,可能最小可能出现的位置,是最左和右边的最小
left = mid + 1;
} else {
// 右边是升序,最可能出现的位置是,右边最小和左边最小
if (mid < len - 1) {
min = Math.min(nums[mid + 1], min);
}
right = mid - 1;
}
}
return min;
}
}
第一种思路:其实是寻找最大值。
class Solution {
public int findPeakElement(int[] nums) {
int idx = 0;
for(int i=1;i<nums.length;i++){
if(nums[i] > nums[idx]){
idx = i;
}
}
return idx;
}
}
// 利用二分的思路
class Solution {
public int findPeakElement(int[] nums) {
int left = 0, right = nums.length - 1;
while(left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > nums[mid + 1]) {
// 下降趋势,往左走
right = mid;
} else {
// 上升趋势,往右走
left = mid + 1;
}
}
return left;
}
}
和162寻找峰值其实是一样的。
class Solution {
public int peakIndexInMountainArray(int[] arr) {
int left = 0;
int right = arr.length -1;
while(left < right){
int mid = left + (right - left) /2;
if(arr[mid] < arr[mid+1]){
left = mid+1;
}else{
right = mid;
}
}
return left;
}
}
看了labuladong的题解,发现是翻转两次,先翻转整个字符串,然后再对每一个单词进行翻转,这个思路可以记住,但是这个题目没有必要这么做。
class Solution {
List<Integer> spiralOrder(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
int upper_bound = 0, lower_bound = m - 1;
int left_bound = 0, right_bound = n - 1;
List<Integer> res = new LinkedList<>();
// res.size() == m * n 则遍历完整个数组
while (res.size() < m * n) {
if (upper_bound <= lower_bound) {
// 在顶部从左向右遍历
for (int j = left_bound; j <= right_bound; j++) {
res.add(matrix[upper_bound][j]);
}
// 上边界下移
upper_bound++;
}
if (left_bound <= right_bound) {
// 在右侧从上向下遍历
for (int i = upper_bound; i <= lower_bound; i++) {
res.add(matrix[i][right_bound]);
}
// 右边界左移
right_bound--;
}
if (upper_bound <= lower_bound) {
// 在底部从右向左遍历
for (int j = right_bound; j >= left_bound; j--) {
res.add(matrix[lower_bound][j]);
}
// 下边界上移
lower_bound--;
}
if (left_bound <= right_bound) {
// 在左侧从下向上遍历
for (int i = lower_bound; i >= upper_bound; i--) {
res.add(matrix[i][left_bound]);
}
// 左边界右移
left_bound++;
}
}
return res;
}
}
class Solution {
public int[][] generateMatrix(int n) {
int val = 0;
int leftBound = 0, rightBound = n - 1;
int lowerBound = 0, upperBound = n - 1;
int[][] ans = new int[n][n];
int size = n * n;
while (val < size) {
if (lowerBound <= upperBound) {
for (int j = leftBound; j <= rightBound; j++) {
ans[lowerBound][j] = ++val;
}
lowerBound++;
}
if (leftBound <= rightBound) {
for (int i = lowerBound; i <= upperBound; i++) {
ans[i][rightBound] = ++val;
}
rightBound--;
}
if (lowerBound <= upperBound) {
for (int j = rightBound; j >= leftBound; j--) {
ans[upperBound][j] = ++val;
}
upperBound--;
}
if (leftBound <= rightBound) {
for (int i = upperBound; i >= lowerBound; i--) {
ans[i][leftBound] = ++val;
}
leftBound++;
}
}
return ans;
}
}
两个条件:
- 从左到右升序
- 每一行的第一个元素大于上一行的最后一个元素
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
// 元素肯定只存在某一行,因为元素都不相等,所以找到这一行,然后用二分就可以
int m = matrix.length;
int n = matrix[0].length;
// target肯定在row或后面
for (int i = 0; i < m; i++) {
// 必须满足开头的元素小于等于target,结尾的元素大于等于target
if (matrix[i][0] <= target && matrix[i][n-1] >= target) {
int left = 0;
int right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (matrix[i][mid] == target) {
return true;
} else if (matrix[i][mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
// 这一行没有搜到,那就是不存在,返回false
return false;
}
}
return false;
}
}
还有另一种思路,将二维数组打平成为一维数组,这样就可以用二分查找。
数组为m*n,将坐标 (x , y) 转为 y + n * x
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length;
int n = matrix[0].length;
int right = m * n - 1;
int left = 0;
while (left <= right) {
int mid = left + (right - left) / 2;
int y = mid % n;
int x = (mid - y) / n;
if (matrix[x][y] == target) {
return true;
} else if (matrix[x][y] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return false;
}
}
第二个条件改为:每一列从上到下递增。
// 暴力解法,效率不高
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length;
int n = matrix[0].length;
for (int i = 0; i < m; i++) {
// 过滤不符合条件的行
if (matrix[i][0] > target || matrix[i][n - 1] < target) {
continue;
}
int left = 0;
int right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (matrix[i][mid] == target) {
return true;
} else if (matrix[i][mid] < target) {
left++;
} else {
right--;
}
}
}
return false;
}
}
一个独特的思路: 如果从左上出发,那么无法确定下一步是往右走还是往下走,但是如果从右上出发,结果就很明确:
- 如果小于target,往下走
- 如果大于target,往走走
- 如果等于target,返回true
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length, n = matrix[0].length;
// 初始化在右上角
int i = 0, j = n - 1;
while (i < m && j >= 0) {
if (matrix[i][j] == target) {
return true;
}
if (matrix[i][j] < target) {
// 需要大一点,往下移动
i++;
} else {
// 需要小一点,往左移动
j--;
}
}
// while 循环中没有找到,则 target 不存在
return false;
}
}
翻转或轮转
| 序号 | 题目 | 完成 |
|---|---|---|
| 283. 移动零 | ✅ | |
| 向右轮转 | 189. 轮转数组 | ✅ |
| 396. 旋转函数 | ✅ | |
| 48. 旋转图像 | ✅ | |
| 1260. 二维网格迁移 | ✅ | |
| 1329. 将矩阵按对角线排序 | ||
| 867. 转置矩阵 | ||
| 835. 图像重叠 |
class Solution {
public void moveZeroes(int[] nums) {
int cur = 0;
int nonZero = 0;
int len = nums.length;
while (cur < len) {
if (nums[cur] != 0) {
nums[nonZero] = nums[cur];
nonZero++;
}
cur++;
}
for (int i = nonZero; i < len; i++) {
nums[i] = 0;
}
}
}
要求使用原地算法解决问题!
方法一:跳格子,每次遍历的时候,将距离k的数组全都替换了,一共遍历count次
class Solution {
public void rotate(int[] nums, int k) {
int n = nums.length;
k = k % n;
int count = gcd(k, n);
for (int start = 0; start < count; ++start) {
int current = start;
int prev = nums[start];
do {
int next = (current + k) % n;
int temp = nums[next];
nums[next] = prev;
prev = temp;
current = next;
} while (start != current);
}
}
// 最大公约数,k小于n
// 如果能除尽,那就是k自身
// 如果除不尽,那就是1
public int gcd(int x, int y) {
return y > 0 ? gcd(y, x % y) : x;
}
}
方法二:轮转三次
class Solution {
public void rotate(int[] nums, int k) {
int len = nums.length;
// k有可能大于len
k = k % len;
rotate(nums, 0, len - k - 1);
rotate(nums, len - k, len - 1);
rotate(nums, 0, len - 1);
}
public void rotate(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
}
时间复杂度:O(n)
空间复杂度:O(1)
方法三就是用k的临时空间存储移动数组。
计算轮转后的函数元素乘以其索引的总和,用数学公式找出了F(k)和F(k-1)的关系。
class Solution {
public int maxRotateFunction(int[] nums) {
int f = 0, n = nums.length, numSum = Arrays.stream(nums).sum();
for (int i = 0; i < n; i++) {
f += i * nums[i];
}
int res = f;
for (int i = n - 1; i > 0; i--) {
f += numSum - n * nums[i];
res = Math.max(res, f);
}
return res;
}
}
class Solution {
public void rotate(int[][] matrix) {
int tmp = 0;
int m = matrix.length;
for(int i=0;i<m/2;i++){
for(int j=0;j<(m+1)/2;j++){
tmp = matrix[i][j];
matrix[i][j] = matrix[m-j-1][i];
matrix[m-j-1][i] = matrix[m-i-1][m-j-1];
matrix[m-i-1][m-j-1] = matrix[j][m-i-1];
matrix[j][m-i-1] = tmp;
}
}
}
}
class Solution {
public List<List<Integer>> shiftGrid(int[][] grid, int k) {
int m = grid.length;
int n = grid[0].length;
for (int x = 0; x < k; x++) {
// 先把最后元素缓存起来
int tmp = grid[m - 1][n - 1];
for (int i = m - 1; i >= 0; i--) {
for (int j = n - 2; j >= 0; j--) {
grid[i][j + 1] = grid[i][j];
}
// 第一个元素从上一行借
if (i > 0) {
grid[i][0] = grid[i - 1][n - 1];
}
}
grid[0][0] = tmp;
}
List<List<Integer>> ans = new ArrayList<>();
for (int i = 0; i < m; i++) {
List<Integer> rows = new ArrayList<>();
for (int j = 0; j < n; j++) {
rows.add(grid[i][j]);
}
ans.add(rows);
}
return ans;
}
}