1.合并两个有序数组(88-易)
题目描述:给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。
示例:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
思路:本题比较简单的思路是:进行拼接然后再排序(插入排序或库函数)或者使用额外空间进行存储(使用for循环或者库函数)
System.arraycopy(dataType[] srcArray,int srcIndex,int destArray,int destIndex,int length)
其中,srcArray 表示源数组;srcIndex 表示源数组中的起始索引;destArray 表示目标数组;destIndex 表示目标数组中的起始索引;length 表示要复制的数组长度。
这里最优解是三指针,利用数组有序性,原地的排序,即不使用额外空间。
- 关键是使用从尾向头开始遍历(三指针)
- 注意,这时谁大谁先走!
代码:
public void merge(int[] nums1, int m, int[] nums2, int n) {
int i = m - 1, j = n - 1, merge = m + n - 1;
while (i >= 0 || j >= 0) {
if (i < 0) {
nums1[merge--] = nums2[j--];
} else if (j < 0) {
nums1[merge--] = nums1[i--];
} else if (nums1[i] > nums2[j]) {
nums1[merge--] = nums1[i--];
} else {
nums1[merge--] = nums2[j--];
}
}
}
2.下一个排列(31-中)
题目描述:实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。要求:必须 原地 修改,只允许使用额外常数空间。
示例:
输入:nums = [1,2,3]
输出:[1,3,2]
输入:nums = [3,2,1]
输出:[1,2,3]
思路:本题目标是找下一个更大的排列,双指针,我们可以将算法分为两步:
-
逆序遍历,当第一次出现
nums[i] < nums[i + 1]
,记录此时的i为index2;然后我们去之前遍历中第一个比nums[i]
大的数对应的索引index2,交换。 - 我们已经替换了
i
位置的数,但其后的数一定降序(之前遍历过),根据字典序,我们要将i
后边的数逆序。
ps:为什么逆序遍历,目的是找最后一个出现升序的元素,然后找刚好比他大的第一个元素替换,符合字典序。
代码:
public void nextPermutation(int[] nums) {
int index1 = -1, index2 = -1;
for (int i = nums.length - 2; i >= 0; i--) {
if (nums[i] < nums[i + 1]) {
index1 = i;
break;
}
}
// 特判
if (index1 == -1) {
reverse(nums, 0, nums.length - 1);
return;
}
for (int i = nums.length - 1; i > index1; i--) {
if (nums[i] > nums[index1]) {
index2 = i;
break;
}
}
swap(nums, index1, index2);
reverse(nums, index1 + 1, nums.length - 1);
}
public void reverse(int[] nums, int i, int j) {
while (i < j) {
swap(nums, i++, j--);
}
}
public void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
3.盛最多水的容器(11-中)
题目描述:给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器。
示例:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
输入:height = [1,1]
输出:1
思路:
法1:暴力求解,时间复杂度O(n^2),注意:盛水木桶效应。但是遗憾的是超时了。
法2:本题的最优解是双指针,代码简单,主要是理解为什么这样可以:背后思想缩减空间,当双指针位于最两端,这是水的宽度最大:
- 如果左边的高度小,我们移动左指针(移除左边一个柱子),那问题就是,右指针移动的那些就不考虑了吗?其实,由于左边柱子高度低,所以这是个瓶颈,无论怎么移动右指针,都不会比现在的面积大(宽度在减少)。但是如果移动左指针,那么虽然宽度变小了,但是高度可能增大,这带来了不确定性,需要我们去判断。
- 同理移除右边一个柱子,分析相同。
代码:
// 暴力解
public int maxArea(int[] height) {
int n = height.length;
int max = 0;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
max = Math.max(max, Math.min(height[i], height[j]) * (j - i));
}
}
return max;
}
// 双指针
public int maxArea(int[] height) {
int l = 0, r = height.length - 1;
int max = 0;
while (l < r) {
max = height[l] < height[r] ?
Math.max(max, (r - l) * height[l++]):
Math.max(max, (r - l) * height[r--]);
}
return max;
}
4.颜色分类(75-中)
题目描述:给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
示例:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
思路:本题本质是荷兰国旗问题(快排的分区过程),在荷兰国旗中,我们是给定值num,判断数组中的值与num的大小,进而确定自己的位置。cur指针负责遍历数组,时间复杂度O(n)。定义两个边界指针zero和two,遍历数组跟新这两个边界。
- 当前值等于0,相当于推着大于0的部分向后走
- 当前值等于1(中间元素),cur指针移动
- 当前值等于2,需要用后边值进行交换后继续比较
代码:
public void sortColors(int[] nums) {
int zero = -1, two = nums.length;
int cur = 0;
while (cur < two) {
if (nums[cur] == 0) {
swap(nums, ++zero, cur++);
} else if (nums[cur] == 2) {
swap(nums, --two, cur);
} else {
cur++;
}
}
}
public void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
5.将数组分成和相等的三个部分(1013-易)
题目描述:给你一个整数数组 A,只有可以将其划分为三个和相等的非空部分时才返回 true,否则返回 false。
形式上,如果可以找出索引 i+1 < j 且满足 A[0] + A[1] + ... + A[i] == A[i+1] + A[i+2] + ... + A[j-1] == A[j] + A[j-1] + ... + A[A.length - 1] 就可以将数组三等分。
示例:
输入:[0,2,1,-6,6,-7,9,1,2,0,1]
输出:true
解释:0 + 2 + 1 = -6 + 6 - 7 + 9 + 1 = 2 + 0 + 1
思路: 本题先计算数组的总和,如果不能被3整除,直接返回false,否则:
法1:使用双指针遍历,注意遍历终止条件,为 l + 1 < r
,保证数组被划分为三个部分,也就是中间有一个元素的情况。注意细节:左右和初始值设为第一个元素,否则指针提前结束的指针会多移动一次,可能提前退出循环,如[3,3,6,5,-2,2,5,1,-9,4],返回false。
法2:直接查找,当等于target的个数为3,则一定能被分成三部分。其实,可能存在这个个数大于3的情况(目标值target为0的情况),但是我们可以提前终止,否则总和不为0,也就不会出现个数大于3的情况。
代码:
// 双指针
public boolean canThreePartsEqualSum(int[] nums) {
int n = nums.length;
int sum = 0;
for (int num : nums) {
sum += num;
}
if (sum % 3 != 0) {
return false;
}
int target = sum / 3;
int l = 0, r = n - 1;
int lSum = nums[l], rSum = nums[r];
while (l + 1 < r) {
if (lSum == target && rSum == target) {
return true;
}
if (lSum != target) {
lSum += nums[++l];
}
if (rSum != target) {
rSum += nums[--r];
}
}
return false;
}
}
// 直接查找
public boolean canThreePartsEqualSum(int[] nums) {
int n = nums.length;
int sum = 0;
for (int num : nums) {
sum += num;
}
if (sum % 3 != 0) {
return false;
}
int target = sum / 3;
int flag = 0;
int curSum = 0;
for (int num : nums) {
curSum += num;
if (curSum == target) {
flag++;
curSum = 0;
}
if (flag == 3) {
return true;
}
}
return false;
}
6.有序数组的平方(977-易)
题目描述:给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
思路:核心考察点是利用原数组有序,但是可以通过这个题练习一下常见的排序算法。
最优解为双指针,因为结果最大值一定出现在原数组最大值或者最小值,故可以直接使用两个指针从两头进行遍历。
代码:
// 直接调库函数
public int[] sortedSquares(int[] nums) {
int n = nums.length;
for (int i = 0; i < n; i++) {
nums[i] = nums[i] * nums[i];
}
Arrays.sort(nums);
return nums;
}
// 双指针
public int[] sortedSquares(int[] nums) {
int n = nums.length;
int index = n - 1;
int[] ans = new int[n];
for (int i = 0, j = n - 1; i <= j; ) {
if (nums[i] * nums[i] > nums[j] * nums[j]) {
ans[index--] = nums[i] * nums[i];
i++;
} else {
ans[index--] = nums[j] * nums[j];
j--;
}
}
return ans;
}
7.按奇偶排序数组 II(922-易)
题目描述:给定一个非负整数数组 A, A 中一半整数是奇数,一半整数是偶数。
对数组进行排序,以便当 A[i] 为奇数时,i 也是奇数;当 A[i] 为偶数时, i 也是偶数。
你可以返回任何满足上述条件的数组作为答案。
示例:
输入:[4,2,5,7]
输出:[4,5,2,7]
解释:[4,7,2,5],[2,5,4,7],[2,7,4,5] 也会被接受。
思路:本题两种解法,适用两种情况。
法1:可以使用额外空间。两遍遍历,奇数放在新数组奇数位置,偶数放在新数组偶数位置。
法2:不使用额外空间,可以原地修改数组。使用奇偶指针,遍历数组,寻找两个都没放对位置的,交换两个数。
代码:
// 两遍遍历
public int[] sortArrayByParityII(int[] nums) {
int n = nums.length;
int[] tmp = new int[n];
int index = 0;
for (int i = 0; i < n; i++) {
if (nums[i] % 2 == 0) {
tmp[index] = nums[i];
index += 2;
}
}
index = 1;
for (int i = 0; i < n; i++) {
if (nums[i] % 2 == 1) {
tmp[index] = nums[i];
index += 2;
}
}
return tmp;
}
// 快慢指针
public int[] sortArrayByParityII(int[] nums) {
int n = nums.length;
int j = 1;
for (int i = 0; i < n; i += 2) {
if (nums[i] % 2 == 1) {
while (nums[j] % 2 == 1) {
j += 2;
}
swap(nums, i, j);
}
}
return nums;
}
public void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}