代码随想录算法训练营第二天| LeetCode977. 有序数组的平方、209.长度最小的子数组、59.螺旋矩阵II、排序算法复习

题目链接:977. 有序数组的平方 - 力扣(LeetCode)

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

心得:对于已经非递减顺序排序的整数数组,其平方最大值一定在数组两端之一,明白这点就可以使用双指针解决问题了。

借此题顺便也复习了几个常见的排序算法,见文末。

class Solution977{
    public int[] sortedSquares(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            nums[i] = nums[i] * nums[i];
        }
        // 使用两个指针先将最大的平方值相加
        int[] result = new int[nums.length];;
        int k = nums.length - 1;
        int i = 0;
        int j = nums.length - 1;
        while(i <= j){
            if (nums[i] >= nums[j]){
                result[k--] = nums[i++];
            }else {
                result[k--] = nums[j--];
            }
        }
        return result;
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

题目链接:209. 长度最小的子数组 - 力扣(LeetCode)

给定一个含有 n 个正整数的数组和一个正整数 target 。找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度如果不存在符合条件的子数组,返回 0 。

尝试暴力解决,结果超时。

class Solution209 {
    public int minSubArrayLen(int target, int[] nums){
        int ans = Integer.MAX_VALUE;
        int n_sum = Arrays.stream(nums).sum();

        if (n_sum < target){
            return 0;
        }
        for (int i = 0; i < nums.length; i++) {
            int currentSum = 0;
            for (int j = i; j < nums.length; j++) {
                currentSum += nums[j];
                if (currentSum >= target){
                    ans = ans < j - i + 1 ? ans : j - i + 1;
                    break; // 找到满足条件的子数组后,立即退出内层循环
                }
            }
        }
        return ans == Integer.MAX_VALUE ? 0 : ans;
    }
}

学习使用滑动窗口来解决问题。需要思考的点:循环里面应该用窗口的起始位置还是终止位置?如何移动起始位置left?得到满足条件的滑动窗口后如何移动窗口?

关键理解:while (sum >= target){...}

class Solution209 {
    public int minSubArrayLen(int target, int[] nums){
        int ans = Integer.MAX_VALUE;
        int left = 0; // 窗口左端
        int sum = 0;
        for (int right = 0; right < nums.length; right++) { // 窗口右端
            sum += nums[right];
            while (sum >= target){ // 思考为什么用while?
                // 收集此时的区间长度,更新滑动窗口大小
                ans = ans < right - left + 1 ? ans : right - left + 1;
                // 窗口左端右移
                sum -= nums[left];
                left ++;
            }
        }
        return ans == Integer.MAX_VALUE ? 0 : ans;
    }
}

题目链接:59. 螺旋矩阵 II - 力扣(LeetCode)

给你一个正整数 n ,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。

心得:提前设置好矩阵四个角的边界,按照顺序①从左往右、②从上往下、③从右往左、④从下往上依次填数,并且每填完一行或一列后 更新边界 。因为每次都把边界上的数都填了,每次更新边界就相当于矩阵少了一行或一列,这样就不会影响后面数据的填充。

class Solution59 {
    public int[][] generateMatrix(int n) {
        int[][] mat = new int[n][n];
        int num = 1;
        // 定义边界
        int l = 0, r = n - 1, t = 0, b = n - 1;
        while(num <= n*n){
            // 从左往右填数 填完一行边界t+1
            for (int i = l; i <= r; i++) {
                mat[t][i] = num++;
            }
            t++;
            // 从上往下填 填完一列边界r-1
            for (int i = t; i <= b; i++) {
                mat[i][b] = num++;
            }
            r--;
            // 从右往左填 填完一行边界b--
            for (int i = r; i >= l; i--) {
                mat[b][i] = num++;
            }
            b--;
            // 从下往上填 填完一列边界l++
            for (int i = b; i >= t; i--) {
                mat[i][l] = num++;
            }
            l++;
        }
        return mat;
    }
}

代码随想录里给出的参考代码使用的思想时以圈数为while循环条件,使用左闭右开的判断条件,也易于理解,两种方法都有不错的思想值得回味。

class Solution {
    public int[][] generateMatrix(int n) {
        int[][] nums = new int[n][n];
        int startX = 0, startY = 0;  // 每一圈的起始点
        int offset = 1;
        int count = 1;  // 矩阵中需要填写的数字
        int loop = 1; // 记录当前的圈数
        int i, j; // j 代表列, i 代表行;

        while (loop <= n / 2) {

            // 顶部
            // 左闭右开,所以判断循环结束时, j 不能等于 n - offset
            for (j = startY; j < n - offset; j++) {
                nums[startX][j] = count++;
            }

            // 右列
            // 左闭右开,所以判断循环结束时, i 不能等于 n - offset
            for (i = startX; i < n - offset; i++) {
                nums[i][j] = count++;
            }

            // 底部
            // 左闭右开,所以判断循环结束时, j != startY
            for (; j > startY; j--) {
                nums[i][j] = count++;
            }

            // 左列
            // 左闭右开,所以判断循环结束时, i != startX
            for (; i > startX; i--) {
                nums[i][j] = count++;
            }
            startX++;
            startY++;
            offset++;
            loop++;
        }
        if (n % 2 == 1) { // n 为奇数时,单独处理矩阵中心的值
            nums[startX][startY] = count;
        }
        return nums;
    }
}

常见排序算法复习

按顺序依次实现:

选择排序、插入排序、冒泡排序、希尔排序、并归排序、快速排序、堆排序

class VariousSortAlgorithms{
    public static void swap(int[] arr, int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    // 一、选择排序 1、时间复杂度:O(n2)  2、空间复杂度:O(1)  3、非稳定排序  4、原地排序
    public void selectSort(int[] a){
        if(a == null || a.length < 2){
            return;
        }
        for (int i = 0; i < a.length - 1; i++) {
            int min = i;
            // 遍历找到此轮最小值
            for (int j = i + 1; j < a.length; j++) {
                if(a[min] > a[j]) min = j;
            }
            // 交换元素
            swap(a, min, i);
        }
    }

    // 二、插入排序 1、时间复杂度:O(n2)  2、空间复杂度:O(1)  3、稳定排序  4、原地排序
    public void insertSort(int[] a) {
        if(a == null || a.length<2){
            return;
        }
        for (int i = 1; i < a.length; i++) {
            int temp = a[i];
            int k = i - 1;
            // 找到需要插入的位置
            while (k >= 0 && a[k] > temp){
                k--;
            }
            // 腾出位置(元素向后移)
            for (int j = i; j > k+1 ; j--) {
                a[j] = a[j-1];
            }
            // 插入
            a[k+1] = temp;
        }
    }

    // 三、冒泡排序 1、时间复杂度:O(n2)  2、空间复杂度:O(1)  3、稳定排序  4、原地排序
    public void bubbleSort(int[] arr){
        if (arr == null || arr.length < 2) {
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            boolean flag = false;
            for (int j = 0; j < arr.length - i -1; j++) {
                if (arr[j] > arr[j + 1]){
                    swap(arr, j ,j + 1);
                    flag = true;
                }
            }
            // 一轮下来是否发生了位置交换
            if(!flag)
                break;
        }
    }

    // 四、希尔排序(插入排序的变种)1、时间复杂度:O(nlogn)  2、空间复杂度:O(1)  3、非稳定排序  4、原地排序
    public void shellSort(int[] arr){
        if (arr == null || arr.length < 2) {
            return;
        }
        int n = arr.length;
        // 对每组间隔为h的小组进行排序
        for (int h = n / 2; h > 0; h /= 2) {
            // 对各个分组进行插入排序
            for (int i = h; i < n; i++) {
                // 将arr[i]插入到所在分组的正确位置上
                int temp = arr[i];
                int k;
                for (k = i - h; k >= 0 && arr[k] > temp; k -= h){
                    arr[k+h] = arr[k];
                }
                arr[k + h] = temp;
            }
        }
    }

    // 五、归并排序 1、时间复杂度:O(nlogn)  2、空间复杂度:O(n)  3、稳定排序  4、非原地排序
    public void mergeSort(int[] arr){
        if (arr == null || arr.length < 2) {
            return;
        }
        int[] temp = new int[arr.length];
        mergeSortFunction(arr, 0, arr.length - 1, temp);
    }

    private void mergeSortFunction(int[] arr, int left, int right, int[] temp){
        // left == right,表示数组只有一个元素
        if (left < right) {
            int mid = (left + right) / 2;  // 大数组分隔成两个数组
            mergeSortFunction(arr, left, mid, temp);  // 对左半部分进行排序
            mergeSortFunction(arr, mid + 1, right, temp);  // 对右半部分进行排序
            merge(arr, left, mid, right, temp);  // 进行合并
        }
    }
    // 合并函数,把两个有序的数组合并起来
    private void merge(int[] arr, int left, int mid, int right, int[] temp) {
        int i = left;
        int j = mid + 1;
        int t = 0;

        // 将左右两部分按顺序合并到temp数组中
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[t++] = arr[i++];
            } else {
                temp[t++] = arr[j++];
            }
        }

