leetcode第3~4题

简介: 遗憾的是上边的算法没有通过 leetCode,时间复杂度太大,造成了超时。我们怎么来优化一下呢?上边的算法中,我们假设当 i 取 0 的时候,j 取 1,判断字符串 str[0,1) 中有没有重复的字符。j 取 2,判断字符串 str[0,2) 中有没有重复的字符。j 取 3,判断字符串 str[0,3) 中有没有重复的字符。j 取 4,判断字符串 str[0,4) 中有没有重复的字符。做了很多重复的工作,因为如果 str[0,3) 中没有重复的字符,我们不需要再判断整个字符串 str[0,4) 中有没有重复的字符,而只需要判断 str[3] 在不在 str[0,3)

top3

解法一

简单粗暴些,找一个最长子串,那么我们用两个循环穷举所有子串,然后再用一个函数判断该子串中有没有重复的字符。

publicintlengthOfLongestSubstring(Strings) {
intn=s.length();
intans=0;//保存当前得到满足条件的子串的最大值for (inti=0; i<n; i++)
for (intj=i+1; j<=n; j++) //之所以 j<= n,是因为我们子串是 [i,j),左闭右开if (allUnique(s, i, j)) ans=Math.max(ans, j-i); //更新 ansreturnans;
}
publicbooleanallUnique(Strings, intstart, intend) {
Set<Character>set=newHashSet<>();//初始化 hash setfor (inti=start; i<end; i++) {//遍历每个字符Characterch=s.charAt(i);
if (set.contains(ch)) returnfalse; //判断字符在不在 set 中set.add(ch);//不在的话将该字符添加到 set 里边    }
returntrue;
}

时间复杂度:两个循环,加上判断子串满足不满足条件的函数中的循环,O(n³)。

空间复杂度:使用了一个 set,判断子串中有没有重复的字符。由于 set 中没有重复的字符,所以最长就是整个字符集,假设字符集的大小为 m ,那么 set 最长就是 m 。另一方面,如果字符串的长度小于 m ,是 n 。那么 set 最长也就是 n 了。综上,空间复杂度为 O(min(m,n))。


解法二

遗憾的是上边的算法没有通过 leetCode,时间复杂度太大,造成了超时。我们怎么来优化一下呢?

上边的算法中,我们假设当 i 取 0 的时候,

j 取 1,判断字符串 str[0,1) 中有没有重复的字符。

j 取 2,判断字符串 str[0,2) 中有没有重复的字符。

j 取 3,判断字符串 str[0,3) 中有没有重复的字符。

j 取 4,判断字符串 str[0,4) 中有没有重复的字符。

做了很多重复的工作,因为如果 str[0,3) 中没有重复的字符,我们不需要再判断整个字符串 str[0,4) 中有没有重复的字符,而只需要判断 str[3] 在不在 str[0,3) 中,不在的话,就表明 str[0,4) 中没有重复的字符。

如果在的话,那么 str[0,5) ,str[0,6) ,str[0,7) 一定有重复的字符,所以此时后边的 j 也不需要继续增加了。i ++ 进入下次的循环就可以了。

此外,我们的 j 也不需要取 j + 1,而只需要从当前的 j 开始就可以了。

综上,其实整个关于 j 的循环我们完全可以去掉了,此时可以理解变成了一个「滑动窗口」。

image.png


整体就是橘色窗口在依次向右移动。

判断一个字符在不在字符串中,我们需要可以遍历整个字符串,遍历需要的时间复杂度就是 O(n),加上最外层的 i 的循环,总体复杂度就是 O(n²)。我们可以继续优化,判断字符在不在一个字符串,我们可以将已有的字符串存到 Hash 里,这样的时间复杂度是 O(1),总的时间复杂度就变成了 O(n)。

publicclassSolution {
publicintlengthOfLongestSubstring(Strings) {
intn=s.length();
Set<Character>set=newHashSet<>();
intans=0, i=0, j=0;
while (i<n&&j<n) {
if (!set.contains(s.charAt(j))){
set.add(s.charAt(j++));
ans=Math.max(ans, j-i);
            }
else {
set.remove(s.charAt(i++));
            }
        }
returnans;
    }
}

时间复杂度:在最坏的情况下,while 循环中的语句会执行 2n 次,例如 abcdefgg,开始的时候 j 一直后移直到到达第二个 g 的时候固定不变 ,然后 i 开始一直后移直到 n ,所以总共执行了 2n 次,时间复杂度为 O(n)。

空间复杂度:和上边的类似,需要一个 Hash 保存子串,所以是 O(min(m,n))。


top4


已知两个有序数组,找到两个数组合并后的中位数。

解法一

简单粗暴,先将两个数组合并,两个有序数组的合并也是归并排序中的一部分。然后根据奇数,还是偶数,返回中位数。

publicdoublefindMedianSortedArrays(int[] nums1, int[] nums2) {
int[] nums;
intm=nums1.length;
intn=nums2.length;
nums=newint[m+n];
if (m==0) {
if (n%2==0) {
return (nums2[n/2-1] +nums2[n/2]) /2.0;
        } else {
returnnums2[n/2];
        }
    }
if (n==0) {
if (m%2==0) {
return (nums1[m/2-1] +nums1[m/2]) /2.0;
        } else {
returnnums1[m/2];
        }
    }
intcount=0;
inti=0, j=0;
while (count!= (m+n)) {
if (i==m) {
while (j!=n) {
nums[count++] =nums2[j++];
            }
break;
        }
if (j==n) {
while (i!=m) {
nums[count++] =nums1[i++];
            }
break;
        }
if (nums1[i] <nums2[j]) {
nums[count++] =nums1[i++];
        } else {
nums[count++] =nums2[j++];
        }
    }
if (count%2==0) {
return (nums[count/2-1] +nums[count/2]) /2.0;
    } else {
returnnums[count/2];
    }
}

