数组遍历与翻转

159 阅读4分钟

遍历

微信图片_20230510150338 (2).jpg

序号题目完成
31. 下一个排列
162. 寻找峰值
852. 山脉数组的峰顶索引
151. 反转字符串中的单词
54. 螺旋矩阵
59. 螺旋矩阵 II
33. 搜索旋转排序数组
1095. 山脉数组中查找目标值
81. 搜索旋转排序数组 II
153. 寻找旋转排序数组中最小值
154. 寻找旋转排序数组中的最小值 II
74. 搜索二维矩阵
240. 搜索二维矩阵 II
剑指 Offer 29. 顺时针打印矩阵
剑指 Offer 58 - I. 翻转单词顺序
剑指 Offer 04. 二维数组中的查找

数组的遍历最好是考虑用二分查找,可以有效的讲时间复杂度降为logN。

31. 下一个排列

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)

33. 搜索旋转排序数组

原则就是可以确定哪边是递增的数组,就从这边开始判断。

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)

81. 搜索旋转排序数组 II

和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;
    }
}

81. 搜索旋转排序数组 II

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;
    }
}

162. 寻找峰值

第一种思路:其实是寻找最大值。

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;
    }
}

852. 山脉数组的峰顶索引

和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;
    }
}

151. 反转字符串中的单词

相同题目:剑指 Offer 58 - I. 翻转单词顺序

看了labuladong的题解,发现是翻转两次,先翻转整个字符串,然后再对每一个单词进行翻转,这个思路可以记住,但是这个题目没有必要这么做。

54. 螺旋矩阵

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;
    }
}

59. 螺旋矩阵 II

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;
    }
}

74. 搜索二维矩阵

两个条件:

  1. 从左到右升序
  2. 每一行的第一个元素大于上一行的最后一个元素
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;
    }
}

240. 搜索二维矩阵 II

相同问题:剑指 Offer 04. 二维数组中的查找

第二个条件改为:每一列从上到下递增。

// 暴力解法,效率不高
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;
    }
}

一个独特的思路: 如果从左上出发,那么无法确定下一步是往右走还是往下走,但是如果从右上出发,结果就很明确:

  1. 如果小于target,往下走
  2. 如果大于target,往走走
  3. 如果等于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;
    }
}

翻转或轮转

微信图片_20230504100148 (2).jpg

序号题目完成
283. 移动零
向右轮转189. 轮转数组
396. 旋转函数
48. 旋转图像
1260. 二维网格迁移
1329. 将矩阵按对角线排序
867. 转置矩阵
835. 图像重叠

283. 移动零

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;
        }
    }
}

189. 轮转数组

要求使用原地算法解决问题!

方法一:跳格子,每次遍历的时候,将距离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的临时空间存储移动数组。

396. 旋转函数

计算轮转后的函数元素乘以其索引的总和,用数学公式找出了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;
    }
}

48. 旋转图像

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;
            }
        }
    }
}

1260. 二维网格迁移

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;
    }
}