        // 将剩余的左半部分元素移入temp中
        while (i <= mid) {
            temp[t++] = arr[i++];
        }

        // 将剩余的右半部分元素移入temp中
        while (j <= right) {
            temp[t++] = arr[j++];
        }

        // 将temp中的元素拷贝回原数组
        t = 0;
        while (left <= right) {
            arr[left++] = temp[t++];
        }
    }


    // 六、快速排序 1、时间复杂度:O(nlogn)  2、空间复杂度:O(logn)  3、非稳定排序  4、原地排序
    public void quickSort(int[] arr){
        if (arr == null || arr.length < 2) {
            return;
        }
        quickSortFunction(arr, 0, arr.length - 1);
    }
    private static void quickSortFunction(int[] arr, int left, int right) {
        if (left < right) {
            int pivotIndex = partition(arr, left, right);
            quickSortFunction(arr, left, pivotIndex - 1);
            quickSortFunction(arr, pivotIndex + 1, right);
        }
    }

    private static int partition(int[] arr, int left, int right) {
        int pivot = arr[right]; // 选择最后一个元素作为基准
        int i = left - 1; // i指向小于基准的最后一个元素

        for (int j = left; j < right; j++) {
            if (arr[j] <= pivot) {
                i++;
                swap(arr, i, j);
            }
        }
        swap(arr, i + 1, right); // 将基准元素放到正确的位置
        return i + 1; // 返回基准元素的位置
    }

