算法总结
最近作者在坚持每日一道中等难度算法题,也是作者考核时经常会碰到的难度,由于经常是到22:30才意识到自己并没有写算法,因此都是打开LeetCode网站随机一题,并未系统性的去学习,这一点值得反思。在做题过程中经常会遇到一些问题,最常见的便是时间复杂度的不理想、符号公式的积累不够深,因此作者把遇到的问题整理出来,从自己的思路,再到题解的思路,以及补充的知识三个方面来分享一些算法题
1775.通过最少操作次数使数的和相等
首先先来判断什么样的情况是无法使两数组的和相等,这个时候采取极端方法,当长度较小的数组全为6,而长度较大的全为1,这时候如果都无法长度较小的数组都无法比另外一方大,那就真的没办法了。因此这就是我们的判断条件
if((nums1.length > nums2.length * 6) || (nums2.length > nums1.length * 6)) {
return -1;
}
再之后的思路,因为要操作次数最少,那必然是数组中被操作的那个数弹性很大,也就是和大的数组先调整最大的数,和小的数组先调整最小数。但还是得先判断两个数组的数哪个弹性最大,因此整体思路就是先判断是否能调整,排序,比较弹性,计数并输出结果。
下面给出作者的原始代码:
class Solution {
public int minOperations(int[] nums1, int[] nums2) {
if((nums1.length > nums2.length * 6) || (nums2.length > nums1.length * 6)) {
return -1;
}
Arrays.sort(nums1);
Arrays.sort(nums2);
int distance = sum(nums1) - sum(nums2);
if(distance == 0) {
return 0;
}
int left = 0;
int right;
int count = 0;
if(distance > 0) {
right = nums1.length - 1;
while(distance > 0) {
if(left >= nums2.length) {
distance -= nums1[right] - 1;
right--;
if(right < 0 && distance > 0) {
return -1;
}
count++;
continue;
}
if(right < 0) {
distance -= 6 - nums2[left];
left++;
if(left == nums2.length && distance > 0) {
return -1;
}
count++;
continue;
}
if(6 - nums2[left] > nums1[right] - 1) {
distance -= 6 - nums2[left];
left++;
} else {
distance -= nums1[right] - 1;
right--;
}
count++;
}
}else {
right = nums2.length - 1;
while(distance < 0) {
if(left >= nums1.length) {
distance += nums2[right] - 1;
right--;
if(right < 0 && distance < 0) {
return -1;
}
count++;
continue;
}
if(right < 0) {
distance += 6 - nums1[left];
left++;
if(left == nums1.length && distance < 0) {
return -1;
}
count++;
continue;
}
if(6 - nums1[left] > nums2[right] - 1) {
distance += 6 - nums1[left];
left++;
} else {
distance += nums2[right] - 1;
right--;
}
count++;
}
}
return count;
}
int sum(int[] arr) {
int res = 0;
for(int i = 0;i < arr.length; i++) {
res += arr[i];
}
return res;
}
}
但事实证明,作者仍然底蕴不足,此题无论是那种复杂度都惨淡的很
但作者实在脑子不够用了,果断打开题解,一看,不禁感慨,妙!
先贴代码
class Solution {
public int minOperations(int[] nums1, int[] nums2) {
int n = nums1.length, m = nums2.length;
if (6 * n < m || 6 * m < n) {
return -1;
}
int[] cnt1 = new int[7];
int[] cnt2 = new int[7];
int diff = 0;
for (int i : nums1) {
++cnt1[i];
diff += i;
}
for (int i : nums2) {
++cnt2[i];
diff -= i;
}
if (diff == 0) {
return 0;
}
if (diff > 0) {
return help(cnt2, cnt1, diff);
}
return help(cnt1, cnt2, -diff);
}
public int help(int[] h1, int[] h2, int diff) {
int[] h = new int[7];
for (int i = 1; i < 7; ++i) {
h[6 - i] += h1[i];
h[i - 1] += h2[i];
}
int res = 0;
for (int i = 5; i > 0 && diff > 0; --i) {
int t = Math.min((diff + i - 1) / i, h[i]);
res += t;
diff -= t * i;
}
return res;
}
}
欣赏完之后,来分析,首先第一步,作者很荣幸的跟题解想到了一样的判断条件,想法在前面已经阐述过了,便不多言了
接着来看第二步,官方给了一个类似于计数器的做法,Java中 new出来的数组默认值都是0或者null,一边计数一边算差距,之后对差距进行判断,用自定义函数的返回值作为返回值,这相信大家都能懂。
int[] cnt1 = new int[7];
int[] cnt2 = new int[7];
int diff = 0;
for (int i : nums1) {
++cnt1[i];
diff += i;
}
for (int i : nums2) {
++cnt2[i];
diff -= i;
}
if (diff == 0) {
return 0;
}
if (diff > 0) {
return help(cnt2, cnt1, diff);
}
return help(cnt1, cnt2, -diff);
那么重点就是这个help函数是如何help时间复杂度的降低的,来细看
public int help(int[] h1, int[] h2, int diff) {
int[] h = new int[7];
for (int i = 1; i < 7; ++i) {
h[6 - i] += h1[i];
h[i - 1] += h2[i];
}
int res = 0;
for (int i = 5; i > 0 && diff > 0; --i) {
int t = Math.min((diff + i - 1) / i, h[i]);
res += t;
diff -= t * i;
}
return res;
}
作者当时看到代码就觉得,这代码和作者写的有点相似,只能说,同跨过一条槛,有人风风光光,有人狼狈不堪。回到正题,该函数的计数器,记录的是弹性值,因为h1[i]中的i代表的是数组里的数值,所以记录他最多能增加/减少多少,之后的循环就从最大弹性值开始一步步减,最后得出结果
总结:作者感觉题解对比作者的代码,最大的亮点就是采用计数,这样就少了很多弯弯绕绕的if语句
1014.最佳观光组合
作者看到这道题便想到了盛最多水的容器,于是打算依样画葫芦,但最后也就是画虎类犬罢了,后来作者想起了动态规划的定义,发现这确实有点像,苦思冥想,在看到题目中的一行话时幡然醒悟
value[i] + value[j] + i - j
这句话看似普普通通,但当我们调换顺序时,便能发现其中奥秘
value[i] + i + value[j] - j
把它当作两个变量来看,我们就可以想到一次遍历,不停判断max值即可,贴出代码
class Solution {
public int maxScoreSightseeingPair(int[] values) {
int max = 0;
int ax = values[0] + 0;
for(int i = 1; i < values.length; i++) {
max = Math.max(max, ax + values[i] - i);
ax = Math.max(ax, values[i] + i);
}
return max;
}
}
总结:一定要抓住内在的真正变量,抓住规律
接下来再来一道拆开来看的题目,这道题作者很幸运的做的较为出彩
33.搜索旋转排序数组
首先还是先来判断什么情况是必然找不到的,因为他是旋转过的,所以在(nums[nums.length - 1] , nums[0])是肯定找不到的,其他的之后再说,因此给出第一个判断条件
if(target < nums[0] && target > nums[nums.length - 1]) {
return -1;
}
之后,当我们分开看,其实这个数组可以看成是两个不会有重复且递增的数组拼接在了一起,所以判断target 可能在哪段数组,在for循环寻找,即可
class Solution {
public int search(int[] nums, int target) {
if(target < nums[0] && target > nums[nums.length - 1]) {
return -1;
}
if(target >= nums[0]) {
for(int i = 0; i < nums.length; i++) {
if(target == nums[i]) {
return i;
}
}
}else {
for(int i = nums.length-1; i >= 0; i--) {
if(target == nums[i]) {
return i;
}
}
}
return -1;
}
}