题目链接: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;
}
}