时间复杂度:遍历全部数组,O(m + n)

空间复杂度:开辟了一个数组,保存合并后的两个数组,O(m + n)


解法二

其实,我们不需要将两个数组真的合并,我们只需要找到中位数在哪里就可以了。

开始的思路是写一个循环,然后里边判断是否到了中位数的位置,到了就返回结果,但这里对偶数和奇数的分类会很麻烦。当其中一个数组遍历完后,出了 for 循环对边界的判断也会分几种情况。总体来说,虽然复杂度不影响,但代码会看起来很乱。然后在 这里 找到了另一种思路。

首先是怎么将奇数和偶数的情况合并一下。

用 len 表示合并后数组的长度,如果是奇数,我们需要知道第 (len + 1)/ 2 个数就可以了,如果遍历的话需要遍历 int ( len / 2 ) + 1 次。如果是偶数,我们需要知道第 len / 2 和 len / 2 + 1 个数,也是需要遍历 len / 2 + 1 次。所以遍历的话,奇数和偶数都是 len / 2 + 1 次。

返回中位数的话,奇数需要最后一次遍历的结果就可以了,偶数需要最后一次和上一次遍历的结果。所以我们用两个变量 left 和 right ,right 保存当前循环的结果,在每次循环前将 right 的值赋给 left 。这样在最后一次循环的时候,left 将得到 right 的值,也就是上一次循环的结果,接下来 right 更新为最后一次的结果。

循环中该怎么写,什么时候 A 数组后移,什么时候 B 数组后移。用 aStart 和 bStart 分别表示当前指向 A 数组和 B 数组的位置。如果 aStart 还没有到最后并且此时 A 位置的数字小于 B 位置的数组,那么就可以后移了。也就是aStart < m && A[aStart] < B[bStart]。

但如果 B 数组此刻已经没有数字了,继续取数字B [ bStart ],则会越界,所以判断下 bStart 是否大于数组长度了,这样 || 后边的就不会执行了,也就不会导致错误了,所以增加为 aStart < m && ( bStart >= n || A [ aStart ] < B [ bStart ] ) 。

publicdoublefindMedianSortedArrays(int[] A, int[] B) {
intm=A.length;
intn=B.length;
intlen=m+n;
intleft=-1, right=-1;
intaStart=0, bStart=0;
for (inti=0; i<=len/2; i++) {
left=right;
if (aStart<m&& (bStart>=n||A[aStart] <B[bStart])) {
right=A[aStart++];
        } else {
right=B[bStart++];
        }
    }
if ((len&1) ==0)
return (left+right) /2.0;
elsereturnright;
}

时间复杂度:遍历 len/2 + 1 次,len = m + n ,所以时间复杂度依旧是 O(m + n)。

空间复杂度:我们申请了常数个变量,也就是 m,n,len,left,right,aStart,bStart 以及 i 。

总共 8 个变量,所以空间复杂度是 O(1)。

相关文章
|
6月前
leetcode-1447:最简分数
leetcode-1447:最简分数
46 0
|
6月前
|
消息中间件 Kubernetes NoSQL
LeetCode 3、28、1351
LeetCode 3、28、1351
|
6月前
|
消息中间件 Kubernetes NoSQL
LeetCode 1359、1360
LeetCode 1359、1360
|
Python
LeetCode 1904. 你完成的完整对局数
一款新的在线电子游戏在近期发布,在该电子游戏中,以 刻钟 为周期规划若干时长为 15 分钟 的游戏对局。这意味着,在 HH:00、HH:15、HH:30 和 HH:45 ,将会开始一个新的对局,其中 HH 用一个从 00 到 23 的整数表示。游戏中使用 24 小时制的时钟 ,所以一天中最早的时间是 00:00 ,最晚的时间是 23:59 。
104 0
LeetCode 354. Russian Doll Envelopes
给定一些标记了宽度和高度的信封,宽度和高度以整数对形式 (w, h) 出现。当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。 请计算最多能有多少个信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。
81 0
LeetCode 354. Russian Doll Envelopes
|
C++ Python
LeetCode 771. Jewels and Stones
LeetCode 771. Jewels and Stones
82 0
leetcode第53题
解法一和解法二的动态规划,只是在定义的时候一个表示以 i 开头的子数组,一个表示以 i 结尾的子数组,却造成了时间复杂度的差异。问题就是解法一中求出了太多的没必要的和,不如解法二直接,只保存最大的和。
leetcode第53题
leetcode第55题
当自己按照 45 题的思路写完的时候,看 Solution 的时候都懵逼了,这道题竟然这么复杂?不过 Solution 把问题抽象成动态规划的思想,以及优化的过程还是非常值得学习的。
leetcode第55题
|
存储
leetcode第52题
可以发现对于同一条副对角线,row + col 的值是相等的。 对于同一条主对角线,row - col 的值是相等的。 我们同样可以用一个 bool 型数组,来保存当前对角线是否有元素,把它们相加相减的值作为下标。 对于 row - col ,由于出现了负数,所以可以加 1 个 n,由 [ - 3, 3 ] 转换为 [ 1 , 7 ] 。
leetcode第52题