    // 七、堆排序  1、时间复杂度:O(nlogn)  2、空间复杂度:O(1)  3、非稳定排序  4、原地排序
    public void headSort(int[] arr){
        if (arr == null || arr.length < 2) {
            return;
        }
        int n = arr.length;
        // 构建大顶堆
        for (int i = (n - 2) / 2; i >= 0; i--) {
            downAdjust(arr, i, n - 1);
        }
        // 进行堆排序
        for (int i = n - 1; i >= 1; i--) {
            // 把堆顶元素与最后一个元素交换
            int temp = arr[i];
            arr[i] = arr[0];
            arr[0] = temp;// 把打乱的堆进行调整,恢复堆的特性
            downAdjust(arr, 0, i - 1);
        }
    }

    public static void downAdjust(int[] arr, int parent, int n){
        //临时保存要下沉的元素
        int temp = arr[parent];
        //定位左孩子节点的位置
        int child = 2 * parent + 1;
        //开始下沉
        while (child <= n) {
            // 如果右孩子节点比左孩子大,则定位到右孩子
            if(child + 1 <= n && arr[child] < arr[child + 1])
                child++;
            // 如果孩子节点小于或等于父节点,则下沉结束
            if (arr[child] <= temp ) break;
            // 父节点进行下沉
            arr[parent] = arr[child];
            parent = child;
            child = 2 * parent + 1;

        }
        arr[parent] = temp;
    }
}

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值